epoll多路復(fù)用的一個實例程序(C實現(xiàn))
本文實例為大家分享了epoll多路復(fù)用一個實例程序的具體代碼,供大家參考,具體內(nèi)容如下
1、實例程序描述
編寫一個echo server程序,功能是客戶端向服務(wù)端發(fā)送消息,服務(wù)端接收到消息后輸出,并原樣返回給客戶端,客戶端接收到服務(wù)端的應(yīng)答消息并打印輸出。
2、公共接口函數(shù)部分
2.1、common.h 源文件
/** **描述:公共頭文件 */ #include <stdio.h> #include <string.h> #include <stdlib.h> #include <errno.h> ? #include <fcntl.h> #include <unistd.h> #include <netdb.h> #include <arpa/inet.h> #include <netinet/in.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/epoll.h> ? typedef struct epoll_event EPOLL_EVENT_T; ? #define BUF_SIZE 2048 #define LISTENQ ?10 #define FD_SEZE ?1000 #define MAX_EVENTS 100 ? #define ERROR_SOCKET_SELECT ? -1 #define ERROR_SOCKET_TIMEOUT ?-2 #define ERROR_SOCKET_READ ? ? -3 #define ERROR_SOCKET_WRITE ? ?-4 #define ERROR_SOCKET_CLOSE ? ?-5 #define ERROR_SOCKET_CREATE ? -6 #define ERROR_SOCKET_BIND ? ? -7 #define ERROR_SOCKET_LISTEN ? -8 #define ERROR_SOCKET_CONNECT ?-9 ? #define ERROR_EPOLL_CREATE ? ?-10 #define ERROR_EPOLL_CTL_ADD ? -11 #define ERROR_EPOLL_CTL_DEL ? -12 #define ERROR_EPOLL_CTL_MOD ? -13 #define ERROR_ARGUMENT ? ? ? ? -999 ? int add_epoll_event(int epollfd, int fd, int events); int del_epoll_event(int epoll, int fd, int events); int mod_epoll_event(int epoll, int fd, int events); int make_socket_nonblock(int sock_fd); int listen_socket(char *ip, int port, int nonblock); int connect_socket(char *ip, int port,int nonblock);
2.2、common.c 源文件
/** **描述:公共函數(shù) */ #include "common.h" ? int add_epoll_event(int epollfd, int fd, int events) { ? ? EPOLL_EVENT_T ev; ? ? ev.events = events; ? ? ev.data.fd = fd; ? ? if(epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &ev) == -1){ ? ? ? ? perror("epoll_ctl_add"); ? ? ? ? return ERROR_EPOLL_CTL_ADD; ? ? } } ? int del_epoll_event(int epollfd, int fd, int events) { ? ? EPOLL_EVENT_T ev; ? ? ev.events = events; ? ? ev.data.fd = fd; ? ? if(epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, &ev) == -1){ ? ? ? ? perror("epoll_ctl_del"); ? ? ? ? return ERROR_EPOLL_CTL_ADD; ? ? } } ? int mod_epoll_event(int epollfd, int fd, int events) { ? ? EPOLL_EVENT_T ev; ? ? ev.events = events; ? ? ev.data.fd = fd; ? ? if(epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &ev) == -1){ ? ? ? ? perror("epoll_ctl_mod"); ? ? ? ? return ERROR_EPOLL_CTL_MOD; ? ? } } ? //設(shè)置socket為非阻塞模式函數(shù) int make_socket_nonblock(int sock_fd) { ? ? int flags; ? ?? ? ? if((flags = fcntl(sock_fd, F_GETFL, NULL)) < 0){ ? ? ? ? printf("get socket fd flags error:%d %s", errno, strerror(errno)); ? ? ? ? return -1; ? ? } ? ? if(fcntl(sock_fd, F_SETFL, flags | O_NONBLOCK) == -1){ ? ? ? ? printf("set socket non-block error:%d %s", errno, strerror(errno)); ? ? ? ? return -1; ? ? } ? ?? ? ? return 0; } ? int listen_socket(char *ip, int port, int nonblock) { ? ? int opt=1, sockfd; ? ? struct sockaddr_in svr_addr; ? ?? ? ? if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){ ? ? ? ? close(sockfd); ? ? ? ? return ERROR_SOCKET_CREATE; ? ? } ? ? if(nonblock) //設(shè)置socket為非阻塞模式 ? ? ? ? make_socket_nonblock(sockfd); ? ? ? //SO_REUSEADDR是讓端口釋放后立即就可以被再次使用 ? ? setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); ? ?? ? ? memset(&svr_addr, 0, sizeof(struct sockaddr_in)); ? ? svr_addr.sin_family = AF_INET; ? ? if(ip == NULL) ? ? ? ? svr_addr.sin_addr.s_addr = htonl(INADDR_ANY); ? ? else ? ? ? ? svr_addr.sin_addr.s_addr = inet_addr(ip); ? ? svr_addr.sin_port = htons(port); ? ?? ? ? if(bind(sockfd, (struct sockaddr*)&svr_addr, sizeof(struct sockaddr)) == -1){ ? ? ? ? close(sockfd); ? ? ? ? return ERROR_SOCKET_BIND; ? ? } ? ?? ? ? if(listen(sockfd, LISTENQ) == -1){ ? ? ? ? close(sockfd); ? ? ? ? return ERROR_SOCKET_LISTEN; ? ? } ? ?? ? ? return sockfd; } ? int connect_socket(char *ip, int port,int nonblock) { ? ? int sockfd; ? ? struct sockaddr_in svr_addr; ? ?? ? ? if(ip==NULL || strlen(ip)==0 || port<=0) ? ? ? ? return ERROR_ARGUMENT; ? ?? ? ? sockfd = socket(AF_INET, SOCK_STREAM, 0); ? ? if(sockfd < 0){ ? ? ? ? perror("socket"); ? ? ? ? return ERROR_SOCKET_CREATE; ? ? } ? ?? ? ? memset(&svr_addr, 0, sizeof(svr_addr)); ? ? svr_addr.sin_family = AF_INET; ? ? svr_addr.sin_addr.s_addr = inet_addr(ip); ? ? svr_addr.sin_port = htons(port); ? ? ? if(connect(sockfd, (struct sockaddr*)&svr_addr, sizeof(svr_addr)) == -1){ ? ? ? ? perror("connect"); ? ? ? ? close(sockfd); ? ? ? ? return ERROR_SOCKET_CONNECT; ? ? } ? ? if(nonblock) ? ? ? ? make_socket_nonblock(sockfd); ? ? ? return sockfd; }
3、服務(wù)端源文件 epoll_server.c
/** **程序描述:編寫一個echo server程序,功能是客戶端向服務(wù)器發(fā)送信息,服務(wù)器端接收數(shù)據(jù)后輸出并原樣返回給客戶端,客戶端接收到消息并輸出到終端。 */ #include "common.h" ? void do_epoll(int listen_fd); void handle_epoll_events(int epollfd, EPOLL_EVENT_T *events, int nfds, int listen_fd, char *buf); void on_accept(int epollfd, int listen_fd); void on_read(int epollfd, int fd, char *buf); void on_write(int epollfd, int fd, char *buf); ? int main(int argc, int *argv[]) { ? ? char svr_ip[32]={0}; ? ? int svr_port; ? ? int listen_fd, nonblock=0; ? ?? ? ? if(argc < 3){ ? ? ? ? printf("ERROR: too few command-line arguments\n"); ? ? ? ? printf("Usage: %s <svr_ip> <svr_port>\n", argv[0]); ? ? ? ? return -1; ? ? } ? ? strncpy(svr_ip, argv[1], strlen(argv[1])); ? ? svr_port = atoi(argv[2]); ? ?? ? ? listen_fd = listen_socket(svr_ip, svr_port, nonblock); ? ? if(listen_fd < 0){ ? ? ? ? printf("listen socket error:%d %s\n", errno, strerror(errno)); ? ? ? ? return -2; ? ? } ? ? printf("epoll_svr listen on[%s:%d] succ\n", svr_ip, svr_port); ? ? do_epoll(listen_fd); ? ?? ? ? printf("exit epoll_svr succ\n"); ? ? return 0; } ? void do_epoll(int listen_fd) { ? ? EPOLL_EVENT_T events[MAX_EVENTS]; ? ? int epollfd, conn_fd, nfds; ? ? char buf[BUF_SIZE]={0}; ? ? ?? ? ? //創(chuàng)建一個epoll文件描述符 ? ? epollfd = epoll_create(FD_SEZE); ? ? if(epollfd == -1){ ? ? ? ? perror("epoll_create"); ? ? ? ? close(listen_fd); ? ? ? ? return; ? ? } ? ? //添加監(jiān)聽描述符的讀事件 ? ? add_epoll_event(epollfd, listen_fd, EPOLLIN); ? ?? ? ? for(;;){ ? ? ? ? nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1); ? ? ? ? if(nfds == -1){ ? ? ? ? ? ? perror("epoll_wait"); ? ? ? ? ? ? close(listen_fd); ? ? ? ? ? ? break; ? ? ? ? } ? ? ? ? handle_epoll_events(epollfd, events, nfds, listen_fd, buf); ? ? } ? ? close(epollfd); } ? void handle_epoll_events(int epollfd, EPOLL_EVENT_T *events, int nfds, int sockfd, char *buf) { ? ? int i=0; ? ? int fd; ? ? ? //printf("nfds = %d\n", nfds); ? ? for(i=0; i < nfds; i++){ ? ? ? ? fd = events[i].data.fd; ? ? ? ? //根據(jù)文件描述符類型和事件類型進行相應(yīng)處理 ? ? ? ? if(fd == sockfd && (events[i].events & EPOLLIN)) ? ? ? ? ? ? on_accept(epollfd, sockfd); ? ? ? ? else if(events[i].events & EPOLLIN) ? ? ? ? ? ? on_read(epollfd, fd, buf); ? ? ? ? else if(events[i].events & EPOLLOUT) ? ? ? ? ? ? on_write(epollfd, fd, buf); ? ? } } ? void on_accept(int epollfd, int listen_fd) { ? ? int conn_fd; ? ? struct sockaddr_in cli_addr; ? ? socklen_t cli_addr_len=sizeof(struct sockaddr); ? ?? ? ? conn_fd = accept(listen_fd, (struct sockaddr*)&cli_addr, &cli_addr_len); ? ? if(conn_fd == -1){ ? ? ? ? perror("accept"); ? ? } ? ? else{ ? ? ? ? printf("accept a new client:[%s:%d], conn_fd=%d\n", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port), conn_fd); ? ? ? ? //make_socket_nonblock(conn_fd); ? ? ? ? add_epoll_event(epollfd, conn_fd, EPOLLIN); //添加一個客戶端連接描述符的讀事件 ? ? } } ? void on_read(int epollfd, int fd, char *buf) { ? ? int nread; ? ?? ? ? nread = read(fd, buf, BUF_SIZE); ? ? if(nread == -1){ ? ? ? ? perror("read"); ? ? ? ? close(fd); ? ? ? ? del_epoll_event(epollfd, fd, EPOLLIN); ? ? } ? ? else if(nread == 0){ ? ? ? ? printf("ERROR:client close\n"); ? ? ? ? close(fd); ? ? ? ? del_epoll_event(epollfd, fd, EPOLLIN); ? ? } ? ? else{ ? ? ? ? printf("recv req msg from client succ, fd=%d, msg:%s", fd, buf); ? ? ? ? //修改描述符對應(yīng)的事件,由讀改為寫 ? ? ? ? mod_epoll_event(epollfd, fd, EPOLLOUT); ? ? } } ? void on_write(int epollfd, int fd, char *buf) { ? ? int nwrite; ? ?? ? ? nwrite=write(fd, buf, strlen(buf)); ? ? if(nwrite == -1){ ? ? ? ? perror("write"); ? ? ? ? close(fd); ? ? ? ? del_epoll_event(epollfd, fd, EPOLLOUT); ? ? } ? ? else{ ? ? ? ? printf("send resp msg to client succ, fd=%d, msg:%s\n", fd, buf); ? ? ? ? mod_epoll_event(epollfd, fd, EPOLLIN); //將描述符對應(yīng)的事件,由寫改為讀 ? ? } ? ? memset(buf, 0, sizeof(buf)); }
4、客戶端源文件 epoll_client.c
/** **程序描述:回射程序echo客戶端??蛻舳艘惨褂胑poll實現(xiàn)對路復(fù)用,控制STDIN_FILENO、STDOUT_FILENO和sockfd這三個描述符對應(yīng)的事件。 STDIN_FILENO:標(biāo)準(zhǔn)輸入描述符,只有一個讀事件 STDOUT_FILENO:標(biāo)準(zhǔn)輸出描述符,只有一個寫事件 sockfd: 有兩個事件,一個是讀事件,即讀取從服務(wù)端發(fā)送來的數(shù)據(jù);另一個是寫事件,即發(fā)送數(shù)據(jù)給服務(wù)端。需要注意的是,在同一時刻,它只能有一個事件 */ #include "common.h" ? void do_epoll(int sockfd); void handle_epoll_events(int epollfd, EPOLL_EVENT_T *events, int nfds, int sockfd, char *buf); void on_read(int epollfd, int fd, int sockfd, char *buf); void on_write(int epollfd, int fd, int sockfd, char *buf); ? int main(int argc, char *argv[]) { ? ? char svr_ip[32]={0}; ? ? int svr_port; ? ? int conn_fd, nonblock=0; ? ?? ? ?? ? ? if(argc < 3){ ? ? ? ? printf("ERROR: too few command-line arguments\n"); ? ? ? ? printf("Usage: %s <svr_ip> <svr_port>\n", argv[0]); ? ? ? ? return -1; ? ? } ? ? strncpy(svr_ip, argv[1], strlen(argv[1])); ? ? svr_port = atoi(argv[2]); ? ?? ? ? conn_fd=connect_socket(svr_ip, svr_port, nonblock); ? ? if(conn_fd < 0){ ? ? ? ? printf("connect to server failed!\n", conn_fd); ? ? ? ? return -2; ? ? } ? ? printf("connect to server succ, sockfd=%d\n", conn_fd); ? ? do_epoll(conn_fd); ? ?? ? ? printf("exit epoll_cli succ\n"); ? ? return 0; } ? void do_epoll(int sockfd) { ? ? int epollfd; ? ? EPOLL_EVENT_T events[MAX_EVENTS]; ? ? int nfds; ? ? char buf[BUF_SIZE]={0}; ? ? ? epollfd = epoll_create(FD_SEZE); ? ? add_epoll_event(epollfd, STDIN_FILENO, EPOLLIN); //注冊標(biāo)準(zhǔn)輸入讀事件 ? ? for(;;){ ? ? ? ? nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1); ? ? ? ? if(nfds == -1){ ? ? ? ? ? ? perror("epoll_wait"); ? ? ? ? ? ? close(sockfd); ? ? ? ? ? ? break; ? ? ? ? } ? ? ? ? handle_epoll_events(epollfd, events, nfds, sockfd, buf); ? ? } ? ? close(epollfd); } ? void handle_epoll_events(int epollfd, EPOLL_EVENT_T *events, int nfds, int sockfd, char *buf) { ? ? int i, fd; ? ? ? //printf("nfds = %d\n", nfds); ? ? for(i=0; i<nfds; i++){ ? ? ? ? fd = events[i].data.fd; ? ? ? ? //根據(jù)文件描述符類型和事件類型進行相應(yīng)處理 ? ? ? ? if(events[i].events & EPOLLIN) ? ? ? ? ? ? on_read(epollfd, fd, sockfd, buf); ? ? ? ? else if(events[i].events & EPOLLOUT) ? ? ? ? ? ? on_write(epollfd, fd, sockfd, buf); ? ? } } ? void on_read(int epollfd, int fd, int sockfd, char *buf) { ? ? int nread; ? ?? ? ? nread = read(fd, buf, BUF_SIZE); ? ? if(nread < 0){ ? ? ? ? perror("read error"); ? ? ? ? close(fd); ? ? } ? ? else if(nread == 0){ ? ? ? ? printf("epoll_svr close.\n"); ? ? ? ? close(fd); ? ? ? ? exit(-1); ? ? } ? ? else{ ? ? ? ? if(fd == STDIN_FILENO){ //標(biāo)準(zhǔn)輸入描述符的讀事件 ? ? ? ? ? ? printf("stdin buf=%s", buf); ? ? ? ? ? ? add_epoll_event(epollfd, sockfd, EPOLLOUT); //注冊sockfd的寫事件 ? ? ? ? } ? ? ? ? else{//sockfd描述符的讀事件 ? ? ? ? ? ? del_epoll_event(epollfd, sockfd, EPOLLIN); //刪除當(dāng)前sockfd描述符的讀事件 ? ? ? ? ? ? add_epoll_event(epollfd, STDOUT_FILENO, EPOLLOUT); //注冊標(biāo)準(zhǔn)輸出寫事件 ? ? ? ? } ? ? } } ? void on_write(int epollfd, int fd, int sockfd, char *buf) { ? ? int nwrite; ? ?? ? ? nwrite = write(fd, buf, strlen(buf)); ? ? if(nwrite < 0){ ? ? ? ? perror("write error"); ? ? ? ? close(fd); ? ? } ? ? else{ ? ? ? ? if(fd == STDOUT_FILENO) //標(biāo)準(zhǔn)輸出描述符的寫事件 ? ? ? ? { ? ? ? ? ? ? printf("recv resp_msg from svr, msg=%s\n", buf); ? ? ? ? ? ? del_epoll_event(epollfd, fd, EPOLLOUT); //刪除輸出描述符的寫事件 ? ? ? ? } ? ? ? ? else //sockfd描述符的寫事件 ? ? ? ? { ? ? ? ? ? ? printf("send req_msg to svr succ, msg=%s", buf); ? ? ? ? ? ? mod_epoll_event(epollfd, fd, EPOLLIN); //修改sockfd描述符為讀事件 ? ? ? ? } ? ? } ? ? memset(buf, 0, BUF_SIZE); }
5、Makefile文件
#第1種方式 ? all: epoll_server epoll_client ? epoll_server: epoll_server.o common.o ? ? $(LINK) epoll_client: epoll_client.o common.o ? ? $(LINK) ? %.o: %.c ? ? $(COMPILE) ? #compile & link CFLAGS += -g COMPILE=gcc -c -g -std=gnu99 -o $@ $< LINK=gcc -g -o $@ $^ ? clean: ? ? rm -rf *.o epoll_server epoll_client
6、總結(jié)分析
6.1 客戶端程序分析
1、對于客戶端程序而言,我們監(jiān)聽3個文件描述符,分別是連接服務(wù)端的sockfd,標(biāo)準(zhǔn)輸入描述符STDIN_FILENO 以及 標(biāo)準(zhǔn)輸出描述符STDOUT_FILENO。在 do_poll函數(shù)中,我們首先注冊了標(biāo)準(zhǔn)輸入描述符的讀事件(EPOLLIN),然后在for循環(huán)中,循環(huán)調(diào)用epoll_wait系統(tǒng)調(diào)用,handle_epoll_events是整個epoll事件表的handler函數(shù)。當(dāng)我們向終端輸入數(shù)據(jù)完畢的時候,就會觸發(fā)標(biāo)準(zhǔn)輸入描述符的讀事件,從而調(diào)用讀事件處理函數(shù)on_read,在on_read函數(shù)中,注冊了sockfd的寫事件。
2、在do_poll的for循環(huán)中,繼續(xù)調(diào)用epoll_wait 和 handle_epoll_events,發(fā)現(xiàn)sockfd的寫事件已就緒,轉(zhuǎn)向調(diào)用寫事件處理函數(shù)on_write。在on_write函數(shù)中,發(fā)送消息給服務(wù)端,然后修改sockfd描述符的讀事件就緒,準(zhǔn)備接收服務(wù)端發(fā)來的應(yīng)答消息。
3、在do_poll的for循環(huán)中,繼續(xù)調(diào)用epoll_wait 和 handle_epoll_events,發(fā)現(xiàn)sockfd的讀事件已就緒,轉(zhuǎn)向調(diào)用寫事件處理函數(shù)on_write。在on_read函數(shù)中,刪除當(dāng)前sockfd描述符的讀事件并注冊標(biāo)準(zhǔn)輸出描述符的寫事件。之所以要刪除掉sockfd的讀事件,是避免其一直處于就緒狀態(tài)。
4、在do_poll的for循環(huán)中,繼續(xù)調(diào)用epoll_wait 和 handle_epoll_events,發(fā)現(xiàn)標(biāo)準(zhǔn)輸出描述符的寫事件已就緒,轉(zhuǎn)向調(diào)用寫事件處理函數(shù)on_write。在on_write函數(shù)中,輸出服務(wù)端發(fā)來的應(yīng)答消息并刪除標(biāo)準(zhǔn)輸出描述符的寫事件。之所以要刪除掉標(biāo)準(zhǔn)輸出描述符的寫事件,還是為了避免其一直處于就緒狀態(tài),具體表現(xiàn)就是不停地在終端打印信息。
6.2 服務(wù)端程序分析
1、對于服務(wù)端程序而言,我們監(jiān)聽2個文件描述符,分別是接受多個客戶端連接請求的listen_fd和處理單個客戶端的數(shù)據(jù)讀寫的conn_fd。
2、在main函數(shù)中,首先創(chuàng)建了監(jiān)聽連接請求的listen_fd文件描述符。然后在do_poll函數(shù)中,首先注冊了listen_fd描述符的讀事件(EPOLLIN)。在for循環(huán)中,循環(huán)調(diào)用epoll_wait系統(tǒng)調(diào)用和epoll事件handler函數(shù)handle_epoll_events。
3、當(dāng)有客戶端發(fā)起連接請求時,會觸發(fā)listen_fd描述符的讀事件,轉(zhuǎn)向執(zhí)行l(wèi)isten_fd的讀事件處理函數(shù)on_accept。在on_accept函數(shù)中,客戶端與服務(wù)端成功建立連接,并返回一個conn_fd文件描述符,然后注冊這個描述符的讀事件。
4、在do_poll的for循環(huán)中,繼續(xù)調(diào)用epoll_wait 和 handle_epoll_events,當(dāng)客戶端有數(shù)據(jù)發(fā)送給服務(wù)端時,觸發(fā)conn_fd的讀事件,轉(zhuǎn)向執(zhí)行on_read函數(shù)。在on_read函數(shù)中,接收客戶端發(fā)來的消息并在終端輸出,然后修改conn_fd描述符為寫事件就緒。如果接收到的數(shù)據(jù)大小為0,則說明連接已經(jīng)斷開,則關(guān)閉conn_fd描述符并刪除其讀事件。
5、在do_poll的for循環(huán)中,繼續(xù)調(diào)用epoll_wait 和 handle_epoll_events,發(fā)現(xiàn)conn_fd的寫事件就緒,轉(zhuǎn)向執(zhí)行on_write函數(shù)。在on_write函數(shù)中,發(fā)送應(yīng)答消息給客戶端,并修改conn_fd描述符的讀事件就緒。
題外話
本epoll實例程序,本人已經(jīng)在CentOS 7.6系統(tǒng)下測試通過了。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。