echo_epollserv.c
//使用epoll函数实现IO复用回声服务器 //epoll函数有以下优点: //1.无需编写针对所有描述符的循环语句 //2.调用epoll_wait函数时无需每次传递监视对象信息 //epoll_create:创建保存epoll文件描述符的空间 //epoll_ctl:向空间注册并注销文件描述符 //epoll_wait:等待文件描述符发生变化 //epoll默认以条件触发方式运行,只要输入缓冲有数据就会一直通知该事件 //可以看到只要输入缓冲有数据,程序就会一直输出"return epoll_wait" #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <sys/time.h> #include <sys/select.h> #include <sys/epoll.h> #define BUF_SIZE 4 #define EPOLL_SIZE 50 void error_handling(char *message); int main(int argc, char *argv[]) { int serv_sock; int clnt_sock; struct sockaddr_in serv_addr; struct sockaddr_in clnt_addr; socklen_t clnt_addr_size; int str_len; char buf[BUF_SIZE]; struct epoll_event *ep_events; struct epoll_event event; int epfd, event_cnt; if(argc!=2) { exit(1); } //TCP socket serv_sock=socket(PF_INET, SOCK_STREAM, 0); if(serv_sock == -1) error_handling("socket error!"); memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; //IPV4协议族 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); //主机字节序(host)转换成网络字节序(net)(大端序) serv_addr.sin_port = htons(atoi(argv[1])); //端口号 if(bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1) error_handling("bind error"); if(listen(serv_sock, 5) == -1) error_handling("listen error"); epfd=epoll_create(EPOLL_SIZE); ep_events=malloc(sizeof(struct epoll_event)*EPOLL_SIZE); //申请内存空间用于保存文件描述符 event.events=EPOLLIN; event.data.fd=serv_sock; epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event); //注册文件描述符 while(1) { //监控文件描述符,发生变化的描述符会存到ep_events event_cnt=epoll_wait(epfd, ep_events, EPOLL_SIZE, -1); if(event_cnt==-1) { puts("epoll_wait error"); break; } puts("return epoll_wait"); //验证条件触发方式 int i; for(i=0;i<event_cnt;i++) { if(ep_events[i].data.fd==serv_sock) //连接请求 { clnt_addr_size=sizeof(clnt_addr); clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size); event.events=EPOLLIN; event.data.fd=clnt_sock; epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event); printf("connected client: %d \n", clnt_sock); } else { str_len=read(ep_events[i].data.fd, buf, BUF_SIZE); if(str_len==0) //关闭请求 { epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL); //删除文件描述符 close(ep_events[i].data.fd); printf("close client: %d \n", ep_events[i].data.fd); } else { write(ep_events[i].data.fd, buf, str_len); } } } } close(serv_sock); close(epfd); return 0; } void error_handling(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); }
条件触发和边缘触发
echo_EPLTserv.c
//条件触发:只要输入缓冲有数据就会一直通知该事件 //边缘触发:输入缓冲收到数据时仅注册一次该事件 //select模型以条件触发方式工作 //边缘触发的优点:可以分离接收数据和处理数据的时间点 //而条件触发在输入缓冲收到数据的情况下,如果不读取,则每次调用epoll_wait函数都会 //触发事件,而且事件数也会累加,服务器无法承受 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/socket.h> #include <sys/time.h> #include <sys/select.h> #include <sys/epoll.h> #include <fcntl.h> #include <errno.h> #define BUF_SIZE 4 #define EPOLL_SIZE 50 void error_handling(char *message); void setnonblockingmode(int fd); int main(int argc, char *argv[]) { int serv_sock; int clnt_sock; struct sockaddr_in serv_addr; struct sockaddr_in clnt_addr; socklen_t clnt_addr_size; int str_len; char buf[BUF_SIZE]; struct epoll_event *ep_events; struct epoll_event event; int epfd, event_cnt; if(argc!=2) { exit(1); } //TCP socket serv_sock=socket(PF_INET, SOCK_STREAM, 0); if(serv_sock == -1) error_handling("socket error!"); memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; //IPV4协议族 serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); //主机字节序(host)转换成网络字节序(net)(大端序) serv_addr.sin_port = htons(atoi(argv[1])); //端口号 if(bind(serv_sock, (struct sockaddr*) &serv_addr, sizeof(serv_addr)) == -1) error_handling("bind error"); if(listen(serv_sock, 5) == -1) error_handling("listen error"); epfd=epoll_create(EPOLL_SIZE); ep_events=malloc(sizeof(struct epoll_event)*EPOLL_SIZE); //申请内存空间用于保存文件描述符 setnonblockingmode(serv_sock); event.events=EPOLLIN; event.data.fd=serv_sock; epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event); //注册文件描述符 while(1) { //监控文件描述符,发生变化的描述符会存到ep_events event_cnt=epoll_wait(epfd, ep_events, EPOLL_SIZE, -1); if(event_cnt==-1) { puts("epoll_wait error"); break; } puts("return epoll_wait"); int i; for(i=0;i<event_cnt;i++) { if(ep_events[i].data.fd==serv_sock) //连接请求 { clnt_addr_size=sizeof(clnt_addr); clnt_sock=accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size); setnonblockingmode(clnt_sock); event.events=EPOLLIN|EPOLLET; //改为边缘触发方式 event.data.fd=clnt_sock; epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event); printf("connected client: %d \n", clnt_sock); } else { //采用边缘触发方式要将输入缓冲中的数据一次性读取完毕 while(1) { str_len=read(ep_events[i].data.fd, buf, BUF_SIZE); if(str_len==0) //关闭请求 { epoll_ctl(epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL); //删除文件描述符 close(ep_events[i].data.fd); printf("close client: %d \n", ep_events[i].data.fd); } else if(str_len<0) { //read返回-1并且errno==EAGAIN时证明输入缓冲中没数据了,可以跳出循环 if(errno==EAGAIN) break; } else { write(ep_events[i].data.fd, buf, str_len); } } } } } close(serv_sock); close(epfd); return 0; } void error_handling(char *message) { fputs(message, stderr); fputc('\n', stderr); exit(1); } //将套接字改为非阻塞方式 //边缘触发方式下,以阻塞方式工作的read和write函数可能引起服务器端长时间停顿 void setnonblockingmode(int fd) { int flag=fcntl(fd, F_GETFL, 0); fcntl(fd, F_SETFL, flag|O_NONBLOCK); }