代码
#include <stdio.h>#include <sys/types.h>#include <sys/socket.h>#include <netdb.h>#include <stdlib.h>#include <unistd.h>#include <arpa/inet.h>#include <netinet/in.h>#include <errno.h>#include <string.h>#include <ctype.h>#include <sys/epoll.h>int main(){int lfd = socket(AF_INET, SOCK_STREAM, 0);if (lfd == -1){perror("socket error");exit(1);}//设置端口复用int opt = 1;setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, (const void *)&opt, sizeof(opt));struct sockaddr_in serv_addr;memset(&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_port = htons(9317);serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);int ret = bind(lfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));if (ret == -1){perror("bind error");exit(1);}ret = listen(lfd, 64);if (ret == -1){perror("listen error");exit(1);}struct sockaddr_in client_addr;socklen_t cli_addr_len = sizeof(struct sockaddr_in);//struct epoll_event events[2000];// 创建epoll实例int epfd = epoll_create(2000);//把监听事件挂到epoll红黑树上struct epoll_event event_l;event_l.events = EPOLLIN;event_l.data.fd = lfd;if ((epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &event_l)) < 0){fprintf(stderr, "epoll ctl error: %s\n", strerror(errno));exit(1);}while (1){// 有事件发送的epoll_event结构体会复制到events上,ret为事件总数int ret = epoll_wait(epfd, events, sizeof(events) / sizeof(events[0]), -1);if (ret < 0){fprintf(stderr, "epoll wait error: %s\n", strerror(errno));exit(1);}int k;for (k = 0; k < ret; k++){// 读事件if (events[k].events & EPOLLIN){// tfd: this fdint tfd = events[k].data.fd;if (tfd == lfd){// 处理监听套接字的事件int cfd = accept(lfd, (struct sockaddr *)&client_addr, &cli_addr_len);if (cfd < 0){fprintf(stderr, "accept error: %s\n", strerror(errno));}// 把客户端fd挂到epoll红黑树上struct epoll_event tmp;tmp.events = EPOLLIN;tmp.data.fd = cfd;if ((epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &tmp)) < 0){fprintf(stderr, "epoll ctl error: %s\n", strerror(errno));exit(1);}}else{// 客户端fd的事件char buff[1024];int rec_len, i;if ((rec_len = recv(tfd, buff, sizeof(buff), 0)) < 0){fprintf(stderr, "read error: %s\n", strerror(errno));exit(1);}else if (rec_len == 0){// 客户端关闭连接if ((epoll_ctl(epfd, EPOLL_CTL_DEL, tfd, NULL)) < 0){fprintf(stderr, "epoll ctl error: %s\n", strerror(errno));exit(1);}close(tfd);}else{printf("%s", buff);for (i = 0; i < rec_len; ++i){buff[i] = toupper(buff[i]);}send(tfd, buff, rec_len, 0);}}}}}close(epfd);close(lfd);return 0;}
epoll的一点小问题
如果某个fd被关闭,并且这次关闭是真正地释放了资源,而不是减少引用技术,那么这个fd会自动从eopll中删除,这时再调用epoll_ctl(EPOLL_CTL_DEL)可能会报错。
Q6 Will closing a file descriptor cause it to be removed from all<br /> epoll sets automatically?
A6 Yes, but be aware of the following point. A file descriptor is a<br /> reference to an open file description (see [open(2)](http://man.he.net/man2/open)). Whenever a<br /> descriptor is duplicated via [dup(2)](http://man.he.net/man2/dup), [dup2(2)](http://man.he.net/man2/dup2), [fcntl(2)](http://man.he.net/man2/fcntl) F_DUPFD, or<br /> [fork(2)](http://man.he.net/man2/fork), a new file descriptor referring to the same open file<br /> description is created. An open file description continues to<br /> exist until all file descriptors referring to it have been closed.<br /> A file descriptor is removed from an epoll set only after all the<br /> file descriptors referring to the underlying open file description<br /> have been closed (or before if the descriptor is explicitly removed<br /> using [epoll_ctl(2)](http://man.he.net/man2/epoll_ctl) EPOLL_CTL_DEL). This means that even after a<br /> file descriptor that is part of an epoll set has been closed,<br /> events may be reported for that file descriptor if other file<br /> descriptors referring to the same underlying file description<br /> remain open.
