網絡編程-----服務器(多路復用IO 和 TCP并發模型)

一、單循環服務器模型

1. 核心特征
while(1){newfd = accept();recv();close(newfd);}
2. 典型應用場景
  • HTTP短連接服務(早期Apache)
  • CGI快速處理
  • 簡單測試服務器
3. 綜合代碼
#include <stdio.h>
#include <sys/types.h>	       /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include <strings.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>int main(int argc, const char *argv[])
{if (argc != 3){printf("Usage: %s <port> <ip>\n",argv[0]);return -1;}//1.socket 創建通信一端 int fd = socket(AF_INET,SOCK_STREAM,0);if (fd < 0){perror("socket fail\n");return -1;}struct sockaddr_in seraddr;bzero(&seraddr,sizeof(seraddr));seraddr.sin_family = AF_INET;seraddr.sin_port = htons(atoi(argv[1]));seraddr.sin_addr.s_addr = inet_addr(argv[2]);printf("fd = %d\n",fd);//2.bind -- 綁定服務器端的地址信息 if (bind(fd,(const struct sockaddr*)&seraddr,sizeof(seraddr)) < 0){perror("connect fail");return -1;}printf("connect success!\n");//3.listen -- 設置監聽 if (listen(fd,5) < 0){perror("listen fail");return -1;}while (1){//4.acceptint connfd = accept(fd,NULL,NULL);if (connfd < 0){perror("accept fail");return -1;}printf("----client --- connectted\n");char buf[1024];char sbuf[1024];while (1){recv(connfd,buf,sizeof(buf),0);printf("c: %s\n",buf);if (strncmp(buf,"quit",4) == 0){close(connfd);break;}sprintf(sbuf,"server + %s\n",buf);send(connfd,sbuf,strlen(sbuf)+1,0);}}close(fd);return 0;
}
4. 優缺點分析
優點缺點
實現簡單無法處理并發請求
無資源競爭問題長連接會阻塞后續請求
適合低負載場景吞吐量低(QPS < 100)

二、多進程并發模型

1. 核心實現
while(1) {int newfd = accept(listen_fd, ...);pid_t pid = fork();if (pid == 0) {  // 子進程close(listen_fd);handle_connection(newfd);close(newfd);exit(0);} else if (pid > 0) {  // 父進程close(newfd);waitpid(-1, NULL, WNOHANG);  // 非阻塞回收}
}
2. 進程管理優化
// 使用信號處理避免僵尸進程
signal(SIGCHLD, SIG_IGN);  // 忽略子進程結束信號// 或使用waitpid循環
while (waitpid(-1, NULL, WNOHANG) > 0);
3. 典型應用
  • 傳統Apache的prefork模式
  • FTP服務器
  • 數據庫連接池
4. 資源消耗對比
資源類型進程創建開銷示例系統調用
內存需要復制整個PCBfork()
CPU上下文切換成本高schedule()
文件描述符需要顯式關閉繼承的fdclose()
5. 優缺點分析
優點缺點
可以完成多個進程的實時交互回收資源不方便
信息的完整性可以保證。每次fork 占用系統資源多
適合低負載場景可能出現僵尸進程

6. 綜合代碼

#include <stdio.h>
#include <sys/types.h>	       /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include <strings.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>void handler(int signo)
{wait(NULL);
}int init_server(const char *ip,unsigned short port)
{//1.socket 創建通信一端 int fd = socket(AF_INET,SOCK_STREAM,0);if (fd < 0){perror("socket fail\n");return -1;}struct sockaddr_in seraddr;bzero(&seraddr,sizeof(seraddr));seraddr.sin_family = AF_INET;seraddr.sin_port = htons(port);seraddr.sin_addr.s_addr = inet_addr(ip);//2.bind -- 綁定服務器端的地址信息 if (bind(fd,(const struct sockaddr*)&seraddr,sizeof(seraddr)) < 0){perror("connect fail");return -1;}//3.listen -- 設置監聽 if (listen(fd,5) < 0){perror("listen fail");return -1;}return fd;
}int client_handler(int connfd)
{char buf[1024];char sbuf[1024];int ret = 0;while (1){ret = recv(connfd,buf,sizeof(buf),0);if (ret < 0){perror("client_handler recv fail");ret = -1;}printf("c: %s\n",buf);if (strncmp(buf,"quit",4) == 0){close(connfd);ret = 1;break;}sprintf(sbuf,"server + %s\n",buf);ret = send(connfd,sbuf,strlen(sbuf)+1,0);if (ret < 0){perror("client_handler send fail");ret = -1;}}return ret;}int main(int argc, const char *argv[])
{if (argc != 3){printf("Usage: %s <ip> <port>\n",argv[0]);return -1;}signal(SIGCHLD,handler);int fd = init_server(argv[1],atoi(argv[2]));if (fd < 0){printf("init_server fail\n");return -1;}while (1){//4.acceptint connfd = accept(fd,NULL,NULL);if (connfd < 0){perror("accept fail");return -1;}pid_t pid = fork();if (pid < 0){perror("fork fail");return -1;}if (pid == 0){int ret = 0;if ((ret = client_handler(connfd)) < 0){printf("client_handler fail");return -1;}if (ret == 1){printf("child exit...\n");exit(EXIT_SUCCESS);}}}close(fd);return 0;
}

三、多線程并發模型

1. 核心實現(POSIX線程)
while(1) {int newfd = accept(listen_fd, ...);pthread_t tid;pthread_create(&tid, NULL, thread_handler, (void*)newfd);pthread_detach(tid);  // 分離線程自動回收
}void* thread_handler(void* arg) {int fd = (int)arg;// 處理請求close(fd);return NULL;
}
2. 線程安全控制
// 使用互斥鎖保護共享資源
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;void safe_write(int fd, const char* data) {pthread_mutex_lock(&lock);write(fd, data, strlen(data));pthread_mutex_unlock(&lock);
}
3. 典型應用
  • Java Tomcat
  • IIS應用池
  • 實時通信服務器
4. 性能指標對比
指標進程模型線程模型
創建速度慢(10-100ms)快(0.1-1ms)
上下文切換成本高(切換頁表等)低(共享地址空間)
內存占用高(獨立資源)低(共享資源)
5. 優缺點分析
優點缺點
可以完成多個進程的實時交互線程共享進程資源
創建速度快,調度快穩定性 較差
適合低負載場景安全性 較差?

6. 綜合代碼

#include <stdio.h>
#include <pthread.h>
#include <sys/types.h>	       /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include <strings.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
#include <errno.h>int init_server(const char *ip,unsigned short port)
{//1.socket 創建通信一端 int fd = socket(AF_INET,SOCK_STREAM,0);if (fd < 0){perror("socket fail\n");return -1;}struct sockaddr_in seraddr;bzero(&seraddr,sizeof(seraddr));seraddr.sin_family = AF_INET;seraddr.sin_port = htons(port);seraddr.sin_addr.s_addr = inet_addr(ip);//2.bind -- 綁定服務器端的地址信息 if (bind(fd,(const struct sockaddr*)&seraddr,sizeof(seraddr)) < 0){perror("connect fail");return -1;}//3.listen -- 設置監聽 if (listen(fd,5) < 0){perror("listen fail");return -1;}return fd;
}void* client_handler(void *arg)
{int connfd = *(int *)arg;char buf[1024];char sbuf[1024];long int ret = 0;while (1){ret = recv(connfd,buf,sizeof(buf),0);if (ret < 0){perror("client_handler recv fail");ret = -1;}printf("c: %s\n",buf);if (strncmp(buf,"quit",4) == 0){close(connfd);ret = 1;break;}sprintf(sbuf,"server + %s\n",buf);ret = send(connfd,sbuf,strlen(sbuf)+1,0);if (ret < 0){perror("client_handler send fail");ret = -1;}}return (void*)ret;}int main(int argc, const char *argv[])
{if (argc != 3){printf("Usage: %s <ip> <port>\n",argv[0]);return -1;}int fd = init_server(argv[1],atoi(argv[2]));if (fd < 0){printf("init_server fail\n");return -1;}while (1){//4.acceptint connfd = accept(fd,NULL,NULL);if (connfd < 0){perror("accept fail");return -1;}pthread_t tid;int ret = pthread_create(&tid,NULL,client_handler,&connfd);if(ret != 0){errno = ret;perror("pthread_create fail");return -1;}pthread_detach(tid);//設置分離屬性,由系統回收資源}close(fd);return 0;
}

四、并發的服務器模型 ---更高程度上的并發?

(一)fcntl?函數與 I/O 模型詳解

1. 函數原型
#include <unistd.h>
#include <fcntl.h>int fcntl(int fd, int cmd, ... /* arg */ );
2. 主要操作類型
命令功能描述參數要求
F_DUPFD復制文件描述符指定最小可用fd值
F_GETFD/F_SETFD獲取/設置文件描述符標志標志值
F_GETFL/F_SETFL獲取/設置文件狀態標志新標志值
F_GETOWN/F_SETOWN獲取/設置異步I/O所有權進程ID或組ID

(二)非阻塞I/O設置示例

1. 設置流程
int flag = fcntl(connfd,F_GETFL,0);flag = flag | O_NONBLOCK;fcntl(connfd,F_SETFL,flag);
2. 行為變化對比
操作阻塞模式非阻塞模式
read()阻塞直到數據到達立即返回,無數據時返回EAGAIN
write()阻塞直到緩沖區空間可用立即返回,空間不足返回EAGAIN
accept()阻塞直到有新連接立即返回,無連接時返回EAGAIN

(三)I/O 模型對比

1. 阻塞I/O模型
2. 非阻塞I/O模型

#include <stdio.h>
#include <sys/types.h>	       /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include <strings.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>#include <fcntl.h>
int main(int argc, const char *argv[])
{if (argc != 3){printf("Usage: %s <port> <ip>\n",argv[0]);return -1;}//1.socket 創建通信一端 int fd = socket(AF_INET,SOCK_STREAM,0);if (fd < 0){perror("socket fail\n");return -1;}struct sockaddr_in seraddr;bzero(&seraddr,sizeof(seraddr));seraddr.sin_family = AF_INET;seraddr.sin_port = htons(atoi(argv[1]));seraddr.sin_addr.s_addr = inet_addr(argv[2]);printf("fd = %d\n",fd);//2.bind -- 綁定服務器端的地址信息 if (bind(fd,(const struct sockaddr*)&seraddr,sizeof(seraddr)) < 0){perror("connect fail");return -1;}printf("connect success!\n");//3.listen -- 設置監聽 if (listen(fd,5) < 0){perror("listen fail");return -1;}while (1){//4.acceptint connfd = accept(fd,NULL,NULL);if (connfd < 0){perror("accept fail");return -1;}printf("----client --- connectted\n");char buf[1024];char sbuf[1024];int flag = fcntl(connfd,F_GETFL,0);flag = flag | O_NONBLOCK;fcntl(connfd,F_SETFL,flag);while (1){recv(connfd,buf,sizeof(buf),0);printf("c: %s\n",buf);if (strncmp(buf,"quit",4) == 0){close(connfd);break;}sprintf(sbuf,"server + %s\n",buf);send(connfd,sbuf,strlen(sbuf)+1,0);}}close(fd);return 0;
}

(四)信號驅動 I/O 詳解

1. 設置異步標志
// 獲取當前文件狀態標志
int flags = fcntl(fd, F_GETFL);
if (flags == -1) {perror("fcntl F_GETFL");exit(EXIT_FAILURE);
}// 添加異步I/O標志
if (fcntl(fd, F_SETFL, flags | O_ASYNC) == -1) {perror("fcntl F_SETFL");exit(EXIT_FAILURE);
}
2. 指定信號接收者
// 設置當前進程為信號接收者
if (fcntl(fd, F_SETOWN, getpid()) == -1) {perror("fcntl F_SETOWN");exit(EXIT_FAILURE);
}
3. 注冊信號處理函數
// 更安全的sigaction替代signal
struct sigaction sa;
sa.sa_flags = SA_RESTART;
sa.sa_handler = sigio_handler;
sigemptyset(&sa.sa_mask);if (sigaction(SIGIO, &sa, NULL) == -1) {perror("sigaction");exit(EXIT_FAILURE);
}
4. 基本處理邏輯
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <signal.h>int g_fd;void handler(int signo)
{char buf[1024];read(g_fd,buf,sizeof(buf));if (strncmp(buf,"quit",4) == 0)return;printf("buf = %s\n",buf);}int main(int argc, const char *argv[])
{if (mkfifo(argv[1],0666) < 0 && errno != EEXIST){perror("mkfifo fail");return -1;}printf("mkfifo success\n");int fd = open(argv[1], O_RDONLY);if (fd < 0){perror("open fail");return -1;}g_fd = fd;int flag = fcntl(fd,F_GETFL,0);flag = flag | O_ASYNC;//設置為異步通信fcntl(fd,F_SETFL,flag);fcntl(fd,F_SETOWN,getpid());//所有者signal(SIGIO,handler);int i = 0;while (1){printf("i = %d\n",i);sleep(1);++i;}close(fd);return 0;
}

5.核心局限性分析
問題類型具體表現解決思路
信號合并快速連續信號可能被合并使用實時信號(SIGRTMIN+)
多fd區分困難無法直接判斷哪個fd觸發信號每個fd綁定不同信號(不現實)
異步安全限制信號處理函數中操作受限僅設置標志,主循環處理
性能瓶頸高頻率信號導致CPU占用高配合epoll使用

(五)select?函數詳解

一、函數原型與參數解析

#include <sys/select.h>int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
參數說明
參數類型說明
nfdsint監控的文件描述符最大值 +1(優化內核檢查范圍)
readfdsfd_set*監控可讀事件的描述符集合(可NULL)
writefdsfd_set*監控可寫事件的描述符集合(可NULL)
exceptfdsfd_set*監控異常事件的描述符集合(可NULL)
timeouttimeval*超時時間:<br>? NULL:阻塞等待<br>? 0:立即返回<br>? 正數:定時等待
返回值
  • 成功:返回就緒的文件描述符總數(可能為0)
  • 失敗:返回-1并設置errno
  • 超時:返回0

二、核心操作宏

功能示例
FD_ZERO清空描述符集合FD_ZERO(&read_fds);
FD_SET添加描述符到集合FD_SET(sockfd, &read_fds);
FD_CLR從集合中移除描述符FD_CLR(sockfd, &read_fds);
FD_ISSET檢測描述符是否在集合中if(FD_ISSET(sockfd, &read_fds))

三、典型使用流程

1. 初始化描述符集合
fd_set read_fds;
FD_ZERO(&read_fds);
FD_SET(listen_fd, &read_fds);
int max_fd = listen_fd;
2. 等待事件就緒
struct timeval tv = {5, 0}; // 5秒超時
fd_set tmp_fds = read_fds;int ready = select(max_fd + 1, &tmp_fds, NULL, NULL, &tv);
if (ready == -1) {if (errno == EINTR) continue; // 處理信號中斷perror("select error");break;
} else if (ready == 0) {printf("Timeout\n");continue;
}
3. 處理就緒事件
for (int fd = 0; fd <= max_fd; fd++) {if (FD_ISSET(fd, &tmp_fds)) {if (fd == listen_fd) {// 處理新連接int new_fd = accept(listen_fd, ...);FD_SET(new_fd, &read_fds);max_fd = (new_fd > max_fd) ? new_fd : max_fd;} else {// 處理客戶端數據ssize_t n = read(fd, ...);if (n <= 0) {close(fd);FD_CLR(fd, &read_fds);}}}
}

四、關鍵注意事項

  1. 集合重用問題
    select返回后,集合會被修改為就緒的fd集合,每次調用前必須重新初始化:

    fd_set tmp_fds = read_fds; // 使用臨時集合
    
  2. 超時時間重置
    timeout參數會被修改為剩余時間,循環調用時需要重新設置:

    struct timeval tv = {5, 0};
    while(1) {select(..., &tv);tv.tv_sec = 5; // 必須重置
    }
    
  3. 最大fd限制
    FD_SETSIZE限制(通常1024),超出會導致未定義行為

  4. 性能問題
    每次調用需要從用戶態復制整個fd_set到內核態,時間復雜度O(n)

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
int main(int argc, const char *argv[])
{if (mkfifo(argv[1],0666) < 0 && errno != EEXIST){perror("mkfifo fail");return -1;}printf("mkfifo success\n");int fd = open(argv[1], O_RDONLY);if (fd < 0){perror("open fail");return -1;}char buf[1024] = {0};//1.建表fd_set readfds;FD_ZERO(&readfds);//2.添加要關心的fdFD_SET(0,&readfds);FD_SET(fd,&readfds);//3.select函數監控fd_set backfds;struct timeval tv = {5,0};while(1){backfds = readfds;//每次循環回來拿到的都是最原始數據int nfds = fd + 1;//因為另一個是0,所以最大也就是fdint ret = select(nfds,&backfds,NULL,NULL,&tv);if(ret < 0){perror("select fail");return -1;}if(ret > 0){for(int i = 0;i < nfds;i++)//也可以是1024,但沒必要 {if(FD_ISSET(i,&backfds)){if(i == 0){fgets(buf,sizeof(buf),stdin);if (strncmp(buf,"quit",4) == 0)break;printf("buf = %s\n",buf);}else if(i == fd){read(fd,buf,sizeof(buf));if (strncmp(buf,"quit",4) == 0)break;printf("buf = %s\n",buf);}}}}}close(fd);return 0;
}

可以從客戶端讀取數據,也可以自身從鍵盤輸入

tcp多客戶端連接到服務器

#include <stdio.h>
#include <sys/types.h>	       /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include <strings.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>int main(int argc, const char *argv[])
{if (argc != 3){printf("Usage: %s <port> <ip>\n",argv[0]);return -1;}//1.socket 創建通信一端 int fd = socket(AF_INET,SOCK_STREAM,0);if (fd < 0){perror("socket fail\n");return -1;}struct sockaddr_in seraddr;bzero(&seraddr,sizeof(seraddr));seraddr.sin_family = AF_INET;seraddr.sin_port = htons(atoi(argv[1]));seraddr.sin_addr.s_addr = inet_addr(argv[2]);printf("fd = %d\n",fd);//2.bind -- 綁定服務器端的地址信息 if (bind(fd,(const struct sockaddr*)&seraddr,sizeof(seraddr)) < 0){perror("connect fail");return -1;}printf("connect success!\n");//3.listen -- 設置監聽 if (listen(fd,5) < 0){perror("listen fail");return -1;}//1.準備表 fd_set readfds;FD_ZERO(&readfds);//2.添加要監控的fdFD_SET(fd,&readfds);int connfd = 0;fd_set backfds;int i = 0;int nfds = fd + 1;while (1){backfds = readfds;int ret = select(nfds,&backfds,NULL,NULL,NULL);if (ret < 0){perror("select fail");return -1;}if (ret > 0){for (i = 0; i < nfds; ++i){if (FD_ISSET(i,&backfds)){if (i == fd){//4.acceptconnfd = accept(fd,NULL,NULL);if (connfd < 0){perror("accept fail");return -1;}FD_SET(connfd,&readfds);nfds = nfds > connfd + 1 ? nfds:connfd + 1;}else {char buf[1024];char sbuf[1024];recv(i,buf,sizeof(buf),0);printf("c: %s\n",buf);if (strncmp(buf,"quit",4) == 0){	close(i);FD_CLR(i,&readfds); }sprintf(sbuf,"server + %s\n",buf);send(i,sbuf,strlen(sbuf)+1,0);}}}}}close(fd);return 0;
}

并發模型對比

模型實現方式優點缺點
多進程fork()隔離性好資源消耗大
多線程pthread_create()資源共享高效同步復雜度高
I/O多路復用select/poll/epoll高并發低開銷編程復雜度較高
信號驅動SIGIO+fcntl實時性好信號處理復雜
異步I/Oaio_*系列函數真正的異步操作系統支持不統一

(六)epoll?


一、核心函數解析

1.?epoll_create:創建 epoll 實例
#include <sys/epoll.h>
int epoll_create(int size);
  • 參數
    • size:內核初始分配數據結構的建議值(Linux 2.6.8+ 后忽略,但需 > 0)
  • 返回值
    • 成功:epoll 文件描述符 (epfd)
    • 失敗:-1,設置?errno
  • 注意
    • 需手動調用?close(epfd)?釋放資源
    • 典型用法:epoll_create1(0)(更推薦,支持?EPOLL_CLOEXEC?標志)

2.?epoll_ctl:管理監控列表
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
  • 操作類型?(op):

    操作說明
    EPOLL_CTL_ADD添加 fd 到監控列表(重復添加報?EEXIST?錯誤)
    EPOLL_CTL_MOD修改已注冊 fd 的事件(未注冊的 fd 報?ENOENT?錯誤)
    EPOLL_CTL_DEL從監控列表刪除 fd(內核會忽略 event 參數)
  • 事件結構

    struct epoll_event {uint32_t     events;  // 監控的事件類型(位掩碼)epoll_data_t data;    // 用戶數據(可攜帶 fd、指針等)
    };typedef union epoll_data {void    *ptr;int      fd;uint32_t u32;uint64_t u64;
    } epoll_data_t;
    

3.?epoll_wait:等待事件就緒
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
  • 參數
    • events:輸出參數,存儲就緒事件數組
    • maxevents:最多返回的事件數量(需 ≤ 數組長度)
    • timeout:超時時間(ms),-1 表示阻塞,0 表示立即返回
  • 返回值
    • 成功:就緒事件數量
    • 超時:0
    • 錯誤:-1,設置?errno

二、事件類型與觸發模式

1.?基礎事件類型
事件類型說明
EPOLLIN數據可讀(包括對端關閉)
EPOLLOUT數據可寫(注意:可能觸發虛假就緒)
EPOLLRDHUP對端關閉連接或關閉寫方向(需內核 ≥ 2.6.17)
EPOLLPRI緊急數據可讀(如 TCP 帶外數據)
EPOLLERR錯誤條件(自動監控,無需手動設置)
EPOLLHUP掛起(如管道對端關閉,自動監控)
2.?高級控制標志
標志說明
EPOLLET邊沿觸發模式(默認水平觸發 LT)
EPOLLONESHOT單次觸發,事件處理后需用?EPOLL_CTL_MOD?重新激活

三、觸發模式對比

特性水平觸發 (LT)邊沿觸發 (ET)
觸發條件只要緩沖區有數據/空間就會觸發僅在緩沖區狀態變化時觸發一次
數據讀取可部分讀取,下次仍會觸發必須一次性讀取到?EAGAIN
性能適合低頻大塊數據適合高頻高并發場景
實現復雜度簡單需配合非阻塞 I/O 和循環讀寫
適用場景簡單交互、文件傳輸實時通信、高并發服務器

實例:

基于 epoll 的簡單 TCP 服務器,可以同時處理多個客戶端連接

#include <stdio.h>
#include <sys/types.h>	       /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include <strings.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/epoll.h>// 將文件描述符添加到 epoll 實例中
int add_fd(int epfd,int fd)
{struct epoll_event ev;ev.events = EPOLLIN;// 監聽可讀事件ev.data.fd = fd;// 設置文件描述符if(epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev) < 0){perror("epoll_ctl fail");return -1;}return 0;
}
// 從 epoll 實例中刪除文件描述符
int del_fd(int epfd,int fd)
{struct epoll_event ev;ev.events = EPOLLIN;ev.data.fd = fd;if(epoll_ctl(epfd,EPOLL_CTL_DEL,fd,&ev) < 0){perror("epoll_ctl fail");return -1;}return 0;
}int main(int argc, const char *argv[])
{if (argc != 3){printf("Usage: %s <port> <ip>\n",argv[0]);return -1;}//1.socket 創建通信一端 int fd = socket(AF_INET,SOCK_STREAM,0);if (fd < 0){perror("socket fail\n");return -1;}struct sockaddr_in seraddr;bzero(&seraddr,sizeof(seraddr));seraddr.sin_family = AF_INET;seraddr.sin_port = htons(atoi(argv[1]));seraddr.sin_addr.s_addr = inet_addr(argv[2]);//2.bind -- 綁定服務器端的地址信息 if (bind(fd,(const struct sockaddr*)&seraddr,sizeof(seraddr)) < 0){perror("connect fail");return -1;}printf("connect success!\n");//3.listen -- 設置監聽 if (listen(fd,5) < 0){perror("listen fail");return -1;}//1.準備表 int epfd = epoll_create(2);if(epfd < 0){perror("epoll_create fail");return -1;}//2.添加要監控的fdadd_fd(epfd,fd); // 添加監聽 socket 到 epollint connfd = 0;struct epoll_event result[1024];// 保存 epoll_wait 返回的事件int maxevents = 1024;//指定 epoll_wait 函數最多可以返回的事件數量。int ret = 0;int i = 0;int tm = 3000;//3swhile (1){// 等待 epoll 事件ret = epoll_wait(epfd,result,maxevents,tm);if (ret < 0){perror("epoll_wait fail");return -1;}else if (ret == 0) {printf("epoll_wait timeout\n");//處理超時}else if(ret > 0){for (i = 0; i < ret; ++i){// 如果是監聽 socket 有事件,說明有新連接if (result[i].data.fd == fd)//作用為監聽的fd{//4.acceptconnfd = accept(fd,NULL,NULL);if (connfd < 0){perror("accept fail");return -1;}// 將新連接的 socket 添加到 epolladd_fd(epfd,connfd);}else //通信的fd{// 處理客戶端數據connfd = result[i].data.fd;//取觸發事件的文件描述符char buf[1024];char sbuf[1024];recv(connfd,buf,sizeof(buf),0);printf("c: %s\n",buf);if (strncmp(buf,"quit",4) == 0){	del_fd(epfd,connfd);close(connfd);continue;}sprintf(sbuf,"server + %s\n",buf);send(connfd,sbuf,strlen(sbuf)+1,0);}}}}close(fd);return 0;
}

基于 epoll 的簡單 TCP 服務器,可以同時處理多個客戶端連接(邊沿觸發模式)

#include <stdio.h>
#include <sys/types.h>	       /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#include <arpa/inet.h>
#include <strings.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/epoll.h>
#include <errno.h>
#include <fcntl.h>// 將文件描述符添加到 epoll 實例中
int add_fd(int epfd,int fd)
{struct epoll_event ev;ev.events = EPOLLIN|EPOLLET;// 監聽可讀事件,并使用邊緣觸發模式ev.data.fd = fd;// 設置文件描述符if(epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev) < 0){perror("epoll_ctl fail");return -1;}return 0;
}
// 從 epoll 實例中刪除文件描述符
int del_fd(int epfd,int fd)
{struct epoll_event ev;ev.events = EPOLLIN;ev.data.fd = fd;if(epoll_ctl(epfd,EPOLL_CTL_DEL,fd,&ev) < 0){perror("epoll_ctl fail");return -1;}return 0;
}
// 設置文件描述符為非阻塞模式
void set_nonblock(int fd)
{int flag = fcntl(fd,F_GETFL,0);// 獲取當前文件狀態標志flag = flag|O_NONBLOCK;// 設置非阻塞標志fcntl(fd,F_SETFL,flag);// 更新文件狀態標志
}int main(int argc, const char *argv[])
{if (argc != 3){printf("Usage: %s <port> <ip>\n",argv[0]);return -1;}//1.socket 創建通信一端 int fd = socket(AF_INET,SOCK_STREAM,0);if (fd < 0){perror("socket fail\n");return -1;}struct sockaddr_in seraddr;bzero(&seraddr,sizeof(seraddr));seraddr.sin_family = AF_INET;seraddr.sin_port = htons(atoi(argv[1]));seraddr.sin_addr.s_addr = inet_addr(argv[2]);//2.bind -- 綁定服務器端的地址信息 if (bind(fd,(const struct sockaddr*)&seraddr,sizeof(seraddr)) < 0){perror("connect fail");return -1;}printf("connect success!\n");//3.listen -- 設置監聽 if (listen(fd,5) < 0){perror("listen fail");return -1;}//1.準備表 int epfd = epoll_create(2);if(epfd < 0){perror("epoll_create fail");return -1;}//2.添加要監控的fd
// 添加監聽 socket 到 epoll,并設置為非阻塞模式add_fd(epfd,fd); set_nonblock(fd);int connfd = 0;struct epoll_event result[1024];// 保存 epoll_wait 返回的事件int maxevents = 1024;//指定 epoll_wait 函數最多可以返回的事件數量。int ret = 0;int i = 0;int tm = 3000;//3swhile (1){// 等待 epoll 事件ret = epoll_wait(epfd,result,maxevents,tm);if (ret < 0){perror("epoll_wait fail");return -1;}else if (ret == 0) {printf("epoll_wait timeout\n");//處理超時}else if(ret > 0){for (i = 0; i < ret; ++i){// 如果是監聽 socket 有事件,說明有新連接if (result[i].data.fd == fd)//作用為監聽的fd{//4.acceptconnfd = accept(fd,NULL,NULL);if (connfd < 0){perror("accept fail");return -1;}set_nonblock(connfd);// 設置新連接為非阻塞模式// 將新連接的 socket 添加到 epolladd_fd(epfd,connfd);}else //通信的fd{// 處理客戶端數據connfd = result[i].data.fd;//取觸發事件的文件描述符char buf[1024];char sbuf[1024];while(1){ret = recv(connfd,buf,1,0);printf("c: %s\n",buf);if(ret < 0){if(errno == EWOULDBLOCK||errno == EAGAIN)break;// 非阻塞模式下,沒有更多數據可讀}if (strncmp(buf,"quit",4) == 0){	del_fd(epfd,connfd);close(connfd);continue;}sprintf(sbuf,"server + %s\n",buf);send(connfd,sbuf,strlen(sbuf)+1,0);}}}}}close(fd);return 0;
}

(七)select、poll、epoll 的比較

select 的缺點:

  1. select 監聽文件描述符最大個數為 1024。

  2. select 監聽的文件描述符集合在用戶層,需要應用層和內核層互相傳遞數據。

  3. select 需要循環遍歷一次才能找到產生的事件。

  4. select 只能工作在水平觸發模式(低速模式),無法工作在邊沿觸發模式(高速模式)。

poll 的缺點:

  1. poll 監聽文件描述符不受上限限制。

  2. poll 監聽的文件描述符集合在用戶層,需要內核層向用戶層傳遞數據。

  3. poll 需要循環遍歷一次才能找到產生的事件。

  4. poll 只能工作在水平觸發模式(低速模式),無法工作在邊沿觸發模式(高速模式)。

epoll 的優點:

  1. epoll 創建內核事件表,不受到文件描述符上限限制。

  2. epoll 監聽的事件表在內核中,直接在內核中監測事件效率高。

  3. epoll 會直接獲得產生事件的文件描述符的信息,而不需要遍歷檢測。

  4. epoll 既能工作在水平觸發模式,也能工作在邊沿觸發模式。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/73082.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/73082.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/73082.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

typora高亮方案+鼠標側鍵一鍵改色

引言 在typora里面有一個自定義的高亮, <mark></mark>>但是單一顏色就太難看了, 我使用人工智能, 搜索全網藝術家, 匯集了幾種好看的格式,并且方便大家側鍵一鍵 調用, 是不是太方便啦 ! 示例 午夜模式 春意盎然 深海藍調 石墨文檔 秋日暖陽 蜜桃宣言 使用方法 …

swift -(5) 匯編分析結構體、類的內存布局

一、結構體 在 Swift 標準庫中&#xff0c;絕大多數的公開類型都是結構體&#xff0c;而枚舉和類只占很小一部分 比如Bool、 Int、 Double、 String、 Array、 Dictionary等常見類型都是結構體 ① struct Date { ② var year: Int ③ var month: Int ④ …

從零開始學機器學習——初探分類器

首先給大家介紹一個很好用的學習地址:https://cloudstudio.net/columns 在上一節中,我們已經完成了對所有經過均衡和清洗的菜品數據的準備工作。接下來,我們將考慮使用多種算法,也就是不同的分類器,來構建模型。我們將利用這個數據集和各種分類器,基于一組特定的配料,預…

黑色RGB是什么

>好的&#xff0c;用戶現在問的是“黑色rgb是什么”。我需要先理解用戶的問題。之前用戶在使用MATLAB調整網格線顏色時&#xff0c;可能看到了默認顏色是黑色&#xff0c;或者之前我提到過默認顏色是[0.15 0.15 0.15]&#xff0c;而用戶可能現在想知道黑色的RGB值具體是什么…

做到哪一步才算精通SQL

做到哪一步才算精通SQL-Structured Query Language 數據定義語言 DDL for StructCREATE&#xff1a;用來創建數據庫、表、索引等對象ALTER&#xff1a;用來修改已存在的數據庫對象DROP&#xff1a;用來刪除整個數據庫或者數據庫中的表TRUNCATE&#xff1a;用來刪除表中所有的行…

《深度解析DeepSeek-M8:量子經典融合,重塑計算能效格局》

在科技飛速發展的今天&#xff0c;量子計算與經典算法的融合成為了前沿領域的焦點。DeepSeek-M8的“量子神經網絡混合架構”&#xff0c;宛如一把鑰匙&#xff0c;開啟了經典算法與量子計算協同推理的全新大門&#xff0c;為諸多復雜問題的解決提供了前所未有的思路。 量子計算…

解決電腦問題(2)——主板問題

當電腦主板出現問題時&#xff0c;可以嘗試以下解決方法&#xff1a; 外觀檢查與清潔 檢查硬件連接&#xff1a;仔細查看主板上的各種硬件連接&#xff0c;包括 CPU、內存、顯卡、硬盤、電源等的連接線是否松動或損壞。確保所有插頭都牢固地插入相應的插槽中&#xff0c;如有松…

Java 大視界 -- Java 大數據在智能家居能源管理與節能優化中的應用(120)

&#x1f496;親愛的朋友們&#xff0c;熱烈歡迎來到 青云交的博客&#xff01;能與諸位在此相逢&#xff0c;我倍感榮幸。在這飛速更迭的時代&#xff0c;我們都渴望一方心靈凈土&#xff0c;而 我的博客 正是這樣溫暖的所在。這里為你呈上趣味與實用兼具的知識&#xff0c;也…

【網絡】TCP常考知識點詳解

TCP報文結構 TCP報文由**首部&#xff08;Header&#xff09;和數據&#xff08;Data&#xff09;**兩部分組成。首部包括固定部分&#xff08;20字節&#xff09;和可選選項&#xff08;最多40字節&#xff09;&#xff0c;總長度最大為60字節。 1. 首部固定部分 源端口&…

算法1-6 一元三次方程求解

題目描述 有形如&#xff1a;ax3bx2cxd0 這樣的一個一元三次方程。給出該方程中各項的系數&#xff08;a,b,c,d 均為實數&#xff09;&#xff0c;并約定該方程存在三個不同實根&#xff08;根的范圍在 ?100 至 100 之間&#xff09;&#xff0c;且根與根之差的絕對值 ≥1。要…

05.基于 TCP 的遠程計算器:從協議設計到高并發實現

&#x1f4d6; 目錄 &#x1f4cc; 前言&#x1f50d; 需求分析 &#x1f914; 我們需要解決哪些問題&#xff1f; &#x1f3af; 方案設計 &#x1f4a1; 服務器架構 &#x1f680; 什么是協議&#xff1f;為什么要設計協議&#xff1f; &#x1f4cc; 結構化數據的傳輸問題 …

大數據面試之路 (一) 數據傾斜

記錄大數據面試歷程 數據傾斜 大數據崗位 &#xff0c;數據傾斜面試必問的一個問題。 一、數據傾斜的表現與原因 表現 某個或某幾個Task執行時間過長&#xff0c;其他Task快速完成。 Spark/MapReduce作業卡在某個階段&#xff08;如reduce階段&#xff09;&#xff0c;日志顯…

僅僅使用pytorch來手撕transformer架構(3):編碼器模塊和編碼器類的實現和向前傳播

僅僅使用pytorch來手撕transformer架構(2)&#xff1a;編碼器模塊和編碼器類的實現和向前傳播 往期文章&#xff1a; 僅僅使用pytorch來手撕transformer架構(1)&#xff1a;位置編碼的類的實現和向前傳播 最適合小白入門的Transformer介紹 僅僅使用pytorch來手撕transformer…

《OpenCV》—— dlib(換臉操作)

文章目錄 dlib換臉介紹仿射變換在 dlib 換臉中的應用 換臉操作 dlib換臉介紹 dlib 換臉是基于 dlib 庫實現的一種人臉替換技術&#xff0c;以下是關于它的詳細介紹&#xff1a; 原理 人臉檢測&#xff1a;dlib 庫中包含先進的人臉檢測器&#xff0c;如基于 HOG&#xff08;方向…

機器學習中的梯度下降是什么意思?

梯度下降&#xff08;Gradient Descent&#xff09;是機器學習中一種常用的優化算法&#xff0c;用于最小化損失函數&#xff08;Loss Function&#xff09;。通過迭代調整模型參數&#xff0c;梯度下降幫助模型逐步逼近最優解&#xff0c;從而提升模型的性能。 1.核心思想 梯…

三、Docker 集群管理與應用

&#xff08;一&#xff09;項目案例 1、準備主機 &#xff08;1&#xff09;關閉防火墻&#xff0c;或者開放TCP端口2377&#xff08;用于集群管理通信&#xff09;、TCP/UPD端口7946&#xff08;用于節點之間的通信&#xff09;、UDP端口4789&#xff08;用于overlay網絡流…

網絡DNS怎么更改?

訪問速度慢或某些網站無法打開?改變網絡DNS設置可能會幫助解決這些問題。本文將詳細介紹如何更改網絡DNS&#xff0c;包括更改的原因、具體步驟。 一、為什么要更改DNS? 更改DNS的原因有很多&#xff0c;以下是一些主要的考慮因素&#xff1a;某些公共DNS服務器的響應速度比…

江科大51單片機筆記【12】DS18B20溫度傳感器(上)

寫在前言 此為博主自學江科大51單片機&#xff08;B站&#xff09;的筆記&#xff0c;方便后續重溫知識 在后面的章節中&#xff0c;為了防止篇幅過長和易于查找&#xff0c;我把一個小節分成兩部分來發&#xff0c;上章節主要是關于本節課的硬件介紹、電路圖、原理圖等理論…

基于springboot+vue的佳途旅行分享預約平臺

一、系統架構 前端&#xff1a;vue2 | element-ui | html 后端&#xff1a;springboot | mybatis-plus 環境&#xff1a;jdk1.8 | mysql | maven | node 二、代碼及數據庫 三、功能介紹 01. web端-注冊 02. web端-登錄 03. web端-系統主頁1 04. web端-系統主頁2 05. we…

【數據結構】2算法及分析

0 章節 &#xff11;&#xff0e;&#xff14;到1&#xff0e;&#xff15;小節。 掌握算法概念、特性、描述、算法性能時間復雜度和空間復雜度&#xff1b; 理解遞歸含義&#xff1f; 掌握實現遞歸的條件和時機&#xff1b; 應用簡單遞歸問題的算法設計&#xff1b; 重點 算法…