I/O多路复用-epoll
epoll与select和poll在使用和实现上有很大区别。首先,epoll使用一组函数来完成,而不是单独的一个函数;其次,epoll把用户关心的文件描述符上的事件放在内核里的一个事件表中,无需向select和poll那样每次调用都要重复传入文件描述符集合事件集。
IO多路复用epoll模型优缺点
思路:单进程调用epoll_wait()函数来处理多个连接请求
优点:单进程可执行同时处理多个网络连接请求,可以达到硬件上限,性能上由于只管理就绪连接,不需要轮询事件集合,因此性能大大提高
缺点:仅支持linux平台
函数
epoll_create()
size参数并不起作用,只是给内核一个提示,告诉它事件表需要多大。
epoll_ctl()
epfd:文件句柄,标识事件表
op:操作类型,
EPOLL_CTL_ADD:注册fd上的事件
EPOLL_CTL_MOD:修改fd上注册的事件
EPOLL_CTL_DEL:删除fd上注册的事件
event:指定事件,定义如下
常用events:
EPOLLIN:可读事件
EPOLLOUT:可写事件
EPOLLET:边缘事件
epoll_wait()
服务器端示例
这里使用epoll_data联合体中的fd, ptr的使用将在以后讲解。
ptr的使用链接:epoll_data.ptr
#include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/epoll.h> #define MAX_SIZE 1024 int main(int argc, char *argv[]) { int sockfd; int ret; struct sockaddr_in server_addr; struct sockaddr_in client_addr; int client_sockfd; sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0){ perror("socket error"); return -1; } server_addr.sin_family = AF_INET; server_addr.sin_port = htons(9000); server_addr.sin_addr.s_addr = inet_addr("192.168.121.128"); int len = sizeof(struct sockaddr_in); ret = bind(sockfd, (struct sockaddr*)&server_addr, len); if (ret == -1){ perror("bind error"); return -1; } ret = listen(sockfd, MAX_SIZE); if (ret < 0){ perror("listen error"); return -1; } int epfd, nfds = 0, i = 0; struct epoll_event ev, evs[MAX_SIZE]; //创建一个epoll监听集合 epfd = epoll_create(MAX_SIZE); ev.events = EPOLLIN; ev.data.fd = sockfd; //将监听socket描述符与定义好的事件添加到epoll监听集合中 if (epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &ev) < 0){ perror("epoll_ctl error"); close(sockfd); return -1; } char buff[1024] = {0}; while (1){ //epoll阻塞等待事件触发,当某个描述符触发事件,则将触发的事件写入到evs中 //并且返回触发事件的描述符个数nfds nfds = epoll_wait(epfd, evs, MAX_SIZE, 3000); if (nfds < 0){ //epoll等待出错 perror("epoll_wait error"); continue; }else if (nfds == 0){ //3000毫秒内没有任何描述符触发事件 printf("timeout\n"); continue; } //循环从evs中取出触发的事件 for (i = 0;i<nfds;i++){ //如果evs[i].data.fd == sockfd,代表客户端请求通道有客户端请求到来 //则接受客户端连接请求 if (evs[i].data.fd == sockfd){ client_sockfd = accept(sockfd, (struct sockaddr*)&client_addr, &len); if (client_sockfd < 0){ perror("new client error"); continue; } printf("new accept----ip:[%s],port:[%d]\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port)); ev.events = EPOLLIN; ev.data.fd = client_sockfd; //add event ret = epoll_ctl(epfd, EPOLL_CTL_ADD, client_sockfd, &ev); if (ret < 0){ perror("epoll_ctl error"); continue; } //EPOLLIN代表可读事件 }else if (evs[i].events & EPOLLIN){ //代表有其他数据传输,则接受数据 memset(buff, 0x00, 1024); ret = recv(evs[i].data.fd, buff, 1024, 0); if (ret <= 0){ //ret == 0代表对端断开了tcp连接 //ret < 0 出错 //EINTR被信号打断出错 //EAGAIN描述符没有准备好 if (errno == EAGAIN || errno == EINTR){ continue; } //除了上面2个错误之外则从监听集合中删除描述符并关闭socket epoll_ctl(epfd, EPOLL_CTL_DEL, evs[i].data.fd, &ev); close(evs[i].data.fd); } printf("%s--]\n", buff); } } } close(sockfd); return 0; }
客户端示例
#include <stdio.h> #include <unistd.h> #include <errno.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> int main(int argc, char *argv[]) { int sock_fd; struct sockaddr_in ser_addr; struct sockaddr_in cli_addr; char buff[1024] = {0}; //调用socket函数创建套接字 sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (sock_fd < 0){ perror("socket error!"); return -1; } //赋值发送地址信息,服务器端地址信息 ser_addr.sin_family = AF_INET; ser_addr.sin_port = htons(9000); ser_addr.sin_addr.s_addr = inet_addr("192.168.121.128"); cli_addr.sin_family = AF_INET; cli_addr.sin_port = htons(8000); cli_addr.sin_addr.s_addr = inet_addr("192.168.121.128"); int len = sizeof(struct sockaddr_in); int ret; //调用 bind 函数绑定一个发送地址信息 ret = bind(sock_fd, (struct sockaddr*)&cli_addr, len); if (ret < 0){ perror("bind error!!!"); return -1; } //调用 connect 函数连接服务器 ret = connect(sock_fd, (struct sockaddr*)&ser_addr, len); if (ret < 0){ perror("connect error!!!"); return -1; } int i = 0; while (1){ memset(buff, 0x00, 1024); sprintf(buff, "test-%d", i++); //连接成功后,调用 send 函数发送数据到服务器端 send(sock_fd, buff, 1024, 0); sleep(1); } //调用 close 函数关闭套接字 close(sock_fd); return 0; }