Linux C++ Socket 套接字、select、poll、epoll 實例

文章目錄

  • 1. 概述
  • 2. TCP 網絡編程實例
    • 2.1 服務器端
    • 2.2 客戶端
    • 2.3 運行截圖
  • 3. I/O 模型
    • 3.1 阻塞式I/O模型
    • 3.2 非阻塞I/O模型
    • 3.3 I/O 復用模型
    • 3.4 信號驅動式I/O
    • 3.5 異步I/O模型
  • 4. I/O復用之 select
    • 4.1 select 函數描述
    • 4.2 服務端代碼
    • 4.3 客戶端代碼
    • 4.4 運行截圖
  • 5. I/O復用之 poll
    • 5.1 poll 函數描述
    • 5.2 服務端代碼
    • 5.3 客戶端代碼
    • 5.4 運行截圖
  • 6. I/O復用之 epoll
    • 6.1 常用函數
    • 6.2 服務端代碼
  • 參考文獻

1. 概述

  • 網絡編程, 就是編寫程序, 使兩臺聯網的電腦可以交換數據,
  • 套接字是網絡數據傳輸用的軟件設備, 用來連接網絡的工具
  • 在 linux中 socket被認為是文件中的一種, 在網絡數據傳輸過程中, 使用文件I/O的相關函數
  • 套接字常用網絡協議: TCP、UDP

套接字進行網絡連接流程, 如下圖:

服務器端:

  1. 創建服務器套接字 socket()
  2. 綁定端口 bind()
  3. 監聽端口 listen()
  4. 接受客戶端請求 accept()
  5. 讀取客戶端請求的數據 read()
  6. 返回客戶端要響應的數據 write()
  7. 關閉與客戶端的連接 close()
  8. 關閉服務器套接字 close()

客戶端:

  1. 創建客戶端套接字 socket()
  2. 連接服務端 connect()
  3. 請求服務端數據, 發送操作數和操作符到服務器 write()
  4. 從服務器讀取操作結果 read()
  5. 關閉客戶端套接字 close()

流程圖如下, 具體代碼示例可以看下面的 2. TCP 網絡編程實例在這里插入圖片描述

2. TCP 網絡編程實例

2.1 服務器端

開放的端口號 6666:

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>// 定義緩沖區大小和操作數大小
#define BUF_SIZE 1024
#define OPSZ 4// 錯誤處理函數
void error_handling(char *message);// 計算函數,根據操作數和操作符進行計算
int calculate(int opnum, int opnds[], char oprator);int main(int argc, char *argv[]) {int serv_sock, clnt_sock;char opinfo[BUF_SIZE]; // 用于接收客戶端發送的操作數和操作符int result, opnd_cnt, i;int recv_cnt, recv_len;struct sockaddr_in serv_adr, clnt_adr;socklen_t clnt_adr_sz;// 創建服務器套接字serv_sock = socket(PF_INET,SOCK_STREAM, 0);if (serv_sock == -1)error_handling("socket() error");// 初始化服務器地址結構體memset(&serv_adr, 0, sizeof(serv_adr));serv_adr.sin_family = AF_INET;serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);serv_adr.sin_port = 6666; // 設置端口號為 6666// 綁定端口if (bind(serv_sock, (struct sockaddr *) &serv_adr, sizeof(serv_adr)) == -1)error_handling("bind() error");// 監聽端口if (listen(serv_sock, 5) == -1)error_handling("listen() error");clnt_adr_sz = sizeof(clnt_adr); // 設置客戶端地址結構體的大小// 接受多個客戶端連接并進行計算for (i = 0; i < 5; i++) {opnd_cnt = 0;clnt_sock = accept(serv_sock, (struct sockaddr *) &clnt_adr, &clnt_adr_sz); // 接受客戶端連接read(clnt_sock, &opnd_cnt, 1); // 讀取操作數個數recv_len = 0;// 循環接收操作數,直到接收完所有操作數while ((opnd_cnt * OPSZ + 1) > recv_len) {recv_cnt = read(clnt_sock, &opinfo[recv_len], BUF_SIZE - 1); // 從客戶端接收數據recv_len += recv_cnt;// 計算結果并發送回客戶端result = calculate(opnd_cnt, (int *) opinfo, opinfo[recv_len - 1]);write(clnt_sock, (char *) &result, sizeof(result));close(clnt_sock); // 關閉與客戶端的連接}}close(serv_sock); // 關閉服務器套接字return 0;
}// 計算函數,根據操作數和操作符返回計算結果
int calculate(int opnum, int opnds[], char op) {int result = opnds[0], i;switch (op) { // 根據操作符進行不同的計算case '+':for (i = 1; i < opnum; i++) result += opnds[i];break;case '-':for (i = 1; i < opnum; i++) result -= opnds[i];break;case '*':for (i = 1; i < opnum; i++) result *= opnds[i];break;}return result;
}// 錯誤處理函數,在遇到錯誤時打印消息并退出程序
void error_handling(char *message) {fputs(message, stderr);fputc('\n', stderr);exit(1);
}

2.2 客戶端

#include <stdio.h>          // 標準輸入輸出
#include <stdlib.h>         // 系統函數庫
#include <string.h>         // 字符串處理
#include <unistd.h>         // UNIX標準函數
#include <arpa/inet.h>      // Internet網絡協議
#include <sys/socket.h>     // Socket編程相關// 定義緩沖區大小、結果大小和操作數字節大小
#define BUF_SIZE 1024
#define RLT_SIZE 4
#define OPSZ 4// 錯誤處理函數
void error_handling(char *message);int main(int argc, char *argv[]) {int sock; // Socket描述符char opmsg[BUF_SIZE]; // 用于存儲操作數和操作符的消息int result, opnd_cnt, i; // 結果、操作數個數和循環變量struct sockaddr_in serv_adr; // 服務端地址結構體// 創建Socketsock = socket(PF_INET,SOCK_STREAM, 0);if (sock == -1)error_handling("socket() error");// 初始化服務端地址memset(&serv_adr, 0, sizeof(serv_adr));serv_adr.sin_family = AF_INET;serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);serv_adr.sin_port = 6666;// 連接服務端if (connect(sock, (struct sockaddr *) &serv_adr, sizeof(serv_adr)) == -1)error_handling("connect() error!");elseputs("Connected ...........");// 獲取并存儲操作數個數fputs("Operand count: ",stdout);scanf("%d", &opnd_cnt);opmsg[0] = (char) opnd_cnt;// 循環讀取操作數for (i = 0; i < opnd_cnt; i++) {printf("Operand %d:", i + 1);scanf("%d", (int *) &opmsg[i * OPSZ + 1]);}// 暫停,等待用戶輸入操作符fgetc(stdin);fputs("Operator: ",stdout);scanf("%c", &opmsg[opnd_cnt * OPSZ + 1]);// 發送操作數和操作符到服務器write(sock, opmsg, opnd_cnt * OPSZ + 2);// 從服務器讀取操作結果read(sock, &result, RLT_SIZE);// 打印操作結果printf("Operation result: %d \n", result);// 關閉Socketclose(sock);return 0;
}// 錯誤處理函數:輸出錯誤信息并退出程序
void error_handling(char *message) {fputs(message, stderr);fputc('\n', stderr);exit(1);
}

2.3 運行截圖

在這里插入圖片描述

3. I/O 模型

一個入門科普文章

3.1 阻塞式I/O模型

2. TCP 網絡編程實例 就是一個阻塞式的模型, acceptread 都是阻塞的, 當 accept 到新連接, 或者 read 到數據程序才往下走

為了提高服務端處理能力, 一個客戶端連接一個線程處理

不能一個線程處理多個客戶端, 某個客戶端會阻塞這個線程處理其他客戶端
在這里插入圖片描述

3.2 非阻塞I/O模型

不停的輪詢, 看看有沒有accept 到新連接, 沒有連接不阻塞等待, 繼續去看看已經建立的連接有沒有read到客戶端的新數據, read到新數據處理, read不到不處理

為了提高服務端處理能力, 可以一個客戶端連接一個線程處理, 線程不停的輪詢自己要處理的客戶端

也可以一個線程處理多個客戶端, 相較于上面的阻塞I/O模型, 非阻塞不至于某個客戶端阻塞這個線程處理其他客戶端
在這里插入圖片描述

3.3 I/O 復用模型

可以調用 select/poll/epoll , 阻塞在select/poll/epoll, select/poll/epoll 監聽多個客戶端連接事件或寫入的數據, 然后這些事件可再有多個線程分一分處理掉
在這里插入圖片描述

3.4 信號驅動式I/O

讓內核在描述符就緒時發送SINIO信號 通知我們

Linux 網絡編程的5種IO模型:信號驅動IO模型 接受SINIO信號, 直接 recvfrom , 然后 sentTo 了, 不用 , 遍歷socket
在這里插入圖片描述

3.5 異步I/O模型

告訴內核啟動某個操作, 并且把數據copy到用戶緩沖區再通知我們, 和信號驅動的區別是, 信號驅動內核通知我們去I/O, 異步I/O, 通知我們I/O完成了, 送到了

[C++ 網絡協議] 異步通知I/O模型

在這里插入圖片描述

4. I/O復用之 select

4.1 select 函數描述

int      select(int maxfdpl, fd_set *readSet, fd_set *writeSet,fd_set *exceptSet, struct timeval *timeout)
  • 最大描述符+1
  • fd_set *readSet 讀事件描述符
  • fd_set *writeSet 寫事件描述符
  • fd_set *exceptSet 異常事件描述符
  • struct timeval *timeout 等待超時時間

fd_set 里數組初始化1024, 網上都說 select 最多監聽 1024 個連接
在這里插入圖片描述

  • FD_SET: 將套接字添加到文件描述符集合中
  • FD_ZERO: 用來清空文件描述符集合
  • FD_CLR: 從套接字描述符集合中清除指定的套接字描述符
  • FD_ISSET: 檢查文件描述符集合中是否包含文件描述符

4.2 服務端代碼

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/select.h>// 定義緩沖區大小
#define BUF_SIZE 100// 錯誤處理函數
void error_handling(char *message);int main(int argc, char *argv[]) {int serv_sock, clnt_sock; // 服務端和客戶端套接字struct sockaddr_in serv_adr, clnt_adr; // 服務端和客戶端地址結構體struct timeval timeout; // 超時時間結構體fd_set reads, cpy_reads; // 文件描述符集合,用于select函數socklen_t adr_sz; // 地址大小int fd_max, str_len, fd_num, i; // 各種變量初始化char buf[BUF_SIZE]; // 用于讀取和寫入數據的緩沖區// 創建套接字serv_sock = socket(PF_INET,SOCK_STREAM, 0);// 初始化服務端地址結構體memset(&serv_adr, 0, sizeof(serv_adr));serv_adr.sin_family = AF_INET;serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);serv_adr.sin_port = 6666;// 綁定端口if (bind(serv_sock, (struct sockaddr *) &serv_adr, sizeof(serv_adr)) == -1)error_handling("bind() error");// 監聽端口if (listen(serv_sock, 5) == -1)error_handling("listen() error");// 用來清空文件描述符集合readFD_ZERO(&reads);// 將服務器套接字serv_sock添加到文件描述符集合reads中FD_SET(serv_sock, &reads);fd_max = serv_sock;// 無限循環,等待客戶端連接和處理數據while (1) {cpy_reads = reads;timeout.tv_sec = 5;timeout.tv_usec = 5000;// 使用select函數等待事件發生if ((fd_num = select(fd_max + 1, &cpy_reads, 0, 0, &timeout)) == -1)break;// 沒有客戶端連接if (fd_num == 0) {continue;}// 遍歷文件描述符集合,處理連接和數據傳輸for (i = 0; i < fd_max + 1; i++) {// 檢查文件描述符集合cpy_reads中是否包含文件描述符iif (FD_ISSET(i, &cpy_reads)) {if (i == serv_sock) {// 處理新連接adr_sz = sizeof(clnt_adr);clnt_sock = accept(serv_sock, (struct sockaddr *) &clnt_adr, &adr_sz);FD_SET(clnt_sock, &reads);if (fd_max < clnt_sock)fd_max = clnt_sock;printf("connected client: %d \n", clnt_sock);} else {// 處理數據傳輸str_len = read(i, buf, BUF_SIZE);if (str_len == 0) {// 從reads套接字描述符集合中清除指定的套接字描述符iFD_CLR(i, &reads);close(i);printf("connected client: %d \n", i);} else {// 向客戶端寫入數據write(i, buf, str_len);}}}}}// 關閉服務端套接字close(serv_sock);return 0;
}// 錯誤處理函數
void error_handling(char *message) {fputs(message, stderr);fputc('\n', stderr);exit(1);
}

4.3 客戶端代碼

#include <stdio.h>          // 標準輸入輸出頭文件
#include <stdlib.h>         // 通用實用程序頭文件
#include <arpa/inet.h>      // IPv4 Internet地址轉換頭文件
#include <sys/socket.h>     // 用于創建和操作套接字的頭文件
#include <string.h>         // 字符串操作頭文件
#include <unistd.h>         // 提供一些與操作系統交互的函數
#include <sys/types.h>      // 定義各種數據類型的頭文件
#include <sys/time.h>       // 時間處理頭文件
#include <sys/select.h>     // 用于文件描述符選擇的頭文件#define BUF_SIZE 1024      // 定義緩沖區大小// 錯誤處理函數
// 參數: buf - 存儲錯誤信息的字符數組
void error_handling(char *buf);int main(int argc, char *argv[]){ // 程序入口int sock;                     // 套接字變量char message[BUF_SIZE];       // 用于存儲消息的緩沖區int str_len;                  // 存儲字符串長度的變量struct sockaddr_in serv_adr;  // 用于存儲服務器地址信息的結構體// 創建TCP套接字sock = socket(PF_INET, SOCK_STREAM, 0);if (sock==-1){error_handling("socket create error"); // 若創建失敗,調用錯誤處理函數}// 初始化服務器地址結構體memset(&serv_adr, 0,sizeof(serv_adr));serv_adr.sin_family = AF_INET;serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);serv_adr.sin_port = 6666;// 嘗試連接到服務器if (connect(sock,(struct sockaddr*)&serv_adr, sizeof(serv_adr))==-1){error_handling("connect error"); // 若連接失敗,調用錯誤處理函數}else{puts("connected......"); // 連接成功,輸出提示信息}// 進入消息收發循環while (1){fputs("input message (q to quit): ", stdout); // 提示用戶輸入消息fgets(message, BUF_SIZE, stdin); // 從標準輸入讀取消息if (!strcmp(message,"q\n")||!strcmp(message,"Q\n") ){break; // 如果用戶輸入的是 'q' 或 'Q',則退出循環}write(sock, message, strlen(message)); // 將消息寫入套接字,發送給服務器str_len=read(sock, message, BUF_SIZE-1); // 從套接字讀取消息,存儲到message中message[str_len] = 0; // 在消息末尾添加null字符,以結束字符串printf("Message from server: %s", message); // 輸出服務器返回的消息}close(sock); // 關閉套接字return 0; // 程序正常結束,返回0
}// 錯誤處理函數
// 參數: buf - 存儲錯誤信息的字符數組
// 說明: 該函數將錯誤信息輸出到標準錯誤流,并退出程序
void error_handling(char* buf){fputs(buf, stderr); // 將錯誤信息輸出到標準錯誤流fputc('\n', stderr); // 輸出換行符exit(1); // 退出程序,返回碼為1,表示異常結束
}

4.4 運行截圖

在這里插入圖片描述

5. I/O復用之 poll

5.1 poll 函數描述

poll 提供的功能和 selcet 差不多, poll 可以自己聲明最大長度, 還不用自己去 FD_SET、FD_ISSET 維護狀態

int poll(struct pollfd *fdarray, unsigned long nfds, int timeout);struct pollfd {int     fd;short   events;short   revents;
};
  • fdarray : pollfd數組
  • nfds : fd 數量
  • timeout : 超時時間

5.2 服務端代碼

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/poll.h>using namespace std;int main() {int listenfd = socket(AF_INET, SOCK_STREAM, 0);sockaddr_in serverAddr;memset(&serverAddr, 0, sizeof(serverAddr));serverAddr.sin_family = AF_INET;serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);serverAddr.sin_port = 6666;::bind(listenfd, (struct sockaddr*)&serverAddr, sizeof(serverAddr));listen(listenfd, 10);/*poll只是對select函數傳入的參數的一個封裝,底層實現還是select,好處就是需要傳入的參數少了*///pollfd數組, 一個元素為一個 描述符以及相對應的事件。元素的個數為可處理socket的最大個數,突破了select的1024的限制pollfd pfds[2048] = {0};pfds[listenfd].fd = listenfd;pfds[listenfd].events = POLLIN; //可讀//pfds.revents //revents為poll返回的事件int maxfd = listenfd;while (1) {int nready = poll(pfds, maxfd + 1, -1);//監聽socket有可讀事件if (pfds[listenfd].revents & POLLIN) {sockaddr_in clientAddr;socklen_t len = sizeof(clientAddr);int clientfd = accept(pfds[listenfd].fd, (sockaddr*)&clientAddr, &len);printf("accept==>> clientfd:%d %s:%d\n", clientfd, inet_ntoa(clientAddr.sin_addr), ntohs(clientAddr.sin_port));if (-1 == clientfd) {continue;}//請注意這里的clientfd越界問題,這里就不做處理了//需要注意的是,如果有客戶端關閉連接了,再次有新的客戶端連接進來時,accept返回的是沒有使用的最小的描述符//比如現在有4,5,6客戶端建立了連接,4和5客戶端斷開了,再次有新的客戶端建立連接后,accept會給分配4描述符,這點內核還是很人性化的。pfds[clientfd].fd = clientfd;pfds[clientfd].events = POLLIN;maxfd = clientfd;}for (int i = listenfd + 1; i <= maxfd; i++) {if (pfds[i].revents & POLLIN) {char buf[128] = {0};int count = recv(pfds[i].fd, buf, sizeof(buf), 0);if (count == 0) {printf("close\n");close(pfds[i].fd);pfds[i].fd = -1;pfds[i].events = 0;continue;}printf("clientfd:%d, count:%d, buf:%s\n", pfds[i].fd, count, buf);send(pfds[i].fd, buf, count, 0);}}}close(listenfd);return 0;
}

5.3 客戶端代碼

同 4.3

5.4 運行截圖

在這里插入圖片描述

6. I/O復用之 epoll

epoll 比 select、poll 性能都好, 獲得就緒的文件描述符, select、poll O(n) 復雜度, epoll 是 O(1)

select,poll是基于輪詢實現的,將fd_set從用戶空間復制到內核空間,然后讓內核空間以poll機制來進行輪詢,一旦有其中一個fd對應的設備活躍了,那么就把整個fd_set返回給客戶端(復制到用戶空間),再由客戶端來輪詢每個fd的,找出發生了IO事件的fd

epoll是基于事件驅動實現的,加入一個新的fd,會調用epoll_ctr函數為該fd注冊一個回調函數,然后將該fd結點注冊到內核中的epoll紅黑樹中,當IO事件發生時,就會調用回調函數,將該fd結點放到就緒鏈表中,epoll_wait函數實際上就是從這個就緒鏈表中獲取這些fd。

epoll很詳細的文章

6.1 常用函數

poll操作過程需要三個接口,分別如下:

int epoll_create(int size)//創建一個epoll的句柄,size用來告訴內核這個監聽的數目一共有多大
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
  1. int epoll_create(int size); 創建一個epoll的句柄,size用來告訴內核這個監聽的數目一共有多大,這個參數不同于select()中的第一個參數,給出最大監聽的fd+1的值,參數size并不是限制了epoll所能監聽的描述符最大個數,只是對內核初始分配內部數據結構的一個建議。 當創建好epoll句柄后,它就會占用一個fd值,在linux下如果查看/proc/進程id/fd/,是能夠看到這個fd的,所以在使用完epoll后,必須調用close()關閉,否則可能導致fd被耗盡。
  2. int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); 函數是對指定描述符fd執行op操作。
  • epfd:是epoll_create()的返回值。
  • op:表示op操作,用三個宏來表示:添加EPOLL_CTL_ADD,刪除EPOLL_CTL_DEL,修改EPOLL_CTL_MOD。分別添加、刪除和修改對fd的監聽事件。
  • fd:是需要監聽的fd(文件描述符)
  • epoll_event:是告訴內核需要監聽什么事,struct epoll_event結構如下:
struct epoll_event {__uint32_t events;  /* Epoll events */epoll_data_t data;  /* User data variable */
};

//events可以是以下幾個宏的集合:
EPOLLIN :表示對應的文件描述符可以讀(包括對端SOCKET正常關閉);
EPOLLOUT:表示對應的文件描述符可以寫;
EPOLLPRI:表示對應的文件描述符有緊急的數據可讀(這里應該表示有帶外數據到來);
EPOLLERR:表示對應的文件描述符發生錯誤;
EPOLLHUP:表示對應的文件描述符被掛斷;
EPOLLET: 將EPOLL設為邊緣觸發(Edge Triggered)模式,這是相對于水平觸發(Level Triggered)來說的。
EPOLLONESHOT:只監聽一次事件,當監聽完這次事件之后,如果還需要繼續監聽這個socket的話,需要再次把這個socket加入到EPOLL隊列里

  1. int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout); 等待epfd上的io事件,最多返回maxevents個事件。 參數events用來從內核得到事件的集合,maxevents告之內核這個events有多大,這個maxevents的值不能大于創建epoll_create()時的size,參數timeout是超時時間(毫秒,0會立即返回,-1將不確定,也有說法說是永久阻塞)。該函數返回需要處理的事件數目,如返回0表示已超時。

6.2 服務端代碼

我用的MAC電腦, 不支持 epoll, 下面的代碼沒運行過:

#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/select.h>
#include <sys/epoll.h>#define BUF_SIZE 100
#define EPOLL_SIZE 50void error_handling(char *buf);
/*** 主程序:創建一個使用epoll模型的服務器,監聽客戶端連接,并處理客戶端請求。** @param argc 命令行參數個數* @param argv 命令行參數數組* @return 總是返回0,表示程序正常結束*/
int main(int argc, char *argv[]) {// 定義服務器套接字和客戶端套接字int serv_sock, clnt_sock;// 定義服務器地址結構體和客戶端地址結構體struct sockaddr_in serv_adr, clnt_adr;// 定義地址大小變量socklen_t adr_sz;// 定義字符串長度和循環索引int str_len, i;// 定義緩沖區,用于讀取和寫入數據char buf[BUF_SIZE];// 定義epoll事件結構體數組和epoll文件描述符struct epoll_event *ep_events;struct epoll_event event;// 定義epoll等待事件個數變量int epfd, event_cnt;// 檢查命令行參數是否正確if (argc != 2) {printf("usage error\n");exit(1);}// 創建服務器套接字serv_sock = socket(PF_INET, SOCK_STREAM, 0);// 初始化服務器地址結構體memset(&serv_adr, 0, sizeof(serv_adr));serv_adr.sin_family = AF_INET;serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);serv_adr.sin_port = 6666;// 綁定服務器套接字到指定地址和端口if (bind(serv_sock, (struct sockaddr *) &serv_adr, sizeof(serv_adr)) == -1) {error_handling("bind error");}// 監聽服務器套接字if (listen(serv_sock, 5) == -1) {error_handling("listen error");}// 創建epoll實例epfd = epoll_create(EPOLL_SIZE);// 分配存儲epoll事件的內存ep_events = malloc(sizeof(struct epoll_event) * EPOLL_SIZE);// 將服務器套接字添加到epoll監控列表中event.events = EPOLLIN;event.data.fd = serv_sock;epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event);// 主循環,等待和處理客戶端連接和請求while (1) {// 等待epoll事件event_cnt = epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);if (event_cnt == -1) {puts("epoll wait error\n");break;}// 遍歷所有觸發的epoll事件for (i = 0; i < event_cnt; i++) {// 處理服務器套接字上的事件,即新的客戶端連接if (ep_events[i].data.fd == serv_sock) {adr_sz = sizeof(clnt_adr);clnt_sock = accept(serv_sock, (struct sockaddr *) &clnt_adr, &adr_sz);// 將新連接的客戶端套接字添加到epoll監控列表中event.events = EPOLLIN;event.data.fd = clnt_sock;epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);printf("connnected client: %d\n", clnt_sock);} else {// 處理客戶端套接字上的事件,即客戶端發送的數據str_len = read(ep_events[i].data.fd, buf,BUF_SIZE);if (str_len == 0) {// 如果客戶端關閉了連接,則從epoll監控列表中移除,并關閉套接字epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL);close(ep_events[i].data.fd);printf("closed client: %d \n", ep_events[i].data.fd);} else {// 如果客戶端發送了數據,則將數據回傳給客戶端write(ep_events[i].data.fd, buf, str_len);}}}}// 關閉服務器套接字和epoll文件描述符close(serv_sock);close(epfd);return 0;
}/*** 錯誤處理函數:輸出錯誤信息并退出程序。** @param buf 包含錯誤信息的字符串*/
void error_handling(char *buf) {fputs(buf, stderr);fputc('\n', stderr);exit(1);
}

參考文獻

  • UNIX 網絡編程 卷1: 套接字聯網API
  • TCP/IP網絡編程 尹圣雨 著 金國哲 譯
  • Linux IO模式及 select、poll、epoll詳解
  • 淺談select,poll和epoll的區別

在這里插入圖片描述

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

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

相關文章

RocketMq局部順序消息

package com.ldj.rocketmq.producer;import org.apache.rocketmq.client.producer.DefaultMQProducer; import org.apache.rocketmq.common.message.Message;import java.nio.charset.StandardCharsets;/*** User: ldj* Date: 2024/5/26* Time: 15:09* Description: 局部順序消…

【Linux】$()中的內容與不加$()時有什么區別

$()中的內容與不加$()有什么區別&#xff0c;例如$(/usr/local/hadoop/bin/hadoop classpath)與/usr/local/hadoop/bin/hadoop classpath兩者有何區別&#xff1f;&#xff1f;&#xff1f; 關于這個問題&#xff0c;筆者建議可以參考如下文章&#xff1a; Linux—shell中$((…

css卡片翻轉 父元素翻轉子元素不翻轉效果

css卡片翻轉 父元素翻轉子元素不翻轉效果 vue <div class"moduleBox"><div class"headTitle"><span class"headName">大額案例</span></div><div class"moduleItem"><span class"module…

three.js判斷物體在人的前面,還是后面

three.js判斷物體在人的前面&#xff0c;還是后面 const player new THREE.Vectors(10, 0, 5); const mesh new THREE.Vectors(15, 0, 6);上面&#xff0c;兩個變量分別表示&#xff0c;玩家的位置&#xff0c;物體的位置。 從這發現&#xff0c;當玩家和物體的角度關系 小…

spring boot 整合j2cache 項目啟動警告 Redis mode [null] not defined. Using ‘single‘

好 之前的文章 spring boot 整合j2cache 基礎操作 在spring boot環境中整合了 j2cache 我們 項目啟動時 日志會有一個關鍵信息 Redis的模式 沒有定義 默認使用 single Redis 的這個模式有四種 大家可以自己去網上找一下 做個了解 不用很糾結 我們直接在 j2cache.properties …

一文讀懂Apollo客戶端配置加載流程

本文基于 apollo-client 2.1.0 版本源碼進行分析 Apollo 是攜程開源的配置中心&#xff0c;能夠集中化管理應用不同環境、不同集群的配置&#xff0c;配置修改后能夠實時推送到應用端&#xff0c;并且具備規范的權限、流程治理等特性。 Apollo支持4個維度管理Key-Value格式的配…

Elasticsearch智能數據分析平臺項目

Elasticsearch智能數據分析平臺項目是一個功能強大且靈活的數據分析工具,旨在幫助企業快速、準確地分析和挖掘數據中的價值。以下是關于該項目的一些關鍵特點和功能: 數據搜索: Elasticsearch作為全球下載量最大的搜索引擎,支持從關鍵字搜索到向量搜索等多樣化搜索方式,讓…

比勤奮更重要的是系統思考的能力

不要在接近你問題癥狀的地方尋找解決辦法&#xff0c;要追溯過去&#xff0c;查找問題的根源。通常&#xff0c;最有效的活動是最微妙的。有時最好按兵不動&#xff0c;使系統自我修正&#xff0c;或讓系統引導行動。有時會發現&#xff0c;最好的解決辦法出現在完全出乎預料的…

HTML藍色愛心

目錄 寫在前面 HTML入門 完整代碼 代碼分析 運行結果 系列推薦 寫在后面 寫在前面 最近好冷吖&#xff0c;小編給大家準備了一個超級炫酷的愛心&#xff0c;一起來看看吧&#xff01; HTML入門 HTML全稱為HyperText Markup Language&#xff0c;是一種標記語言&#…

C++-指針

在C中&#xff0c;指針是至關重要的組成部分。它是C語言最強大的功能之一&#xff0c;也是最棘手的功能之一。 指針具有強大的能力&#xff0c;其本質是協助程序員完成內存的直接操縱。 指針&#xff1a;特定類型數據在內存中的存儲地址&#xff0c;即內存地址。 指針變量的定…

2024.5組隊學習——MetaGPT(0.8.1)智能體理論與實戰(下):多智能體開發

傳送門&#xff1a; 《2024.5組隊學習——MetaGPT&#xff08;0.8.1&#xff09;智能體理論與實戰&#xff08;上&#xff09;&#xff1a;MetaGPT安裝、單智能體開發》《2024.5組隊學習——MetaGPT&#xff08;0.8.1&#xff09;智能體理論與實戰&#xff08;中&#xff09;&…

ModelBuilder之GDP空間化——批量值提取

一、前言 前面明確說到對于空間化過程中其實只有兩個過程可以進行批量操作,一個是我們燈光指數提取過程和批量進行值提取,這里補充一點,對于燈光指數計算可以實現批量計算總燈光指數和平均燈光指數,綜合燈光指數需要用平均燈光指數乘以面積占比求得,面積比就是(DN大于0的…

VS2022通過C++網絡庫Boost.asio搭建一個簡單TCP異步服務器和客戶端

基本介紹 上一篇博客我們介紹了通過Boost.asio搭建一個TCP同步服務器和客戶端&#xff0c;這次我們再通過asio搭建一個異步通信的服務器和客戶端系統&#xff0c;由于這是一個簡單異步服務器&#xff0c;所以我們的異步特指異步服務器而不是異步客戶端&#xff0c;同步服務器在…

BGP選路規則

配置地址&#xff0c;AS123使用ospf保證通訊&#xff0c;修改接口類型保證ospf學習環回20.0,30.0,100.0 地址時&#xff0c;是以24位掩碼學習&#xff0c;R1&#xff0c;R2&#xff0c;R3都處于BGP邊界&#xff0c;各自都需要宣告三者的私網環回 1&#xff0c; [R4]ip ip-prefi…

點分治練習

P3806 【模板】點分治 1 #include <bits/stdc.h> using namespace std;inline long long read() {char ch getchar();long long f 1,x 0;while (ch > 9 || ch < 0) { if (ch -)f -1; ch getchar(); }while (ch > 0 && ch < 9) { x (x <&l…

Thrift學習深入

Thrift學習深入 https://zhuanlan.zhihu.com/p/22934974 https://zhuanlan.zhihu.com/p/26993406 從具體的demo入手,我們需要學習的是三部分 IDLserver端client端一、IDL深入 IDL定義的通用類型有: 基礎數據類型結構體容器 list、set、map異常:語法與結構體無異,不過用…

第十二周筆記

微信小程序的自定義事件是指開發者可以自行定義并觸發的事件&#xff0c;以實現特定的功能或邏輯。通過自定義事件&#xff0c;開發者可以更靈活地管理小程序的交互和數據流動&#xff0c;提升用戶體驗和開發效率。下面我將詳細講解微信小程序自定義事件&#xff0c;包括定義、…

容器化部署

目錄 docker容器化部署 怎樣使用Docker Compose或Kubernetes等容器編排工具來管理和擴展聯邦學習系統 使用Docker Compose

【Qnx 】Qnx IPC通信PPS

Qnx IPC通信PPS Qnx自帶PPS服務&#xff0c;PPS全稱Persistent Publish/Subscribe Service&#xff0c;就是常見的P/S通信模式。 Qnx PPS的通信模式是異步的&#xff0c;Publisher和Subscriber也無需關心對方是否存在。 利用Qnx提供的PPS服務&#xff0c;Publisher可以通知多…

嵌入式進階——LED呼吸燈(PWM)

&#x1f3ac; 秋野醬&#xff1a;《個人主頁》 &#x1f525; 個人專欄:《Java專欄》《Python專欄》 ??心若有所向往,何懼道阻且長 文章目錄 PWM基礎概念STC8H芯片PWMA應用PWM配置詳解占空比 PWM基礎概念 PWM全稱是脈寬調制&#xff08;Pulse Width Modulation&#xff09…