文章目錄
- 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
套接字進行網絡連接流程, 如下圖:
服務器端:
- 創建服務器套接字
socket()
- 綁定端口
bind()
- 監聽端口
listen()
- 接受客戶端請求
accept()
- 讀取客戶端請求的數據
read()
- 返回客戶端要響應的數據
write()
- …
- 關閉與客戶端的連接
close()
- 關閉服務器套接字
close()
客戶端:
- 創建客戶端套接字
socket()
- 連接服務端
connect()
- 請求服務端數據, 發送操作數和操作符到服務器
write()
- 從服務器讀取操作結果
read()
- …
- 關閉客戶端套接字
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 網絡編程實例
就是一個阻塞式的模型, accept
和 read
都是阻塞的, 當 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);
- int epoll_create(int size); 創建一個epoll的句柄,size用來告訴內核這個監聽的數目一共有多大,這個參數不同于select()中的第一個參數,給出最大監聽的fd+1的值,參數size并不是限制了epoll所能監聽的描述符最大個數,只是對內核初始分配內部數據結構的一個建議。 當創建好epoll句柄后,它就會占用一個fd值,在linux下如果查看/proc/進程id/fd/,是能夠看到這個fd的,所以在使用完epoll后,必須調用close()關閉,否則可能導致fd被耗盡。
- 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隊列里
- 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的區別