目錄
引言:
多進(jìn)程服務(wù)器
例程分享:
多線程服務(wù)器
?例程分享:
I/O多路復(fù)用服務(wù)器
select
例程分享:
poll
例程分享:
epoll
例程分享:
總結(jié)建議
引言:
????????隨著互聯(lián)網(wǎng)的迅猛發(fā)展,服務(wù)器面臨著越來越多的并發(fā)請求。如何設(shè)計一個能夠高效處理大量并發(fā)請求的服務(wù)器成為了一個關(guān)鍵問題。本文將介紹幾種常見的高并發(fā)服務(wù)器設(shè)計方案,包括多進(jìn)程服務(wù)器、多線程服務(wù)器、I/O多路復(fù)用服務(wù)器和epoll服務(wù)器,并分析它們的優(yōu)缺點(diǎn),以便讀者能夠選擇適合自己需求的設(shè)計方案。
多進(jìn)程服務(wù)器
利用fork創(chuàng)建子進(jìn)程處理每個連接請求。
優(yōu)點(diǎn):充分利用多核CPU的計算能力,隔離不同連接之間的資源。
?缺點(diǎn):父進(jìn)程需要設(shè)置較大的文件描述符限制,進(jìn)程創(chuàng)建和切換開銷較大。
相關(guān)API函數(shù):fork、waitpid、socket、bind、listen、accept、read、write、close
實(shí)現(xiàn)要點(diǎn):
- 父進(jìn)程close子進(jìn)程socket,避免泄漏。
- 信號處理回收子進(jìn)程。
- 每個子進(jìn)程處理一個連接請求。
例程分享:
/*
服務(wù)器監(jiān)聽端口,接收客戶端連接。
對每個連接fork子進(jìn)程處理請求。
子進(jìn)程循環(huán)接收客戶端數(shù)據(jù),轉(zhuǎn)換大小寫后返回。
父進(jìn)程關(guān)閉連接socket,信號函數(shù)回收子進(jìn)程。
客戶端連接后循環(huán)發(fā)送接收數(shù)據(jù)。
使用多進(jìn)程處理連接請求,充分利用多核CPU。
fork創(chuàng)建進(jìn)程,waitpid和信號處理回收子進(jìn)程。
父子進(jìn)程同步處理,避免混亂。
*/
/* server.c */
#include <stdio.h> // 標(biāo)準(zhǔn)IO頭文件
#include <string.h> // 字符串處理頭文件
#include <netinet/in.h> // socket編程頭文件
#include <arpa/inet.h> // IP地址轉(zhuǎn)換頭文件
#include <signal.h> // 信號處理頭文件
#include <sys/wait.h> // 等待子進(jìn)程頭文件
#include <sys/types.h> // 數(shù)據(jù)類型頭文件
#include "wrap.h" // socket函數(shù)封裝頭文件
#define MAXLINE 80 // 最大讀寫字節(jié)數(shù)
#define SERV_PORT 800 // 服務(wù)器端口號
// 信號處理函數(shù),回收子進(jìn)程
void do_sigchild(int num) {
while (waitpid(0, NULL, WNOHANG) > 0);
}
int main(void) {
// socket地址結(jié)構(gòu)
struct sockaddr_in servaddr, cliaddr;
// 客戶端地址長度
socklen_t cliaddr_len;
// 監(jiān)聽和連接socket
int listenfd, connfd;
// 數(shù)據(jù)緩沖區(qū)
char buf[MAXLINE];
// 客戶端IP字符串
char str[INET_ADDRSTRLEN];
int i, n; // 循環(huán)變量和讀字節(jié)數(shù)
pid_t pid; // 進(jìn)程ID
// 安裝信號處理函數(shù)
struct sigaction newact;
newact.sa_handler = do_sigchild;
sigemptyset(&newact.sa_mask);
newact.sa_flags = 0;
sigaction(SIGCHLD, &newact, NULL);
// 創(chuàng)建監(jiān)聽socket
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
// 初始化服務(wù)器地址結(jié)構(gòu)
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
// 綁定地址和端口
Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
// 設(shè)置監(jiān)聽隊列長度
Listen(listenfd, 20);
// 循環(huán)接收客戶端連接請求
while (1) {
Copy code
cliaddr_len = sizeof(cliaddr);
// 接收一個客戶端連接
connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
// Fork一個子進(jìn)程處理連接
pid = fork();
if (pid == 0) {
// 子進(jìn)程關(guān)閉監(jiān)聽socket
Close(listenfd);
// 處理客戶端請求
while (1) {
// 接收客戶端數(shù)據(jù)
n = Read(connfd, buf, MAXLINE);
// 判斷客戶端是否關(guān)閉
if (n == 0) {
printf("the other side has been closed.\n");
break;
}
// 打印客戶端信息
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port));
// 轉(zhuǎn)換為大寫
for (i = 0; i < n; i++)
buf[i] = toupper(buf[i]);
// 發(fā)送轉(zhuǎn)換后數(shù)據(jù)
Write(connfd, buf, n);
}
// 關(guān)閉連接
Close(connfd);
return 0;
} else if (pid > 0) {
// 父進(jìn)程關(guān)閉連接socket
Close(connfd);
} else
perr_exit("fork");
}
// 關(guān)閉監(jiān)聽socket
Close(listenfd);
return 0;
}
/* client.c */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include "wrap.h"
#define MAXLINE 80
#define SERV_PORT 6666
int main(int argc, char *argv[]) {
// 服務(wù)器地址結(jié)構(gòu)
struct sockaddr_in servaddr;
// 數(shù)據(jù)緩沖區(qū)
char buf[MAXLINE];
// socket和讀字節(jié)數(shù)
int sockfd, n;
// 創(chuàng)建socket
sockfd = Socket(AF_INET, SOCK_STREAM, 0);
// 初始化服務(wù)器地址結(jié)構(gòu)
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
servaddr.sin_port = htons(SERV_PORT);
// 連接服務(wù)器
Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
// 循環(huán)發(fā)送接收數(shù)據(jù)
while (fgets(buf, MAXLINE, stdin) != NULL) {
Copy code
// 發(fā)送數(shù)據(jù)到服務(wù)器
Write(sockfd, buf, strlen(buf));
// 從服務(wù)器讀取數(shù)據(jù)
n = Read(sockfd, buf, MAXLINE);
// 判斷服務(wù)器是否關(guān)閉
if (n == 0) {
printf("the other side has been closed.\n");
break;
} else
// 輸出服務(wù)器返回數(shù)據(jù)
Write(STDOUT_FILENO, buf, n);
}
// 關(guān)閉連接
Close(sockfd);
return 0;
}
多線程服務(wù)器
一個進(jìn)程內(nèi)創(chuàng)建線程處理每個連接請求。
?優(yōu)點(diǎn):高效利用多核CPU,創(chuàng)建和銷毀線程開銷較小。
?缺點(diǎn):需要調(diào)整進(jìn)程的文件描述符限制,需要進(jìn)行線程同步,線程退出時需要進(jìn)行資源清理。
?相關(guān)API函數(shù):pthread_create、pthread_detach、pthread_join、pthread_mutex_init、pthread_mutex_lock、pthread_mutex_unlock、socket、bind、listen、accept、read、write、close
要點(diǎn):文章來源:http://www.zghlxwxcb.cn/news/detail-828451.html
- 調(diào)整進(jìn)程文件描述符限制
- 共享數(shù)據(jù)同步
- 線程退出處理,防止資源泄漏
- 過多線程會降低性能
?例程分享:
/*
服務(wù)器端創(chuàng)建監(jiān)聽套接字,綁定地址并監(jiān)聽。
主循環(huán)調(diào)用accept接收客戶端連接,為每個客戶端創(chuàng)建線程do_work處理請求。
do_work通過read接收客戶端數(shù)據(jù),轉(zhuǎn)換為大寫后返回。
客戶端創(chuàng)建連接后,循環(huán)發(fā)送數(shù)據(jù)和讀取服務(wù)器返回數(shù)據(jù)。
服務(wù)器使用線程處理每個連接請求,可以處理大量連接。
通過傳遞參數(shù),線程可以獲取客戶端信息。
設(shè)置線程分離態(tài),自動回收資源,實(shí)現(xiàn)高效的多線程服務(wù)器。
*/
/* server.c */
#include <stdio.h> // 標(biāo)準(zhǔn)輸入輸出頭文件
#include <string.h> // 字符串處理頭文件
#include <netinet/in.h> // socket編程頭文件
#include <arpa/inet.h> // inet地址轉(zhuǎn)換頭文件
#include <pthread.h> // 線程編程頭文件
#include "wrap.h" // 封裝的socket函數(shù)頭文件
#define MAXLINE 80 //最大讀取字符數(shù)
#define SERV_PORT 6666 //服務(wù)器端口
// 用于傳遞給線程的客戶信息
struct s_info {
struct sockaddr_in cliaddr; // 客戶socket地址
int connfd; // 客戶端連接套接字
};
// 線程處理函數(shù)
void *do_work(void *arg) {
int n,i;
struct s_info *ts = (struct s_info*)arg; // 獲取傳遞的參數(shù)
char buf[MAXLINE]; // 數(shù)據(jù)緩沖區(qū)
char str[INET_ADDRSTRLEN]; // 存儲socket地址的字符串
// 設(shè)置線程為分離態(tài),線程結(jié)束時自動釋放資源
pthread_detach(pthread_self());
while (1) {
// 獲取客戶端發(fā)送的數(shù)據(jù)
n = Read(ts->connfd, buf, MAXLINE);
if (n == 0) { // 對端關(guān)閉連接
printf("the other side has been closed.\n");
break;
}
// 打印客戶端信息
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &(*ts).cliaddr.sin_addr, str, sizeof(str)),
ntohs((*ts).cliaddr.sin_port));
// 將數(shù)據(jù)轉(zhuǎn)換為大寫
for (i = 0; i < n; i++)
buf[i] = toupper(buf[i]);
// 發(fā)送轉(zhuǎn)換后的數(shù)據(jù)
Write(ts->connfd, buf, n);
}
// 關(guān)閉客戶端連接
Close(ts->connfd);
}
int main(void) {
struct sockaddr_in servaddr, cliaddr; // 本地和客戶端的socket地址
socklen_t cliaddr_len; // 客戶端socket地址長度
int listenfd, connfd; // 監(jiān)聽和連接套接字
int i = 0;
pthread_t tid; // 線程id
struct s_info ts[256]; // 存儲所有客戶端信息的數(shù)組
// 創(chuàng)建監(jiān)聽套接字
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
// 初始化本地socket地址結(jié)構(gòu)
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
// 綁定監(jiān)聽套接字到本地地址
Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
// 設(shè)置監(jiān)聽隊列長度
Listen(listenfd, 20);
printf("Accepting connections ...\n");
// 循環(huán)接收客戶端連接
while (1) {
// 接收一個客戶端連接
cliaddr_len = sizeof(cliaddr);
connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
// 保存客戶信息
ts[i].cliaddr = cliaddr;
ts[i].connfd = connfd;
// 為客戶端創(chuàng)建線程處理請求
pthread_create(&tid, NULL, do_work, (void*)&ts[i]);
i++;
}
return 0;
}
/* client.c */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include "wrap.h"
#define MAXLINE 80
#define SERV_PORT 6666
int main(int argc, char *argv[])
struct sockaddr_in servaddr; // 服務(wù)器端地址結(jié)構(gòu)
char buf[MAXLINE]; // 數(shù)據(jù)緩沖區(qū)
int sockfd, n; // 套接字和讀返回值
// 創(chuàng)建流式套接字
sockfd = Socket(AF_INET, SOCK_STREAM, 0);
// 初始化服務(wù)器地址結(jié)構(gòu)
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
servaddr.sin_port = htons(SERV_PORT);
// 連接服務(wù)器
Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
// 循環(huán)發(fā)送數(shù)據(jù)和讀取服務(wù)器返回數(shù)據(jù)
while (fgets(buf, MAXLINE, stdin) != NULL) {
// 向服務(wù)器發(fā)送數(shù)據(jù)
Write(sockfd, buf, strlen(buf));
// 從服務(wù)器讀取數(shù)據(jù)
n = Read(sockfd, buf, MAXLINE);
// 判斷服務(wù)器是否關(guān)閉
if (n == 0)
printf("the other side has been closed.\n");
else
// 輸出服務(wù)器返回數(shù)據(jù)
Write(STDOUT_FILENO, buf, n);
}
// 關(guān)閉socket連接
Close(sockfd);
return 0;
}
I/O多路復(fù)用服務(wù)器
select/poll/epoll使單線程可以同時處理多個連接請求。
select
優(yōu)點(diǎn): 可移植,使用簡單。
缺點(diǎn): 連接數(shù)受限,監(jiān)聽效率低。
要點(diǎn):
- select監(jiān)聽讀寫事件
- 每次循環(huán)重置監(jiān)聽描述符
- 根據(jù)返回就緒數(shù)遍歷處理事件
- 根據(jù)描述符狀態(tài)處理連接關(guān)閉等
例程分享:
/*
服務(wù)器端初始化socket地址,創(chuàng)建監(jiān)聽套接字。
使用select()監(jiān)聽套接字可讀事件。
調(diào)用accept()接收客戶端連接請求。
添加新的連接到select監(jiān)聽的文件描述符集。
循環(huán)掃描就緒文件描述符,調(diào)用read()接收客戶端數(shù)據(jù)。
對數(shù)據(jù)進(jìn)行處理后,調(diào)用write()返回給客戶端。
客戶端創(chuàng)建連接后,循環(huán)read()和write()實(shí)現(xiàn)雙向通信。
服務(wù)器使用select()處理多個連接,但依然是同步阻塞模型
*/
/* server.c */
/* server.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "wrap.h" // 這是一個自定義的頭文件,封裝了socket函數(shù)
#define MAXLINE 80 // 緩沖區(qū)的最大長度
#define SERV_PORT 6666 // 服務(wù)器端口號
int main(int argc, char *argv[])
{
int i, maxi, maxfd, listenfd, connfd, sockfd;
int nready, client[FD_SETSIZE]; // FD_SETSIZE通常為1024
ssize_t n;
fd_set rset, allset; // 用于select()的文件描述符集
char buf[MAXLINE]; // 數(shù)據(jù)緩沖區(qū)
char str[INET_ADDRSTRLEN]; // 地址字符串緩沖區(qū)
socklen_t cliaddr_len;
struct sockaddr_in cliaddr, servaddr; // 客戶端和服務(wù)器地址結(jié)構(gòu)
listenfd = Socket(AF_INET, SOCK_STREAM, 0); // 創(chuàng)建一個socket
bzero(&servaddr, sizeof(servaddr)); // 清零服務(wù)器地址結(jié)構(gòu)
servaddr.sin_family = AF_INET; // 設(shè)置地址族為IPv4
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 監(jiān)聽任何接口
servaddr.sin_port = htons(SERV_PORT); // 設(shè)置端口號
Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); // 將socket綁定到地址
Listen(listenfd, 20); // 監(jiān)聽連接,隊列長度為20
maxfd = listenfd; // 初始化maxfd
maxi = -1; // 初始化maxi
for (i = 0; i < FD_SETSIZE; i++)
client[i] = -1; // 初始化client[]
FD_ZERO(&allset); // 清空allset
FD_SET(listenfd, &allset); // 將listenfd添加到allset
for ( ; ; ) { // 主服務(wù)器循環(huán)
rset = allset; // 每次循環(huán)都重置rset
nready = select(maxfd+1, &rset, NULL, NULL, NULL); // 調(diào)用select()
if (nready < 0)
perr_exit("select error"); // 如果select()返回錯誤,退出
if (FD_ISSET(listenfd, &rset)) { // 如果有新的客戶端正在連接
cliaddr_len = sizeof(cliaddr);
connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len); // 接受新的連接
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port)); // 打印客戶端的地址和端口
for (i = 0; i < FD_SETSIZE; i++) {
if (client[i] < 0) {
client[i] = connfd; // 將接受的文件描述符保存在client[]中
break;
}
}
if (i == FD_SETSIZE) {
fputs("too many clients\n", stderr); // 如果連接的客戶端過多,打印錯誤并退出
exit(1);
}
FD_SET(connfd, &allset); // 將新的文件描述符添加到allset
if (connfd > maxfd)
maxfd = connfd; // 如果需要,更新maxfd
if (i > maxi)
maxi = i; // 如果需要,更新maxi
if (--nready == 0)
continue; // 如果沒有更多的就緒文件描述符,繼續(xù)下一次循環(huán)
}
for (i = 0; i <= maxi; i++) { // 檢查哪些客戶端有數(shù)據(jù)準(zhǔn)備好讀取
if ( (sockfd = client[i]) < 0)
continue;
if (FD_ISSET(sockfd, &rset)) {
if ( (n = Read(sockfd, buf, MAXLINE)) == 0) {
Close(sockfd); // 如果客戶端已經(jīng)關(guān)閉了連接,也關(guān)閉服務(wù)器端
FD_CLR(sockfd, &allset); // 從allset中移除文件描述符
client[i] = -1; // 從client[]中移除文件描述符
} else {
int j;
for (j = 0; j < n; j++)
buf[j] = toupper(buf[j]); // 將數(shù)據(jù)轉(zhuǎn)換為大寫
Write(sockfd, buf, n); // 將數(shù)據(jù)寫回客戶端
}
if (--nready == 0)
break; // 如果沒有更多的就緒文件描述符,跳出循環(huán)
}
}
}
close(listenfd); // 關(guān)閉監(jiān)聽的socket
return 0;
}
/* client.c */
/* client.c */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include "wrap.h" // 這是一個自定義的頭文件,封裝了socket函數(shù)
#define MAXLINE 80 // 緩沖區(qū)的最大長度
#define SERV_PORT 6666 // 服務(wù)器端口號
int main(int argc, char *argv[])
{
struct sockaddr_in servaddr; // 服務(wù)器地址結(jié)構(gòu)
char buf[MAXLINE]; // 數(shù)據(jù)緩沖區(qū)
int sockfd, n; // Socket文件描述符和讀取的字節(jié)數(shù)
sockfd = Socket(AF_INET, SOCK_STREAM, 0); // 創(chuàng)建一個socket
bzero(&servaddr, sizeof(servaddr)); // 清零服務(wù)器地址結(jié)構(gòu)
servaddr.sin_family = AF_INET; // 設(shè)置地址族為IPv4
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); // 設(shè)置服務(wù)器的IP地址
servaddr.sin_port = htons(SERV_PORT); // 設(shè)置端口號
Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); // 連接到服務(wù)器
while (fgets(buf, MAXLINE, stdin) != NULL) { // 主客戶端循環(huán)
Write(sockfd, buf, strlen(buf)); // 將輸入寫入服務(wù)器
n = Read(sockfd, buf, MAXLINE); // 讀取服務(wù)器的響應(yīng)
if (n == 0)
printf("the other side has been closed.\n"); // 如果服務(wù)器已經(jīng)關(guān)閉了連接,打印一條消息
else
Write(STDOUT_FILENO, buf, n); // 將服務(wù)器的響應(yīng)寫入stdout
}
Close(sockfd); // 關(guān)閉socket
return 0;
}
poll
優(yōu)點(diǎn): 沒有連接數(shù)限制。
缺點(diǎn): 依然輪詢模型,效率低。
要點(diǎn):
- pollfd結(jié)構(gòu)體監(jiān)聽事件
- 每次循環(huán)遍歷pollfd處理就緒事件
- 根據(jù)返回事件和錯誤處理連接狀態(tài)
例程分享:
/*
服務(wù)器端:
創(chuàng)建監(jiān)聽socket,綁定地址并監(jiān)聽
使用pollfd數(shù)組保存所有連接的文件描述符
調(diào)用poll監(jiān)聽socket上的事件,主要是POLLRDNORM讀事件
當(dāng)監(jiān)聽socket有事件時,表示有新連接,調(diào)用accept獲取新連接
將新連接的文件描述符添加到pollfd數(shù)組中,繼續(xù)監(jiān)聽讀事件
當(dāng)連接socket有讀事件時,調(diào)用read讀取客戶端數(shù)據(jù)
對數(shù)據(jù)進(jìn)行轉(zhuǎn)換處理,調(diào)用write將數(shù)據(jù)返回給客戶端
根據(jù)返回事件和錯誤情況判斷連接是否正常
客戶端:
創(chuàng)建socket,連接服務(wù)器地址
循環(huán)調(diào)用read讀取用戶輸入
調(diào)用write將用戶輸入發(fā)送給服務(wù)器
調(diào)用read讀取服務(wù)器返回數(shù)據(jù)
將服務(wù)器數(shù)據(jù)輸出到標(biāo)準(zhǔn)輸出
關(guān)閉連接
*/
/* server.c */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <poll.h>
#include <errno.h>
#include "wrap.h" // 這是一個自定義的頭文件,封裝了socket函數(shù)
#define MAXLINE 80 // 緩沖區(qū)的最大長度
#define SERV_PORT 6666 // 服務(wù)器端口號
#define OPEN_MAX 1024 // 最大的打開文件描述符數(shù)量
int main(int argc, char *argv[])
{
int i, j, maxi, listenfd, connfd, sockfd;
int nready;
ssize_t n;
char buf[MAXLINE], str[INET_ADDRSTRLEN];
socklen_t clilen;
struct pollfd client[OPEN_MAX]; // pollfd結(jié)構(gòu)體數(shù)組,用于存儲多個文件描述符
struct sockaddr_in cliaddr, servaddr; // 客戶端和服務(wù)器地址結(jié)構(gòu)
listenfd = Socket(AF_INET, SOCK_STREAM, 0); // 創(chuàng)建一個socket
bzero(&servaddr, sizeof(servaddr)); // 清零服務(wù)器地址結(jié)構(gòu)
servaddr.sin_family = AF_INET; // 設(shè)置地址族為IPv4
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 監(jiān)聽任何接口
servaddr.sin_port = htons(SERV_PORT); // 設(shè)置端口號
Bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); // 將socket綁定到地址
Listen(listenfd, 20); // 監(jiān)聽連接,隊列長度為20
client[0].fd = listenfd;
client[0].events = POLLRDNORM; // listenfd監(jiān)聽普通讀事件
for (i = 1; i < OPEN_MAX; i++)
client[i].fd = -1; // 用-1初始化client[]里剩下元素
maxi = 0; // client[]數(shù)組有效元素中最大元素下標(biāo)
for ( ; ; ) { // 主服務(wù)器循環(huán)
nready = poll(client, maxi+1, -1); // 調(diào)用poll()函數(shù),阻塞等待文件描述符就緒
if (client[0].revents & POLLRDNORM) { // 有客戶端鏈接請求
clilen = sizeof(cliaddr);
connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &clilen); // 接受新的連接
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port)); // 打印客戶端的地址和端口
for (i = 1; i < OPEN_MAX; i++) {
if (client[i].fd < 0) {
client[i].fd = connfd; // 找到client[]中空閑的位置,存放accept返回的connfd
break;
}
}
if (i == OPEN_MAX)
perr_exit("too many clients"); // 如果連接的客戶端過多,打印錯誤并退出
client[i].events = POLLRDNORM; // 設(shè)置剛剛返回的connfd,監(jiān)控讀事件
if (i > maxi)
maxi = i; // 更新client[]中最大元素下標(biāo)
if (--nready <= 0)
continue; // 沒有更多就緒事件時,繼續(xù)回到poll阻塞
}
for (i = 1; i <= maxi; i++) { // 檢測client[]
if ((sockfd = client[i].fd) < 0)
continue;
if (client[i].revents & (POLLRDNORM | POLLERR)) { // 如果有數(shù)據(jù)可讀或者有錯誤發(fā)生
if ((n = Read(sockfd, buf, MAXLINE)) < 0) {
if (errno == ECONNRESET) { // 當(dāng)收到 RST標(biāo)志時
/* connection reset by client */
printf("client[%d] aborted connection\n", i);
Close(sockfd); // 關(guān)閉socket
client[i].fd = -1; // 從client[]中移除文件描述符
} else {
perr_exit("read error"); // 如果讀取錯誤,退出
}
} else if (n == 0) {
/* connection closed by client */
printf("client[%d] closed connection\n", i);
Close(sockfd); // 關(guān)閉socket
client[i].fd = -1; // 從client[]中移除文件描述符
} else {
for (j = 0; j < n; j++)
buf[j] = toupper(buf[j]); // 將數(shù)據(jù)轉(zhuǎn)換為大寫
Writen(sockfd, buf, n); // 將數(shù)據(jù)寫回客戶端
}
if (--nready <= 0)
break; // 如果沒有更多的就緒文件描述符,跳出循環(huán)
}
}
}
return 0;
}
/* client.c */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netinet/in.h>
#include "wrap.h" // 這是一個自定義的頭文件,封裝了socket函數(shù)
#define MAXLINE 80 // 緩沖區(qū)的最大長度
#define SERV_PORT 6666 // 服務(wù)器端口號
int main(int argc, char *argv[])
{
struct sockaddr_in servaddr; // 服務(wù)器地址結(jié)構(gòu)
char buf[MAXLINE]; // 數(shù)據(jù)緩沖區(qū)
int sockfd, n; // Socket文件描述符和讀取的字節(jié)數(shù)
sockfd = Socket(AF_INET, SOCK_STREAM, 0); // 創(chuàng)建一個socket
bzero(&servaddr, sizeof(servaddr)); // 清零服務(wù)器地址結(jié)構(gòu)
servaddr.sin_family = AF_INET; // 設(shè)置地址族為IPv4
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr); // 設(shè)置服務(wù)器的IP地址
servaddr.sin_port = htons(SERV_PORT); // 設(shè)置端口號
Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr)); // 連接到服務(wù)器
while (fgets(buf, MAXLINE, stdin) != NULL) { // 主客戶端循環(huán)
Write(sockfd, buf, strlen(buf)); // 將輸入寫入服務(wù)器
n = Read(sockfd, buf, MAXLINE); // 讀取服務(wù)器的響應(yīng)
if (n == 0)
printf("the other side has been closed.\n"); // 如果服務(wù)器已經(jīng)關(guān)閉了連接,打印一條消息
else
Write(STDOUT_FILENO, buf, n); // 將服務(wù)器的響應(yīng)寫入stdout
}
Close(sockfd); // 關(guān)閉socket
return 0;
}
epoll
優(yōu)點(diǎn):提高程序在大量并發(fā)連接中的系統(tǒng)CPU利用率,能夠高效處理大量并發(fā)請求。
缺點(diǎn):需要調(diào)整進(jìn)程的文件描述符限制,需要進(jìn)行連接管理。
要點(diǎn):
- epoll_create創(chuàng)建句柄
- epoll_ctl注冊和控制事件
- epoll_wait等待就緒事件
- 根據(jù)就緒事件處理請求
例程分享:
/*
服務(wù)器端:
服務(wù)器端的代碼主要完成以下任務(wù):
創(chuàng)建一個TCP socket并綁定到指定的IP地址和端口。
使用epoll創(chuàng)建一個事件監(jiān)聽列表,并將監(jiān)聽socket添加到這個列表中。
進(jìn)入一個無限循環(huán),使用epoll_wait()函數(shù)等待事件的發(fā)生。
當(dāng)新的客戶端連接時,接受連接并將新的socket添加到epoll的監(jiān)聽列表中。
當(dāng)已連接的客戶端發(fā)送數(shù)據(jù)時,讀取數(shù)據(jù),將數(shù)據(jù)轉(zhuǎn)換為大寫,然后將數(shù)據(jù)回寫到客戶端。
當(dāng)客戶端關(guān)閉連接時,從epoll的監(jiān)聽列表中移除這個socket,并關(guān)閉這個socket。
客戶端端:
客戶端的代碼主要完成以下任務(wù):
創(chuàng)建一個TCP socket并連接到服務(wù)器。
進(jìn)入一個無限循環(huán),從stdin讀取輸入,將輸入寫入服務(wù)器,然后讀取服務(wù)器的響應(yīng)并將響應(yīng)寫入stdout。
當(dāng)服務(wù)器關(guān)閉連接時,打印一條消息并退出循環(huán)。
*/
/* server.c */
// ...省略部分代碼...
// 創(chuàng)建一個TCP socket
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
// 清零服務(wù)器地址結(jié)構(gòu)
bzero(&servaddr, sizeof(servaddr));
// 設(shè)置地址族為IPv4
servaddr.sin_family = AF_INET;
// 監(jiān)聽任何接口
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
// 設(shè)置端口號
servaddr.sin_port = htons(SERV_PORT);
// 將socket綁定到地址
Bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr));
// 監(jiān)聽連接,隊列長度為20
Listen(listenfd, 20);
// 創(chuàng)建epoll實(shí)例
efd = epoll_create(OPEN_MAX);
if (efd == -1)
perr_exit("epoll_create");
// 設(shè)置監(jiān)聽事件為EPOLLIN(可讀事件),并將listenfd添加到epoll的監(jiān)聽列表中
tep.events = EPOLLIN; tep.data.fd = listenfd;
res = epoll_ctl(efd, EPOLL_CTL_ADD, listenfd, &tep);
if (res == -1)
perr_exit("epoll_ctl");
// 主服務(wù)器循環(huán)
while (1) {
// 調(diào)用epoll_wait()函數(shù),阻塞等待文件描述符就緒
nready = epoll_wait(efd, ep, OPEN_MAX, -1);
if (nready == -1)
perr_exit("epoll_wait");
// 遍歷就緒的文件描述符
for (i = 0; i < nready; i++) {
// 如果不是可讀事件,跳過
if (!(ep[i].events & EPOLLIN))
continue;
// 如果是新的客戶端連接
if (ep[i].data.fd == listenfd) {
// 接受新的連接
clilen = sizeof(cliaddr);
connfd = Accept(listenfd, (struct sockaddr *)&cliaddr, &clilen);
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port));
// 將新的socket添加到epoll的監(jiān)聽列表中
tep.events = EPOLLIN;
tep.data.fd = connfd;
res = epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &tep);
if (res == -1)
perr_exit("epoll_ctl");
} else {
// 如果是已連接的客戶端發(fā)送的數(shù)據(jù)
sockfd = ep[i].data.fd;
n = Read(sockfd, buf, MAXLINE);
if (n == 0) {
// 如果客戶端關(guān)閉了連接,從epoll的監(jiān)聽列表中移除這個socket,并關(guān)閉這個socket
res = epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL);
if (res == -1)
perr_exit("epoll_ctl");
Close(sockfd);
printf("client[%d] closed connection\n", j);
} else {
// 如果接收到數(shù)據(jù),將數(shù)據(jù)轉(zhuǎn)換為大寫,然后將數(shù)據(jù)回寫到客戶端
for (j = 0; j < n; j++)
buf[j] = toupper(buf[j]);
Writen(sockfd, buf, n);
}
}
}
}
// 關(guān)閉監(jiān)聽socket和epoll實(shí)例
close(listenfd);
close(efd);
return 0;
/* client.c */
// ...省略部分代碼...
// 創(chuàng)建一個TCP socket
sockfd = Socket(AF_INET, SOCK_STREAM, 0);
// 清零服務(wù)器地址結(jié)構(gòu)
bzero(&servaddr, sizeof(servaddr));
// 設(shè)置地址族為IPv4
servaddr.sin_family = AF_INET;
// 設(shè)置服務(wù)器的IP地址
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
// 設(shè)置端口號
servaddr.sin_port = htons(SERV_PORT);
// 連接到服務(wù)器
Connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
// 主客戶端循環(huán)
while (fgets(buf, MAXLINE, stdin) != NULL) {
// 將輸入寫入服務(wù)器
Write(sockfd, buf, strlen(buf));
// 讀取服務(wù)器的響應(yīng)
n = Read(sockfd, buf, MAXLINE);
if (n == 0)
// 如果服務(wù)器已經(jīng)關(guān)閉了連接,打印一條消息
printf("the other side has been closed.\n");
else
// 將服務(wù)器的響應(yīng)寫入stdout
Write(STDOUT_FILENO, buf, n);
}
// 關(guān)閉socket
Close(sockfd);
return 0;
總結(jié)建議
????????epoll服務(wù)器根據(jù)不同的需求和場景,我們可以選擇不同的高并發(fā)服務(wù)器設(shè)計方案。多進(jìn)程服務(wù)器、多線程服務(wù)器、I/O多路復(fù)用服務(wù)器和epoll服務(wù)器都有各自的優(yōu)缺點(diǎn)和適用場景。通過分享的例程和相關(guān)API函數(shù)的介紹,讀者可以更好地理解和選擇適合自己需求的設(shè)計方案,從而高效處理大量并發(fā)請求,滿足互聯(lián)網(wǎng)快速發(fā)展的需求。文章來源地址http://www.zghlxwxcb.cn/news/detail-828451.html
到了這里,關(guān)于Linux網(wǎng)絡(luò)編程之TCP/IP實(shí)現(xiàn)高并發(fā)網(wǎng)絡(luò)服務(wù)器設(shè)計指南的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!