/*5 - 使用epoll實現多路復用 */
#include <stdio.h> // 標準輸入輸出函數庫
#include <stdlib.h> // 標準庫函數,包含exit等
#include <string.h> // 字符串處理函數
#include <unistd.h> // Unix標準函數,包含read, write等
#include <sys/socket.h> // 套接字相關函數
#include <sys/types.h> // 基本系統數據類型
#include <sys/epoll.h> // epoll相關函數和結構體
#include <fcntl.h> // 文件控制函數
#include <sys/stat.h> // 文件狀態相關定義
#include <netinet/in.h> // 互聯網地址族
#include <arpa/inet.h> // 提供IP地址轉換函數
#include <signal.h> // 信號處理函數
#include <linux/input.h> // Linux輸入設備相關定義(未實際使用)#define FD_CNT 1000 // 定義最大監控的文件描述符數量int main(int argc,char *argv[])
{if(argc!=2){ // 檢查命令行參數數量是否正確printf("Usage:%s port\n",argv[0]); // 輸出程序使用方法:程序名 端口號exit(0); // 正常退出}//1.創建socketint sockfd = socket(AF_INET,SOCK_STREAM,0); // 創建TCP套接字,AF_INET表示IPv4,SOCK_STREAM表示TCPif(sockfd==-1){ // 檢查socket創建是否成功perror("socket"); // 輸出錯誤信息exit(-1); // 異常退出}//允許地址復用int optval = 1; // 選項值,1表示啟用// 設置套接字選項,允許地址復用,避免端口占用問題setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR,&optval, sizeof(optval));//2.綁定ip和端口號(自己)struct sockaddr_in addr; // 定義IPv4地址結構體addr.sin_family = AF_INET; // 設置協議族為IPv4addr.sin_port = htons(atoi(argv[1])); // 將命令行參數的端口號轉換為網絡字節序addr.sin_addr.s_addr = INADDR_ANY; // 綁定到所有可用的網絡接口// 綁定套接字到指定地址和端口int res = bind(sockfd,(struct sockaddr *)&addr,sizeof(addr));if(res==-1){ // 檢查綁定是否成功perror("bind"); // 輸出錯誤信息exit(-1); // 異常退出}//3.監聽res = listen(sockfd,10); // 開始監聽連接,最大等待隊列長度為10if(res==-1){ // 檢查監聽是否成功perror("listen"); // 輸出錯誤信息exit(-1); // 異常退出}//準備epoll事件數組,用于存儲epoll_wait返回的活動事件struct epoll_event evt_arr[FD_CNT] = {}; // 事件數組初始化int maxfd,i; // maxfd未實際使用,i用于循環char msg[1024] = {}; // 用于存儲接收和發送的消息//1.創建epoll專用描述符// 參數為最大監控數量(>=1,Linux 2.6.8后忽略該參數)int epfd = epoll_create(1000);if(epfd==-1){ // 檢查epoll創建是否成功perror("epoll_create"); // 輸出錯誤信息exit(-1); // 異常退出}//2.添加要監控的事件struct epoll_event evt = { // 定義epoll事件結構體.events = EPOLLIN, // 監控讀事件(有數據可讀)};evt.data.fd = 0; // 綁定標準輸入文件描述符(0)// 將標準輸入添加到epoll監控res = epoll_ctl(epfd,EPOLL_CTL_ADD,0,&evt);if(res==-1){ // 檢查添加是否成功perror("epoll_ctl"); // 輸出錯誤信息exit(-1); // 異常退出}evt.data.fd = sockfd; // 綁定服務器監聽套接字// 將監聽套接字添加到epoll監控res = epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&evt);if(res==-1){ // 檢查添加是否成功perror("epoll_ctl"); // 輸出錯誤信息exit(-1); // 異常退出}//4.等待事件發生(客戶端連接、消息或輸入)while(1){ // 主循環,持續運行服務器//調用epoll_wait等待事件,超時時間2000ms(2秒)// 參數:epoll描述符、接收事件的數組、最大事件數、超時時間if((res = epoll_wait(epfd,evt_arr,FD_CNT,2000))<=0){printf("timeout!\n"); // 超時或出錯時輸出信息continue; // 繼續下一次循環}//處理活動的描述符(遍歷所有返回的事件)for(i=0;i<res;i++){//有鍵盤輸入(標準輸入有讀事件)if(evt_arr[i].data.fd==0 && (evt_arr[i].events&EPOLLIN)){memset(msg,0,sizeof(msg)); // 清空消息緩沖區read(0,msg,sizeof(msg)); // 從標準輸入讀取數據printf("輸入的內容為:%s\n",msg); // 輸出讀取到的內容}//1.有客戶端連接請求(監聽套接字有讀事件)if(evt_arr[i].data.fd==sockfd && (evt_arr[i].events&EPOLLIN)){struct sockaddr_in cilent_addr; // 存儲客戶端地址socklen_t len = sizeof(cilent_addr); // 地址長度// 接受客戶端連接,返回新的套接字描述符int newfd = accept(sockfd,(struct sockaddr *)&cilent_addr,&len);if(newfd==-1){ // 檢查連接是否成功perror("accept"); // 輸出錯誤信息exit(-1); // 異常退出}//將新連接的客戶端描述符添加到epoll監控evt.data.fd = newfd; // 綁定新客戶端套接字// 添加新客戶端到epoll監控列表res = epoll_ctl(epfd,EPOLL_CTL_ADD,newfd,&evt);if(res==-1){ // 檢查添加是否成功perror("epoll_ctl"); // 輸出錯誤信息exit(-1); // 異常退出}// 輸出客戶端IP地址,表示有新客戶端連接printf("%s到此一游!\n",inet_ntoa(cilent_addr.sin_addr));}//2.有客戶端發送消息(其他套接字有讀事件)// 排除標準輸入和監聽套接字,處理客戶端消息if(evt_arr[i].data.fd!=0 && evt_arr[i].data.fd!=sockfd && (evt_arr[i].events&EPOLLIN)){res = read(evt_arr[i].data.fd,msg,sizeof(msg)); // 從客戶端讀取消息if(res<=0){ // 如果讀取失敗或客戶端斷開連接// 客戶端斷開連接,將其從epoll監控中刪除res = epoll_ctl(epfd,EPOLL_CTL_DEL,evt_arr[i].data.fd,&evt_arr[i]);if(res==-1){ // 檢查刪除是否成功perror("epoll_ctl"); // 輸出錯誤信息}close(evt_arr[i].data.fd); // 關閉套接字continue; // 繼續下一次循環}//需要長時間通信可以開多任務printf("%s\n",msg); // 輸出接收到的消息if(strcmp(msg,"byebye")==0){ // 如果客戶端發送"byebye"// 客戶端主動斷開連接,從epoll監控中刪除res = epoll_ctl(epfd,EPOLL_CTL_DEL,evt_arr[i].data.fd,&evt_arr[i]);if(res==-1){ // 檢查刪除是否成功perror("epoll_ctl"); // 輸出錯誤信息}close(evt_arr[i].data.fd); // 關閉套接字continue; // 繼續下一次循環}//原路發回消息(回聲功能)write(evt_arr[i].data.fd,msg,res); // 將接收到的消息回發給客戶端}}}close(sockfd); // 關閉監聽套接字(實際不會執行到這里)return 0; // 程序正常退出(實際不會執行到這里)
}
程序功能說明:
這是一個基于?epoll
?實現的多路復用 TCP 服務器程序,相比?select
?和?poll
?具有更高的效率,主要功能如下:
核心功能
TCP 服務器基礎功能:
- 創建 TCP 套接字、設置地址復用、綁定端口、監聽連接
- 支持多客戶端并發連接,通過?
epoll
?機制實現高效 I/O 多路復用
多路復用監控:
- 使用?
epoll
?同時監控三類 I/O 事件:- 標準輸入(鍵盤輸入):讀取并顯示鍵盤輸入內容
- 服務器監聽套接字:接受新的客戶端連接請求
- 已連接客戶端套接字:接收客戶端消息并提供回聲服務(消息原路返回)
- 使用?
連接管理:
- 新客戶端連接時自動將其套接字添加到?
epoll
?監控列表 - 客戶端斷開連接(主動發送 "byebye" 或異常斷開)時,自動從?
epoll
?中移除并關閉套接字 - 最大支持 1000 個并發連接(由?
FD_CNT
?定義)
- 新客戶端連接時自動將其套接字添加到?
技術特點
epoll 機制優勢:
- 采用?
epoll_create
?創建專用描述符,epoll_ctl
?管理監控對象,epoll_wait
?等待事件 - 僅返回活動的文件描述符,無需遍歷所有監控對象,效率更高(尤其在高并發場景)
- 事件驅動模式,避免?
select/poll
?的性能瓶頸(如文件描述符數量限制、每次需重置集合)
- 采用?
事件處理流程:
- 主循環通過?
epoll_wait
?阻塞等待事件,超時時間設為 2 秒 - 收到事件后遍歷活動事件數組,區分不同類型事件(鍵盤輸入、新連接、客戶端消息)
- 針對不同事件類型執行對應處理邏輯,實現單進程高效處理多任務
- 主循環通過?
適用場景
- 高并發網絡服務器開發(相比?
select/poll
?更適合大量客戶端連接場景) - 演示 Linux 下高效 I/O 多路復用機制的實現
- 可作為回聲服務器、聊天服務器等基礎框架擴展
該程序展示了?epoll
?在網絡編程中的典型用法,是 Linux 平臺下高性能服務器的常用技術方案。