IO多路复用--select和epoll详解
来源:互联网 发布:淘宝怎么联系不了卖家 编辑:程序博客网 时间:2024/05/24 02:16
1.基本概念
IO多路复用是指在一个进程或线程中监控多个文件描述符,可以用select和epoll函数实现
select函数声明:
int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
int epoll_create(int size);//创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
2.select与epoll比较
select最多能同时监控1024个文件描述符
epoll可监控的文件描述符个数没有限制
epoll通过回调函数的机制,可以不用遍历所有的文件描述符
epoll通过mmap内存映射机制,可以不用在用户空间和内核空间来拷贝文件描述符
3.示例代码(代码来自网上的博客)
单线程select服务器代码:
#include <stdio.h>#include <string.h>#include <arpa/inet.h>#include <netinet/in.h>#include <sys/socket.h>#include <sys/select.h>const static int MAXLINE = 1024;const static int SERV_PORT = 10001;int main(){ int i , maxi , maxfd, listenfd , connfd , sockfd ; /*nready 描述字的数量*/ int nready ,client[FD_SETSIZE]; int n ; /*创建描述字集合,由于select函数会把未有事件发生的描述字清零,所以我们设置两个集合*/ fd_set rset , allset; char buf[MAXLINE]; socklen_t clilen; struct sockaddr_in cliaddr , servaddr; /*创建socket*/ listenfd = socket(AF_INET , SOCK_STREAM , 0); /*定义sockaddr_in*/ memset(&servaddr , 0 ,sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = htons(SERV_PORT); servaddr.sin_addr.s_addr = htonl(INADDR_ANY); bind(listenfd, (struct sockaddr *) & servaddr , sizeof(servaddr)); listen(listenfd , 100); /*listenfd 是第一个描述字*/ /*最大的描述字,用于select函数的第一个参数*/ maxfd = listenfd; /*client的数量,用于轮询*/ maxi = -1; /*init*/ for(i=0 ;i<FD_SETSIZE ; i++) client[i] = -1; FD_ZERO(&allset); FD_SET(listenfd, &allset); for (;;) { rset = allset; /*只select出用于读的描述字,阻塞无timeout*/ nready = select(maxfd+1 , &rset , NULL , NULL , NULL); if(FD_ISSET(listenfd,&rset)) { clilen = sizeof(cliaddr); connfd = accept(listenfd , (struct sockaddr *) & cliaddr , &clilen); /*寻找第一个能放置新的描述字的位置*/ for (i=0;i<FD_SETSIZE;i++) { if(client[i]<0) { client[i] = connfd; break; } } /*找不到,说明client已经满了*/ if(i==FD_SETSIZE) { printf("Too many clients , over stack .\n"); return -1; } FD_SET(connfd,&allset);//设置fd /*更新相关参数*/ if(connfd > maxfd) maxfd = connfd; if(i>maxi) maxi = i; if(nready<=1) continue; else nready --; } for(i=0 ; i<=maxi ; i++) { if (client[i]<0) continue; sockfd = client[i]; if(FD_ISSET(sockfd,&rset)) { n = read(sockfd , buf , MAXLINE); if (n==0) { /*当对方关闭的时候,server关闭描述字,并将set的sockfd清空*/ close(sockfd); FD_CLR(sockfd,&allset); client[i] = -1; } else { buf[n]='\0'; printf("Socket %d said : %s\n",sockfd,buf); write(sockfd,buf,n); //Write back to client } nready --; if(nready<=0) break; } } } return 0;}
单线程epoll服务器代码:
#include <stdio.h>#include <sys/epoll.h>#include <sys/socket.h>#include <sys/types.h>#include <netinet/in.h>#include <arpa/inet.h>#include <fcntl.h>#include <unistd.h>#include <netdb.h>#include <errno.h>#define MAX_EVENT 20#define READ_BUF_LEN 256/** * 设置 file describe 为非阻塞模式 * @param fd 文件描述 * @return 返回0成功,返回-1失败 */static int make_socket_non_blocking (int fd) { int flags, s; // 获取当前flag flags = fcntl(fd, F_GETFL, 0); if (-1 == flags) { perror("Get fd status"); return -1; } flags |= O_NONBLOCK; // 设置flag s = fcntl(fd, F_SETFL, flags); if (-1 == s) { perror("Set fd status"); return -1; } return 0;}int main() { // epoll 实例 file describe int epfd = 0; int listenfd = 0; int result = 0; struct epoll_event ev, event[MAX_EVENT]; // 绑定的地址 const char * const local_addr = "127.0.0.1"; struct sockaddr_in server_addr = { 0 }; listenfd = socket(AF_INET, SOCK_STREAM, 0); if (-1 == listenfd) { perror("Open listen socket"); return -1; } /* Enable address reuse */ int on = 1; // 打开 socket 端口复用, 防止测试的时候出现 Address already in use result = setsockopt( listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on) ); if (-1 == result) { perror ("Set socket"); return 0; } server_addr.sin_family = AF_INET; inet_aton (local_addr, &(server_addr.sin_addr)); server_addr.sin_port = htons(10001); result = bind(listenfd, (const struct sockaddr *)&server_addr, sizeof (server_addr)); if (-1 == result) { perror("Bind port"); return 0; } result = make_socket_non_blocking(listenfd); if (-1 == result) { return 0; } result = listen(listenfd, 200); if (-1 == result) { perror("Start listen"); return 0; } // 创建epoll实例 epfd = epoll_create1(0); if (1 == epfd) { perror("Create epoll instance"); return 0; } ev.data.fd = listenfd; ev.events = EPOLLIN | EPOLLET /* 边缘触发选项。 */; // 设置epoll的事件 result = epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev); if(-1 == result) { perror("Set epoll_ctl"); return 0; } for ( ; ; ) { int wait_count; // 等待事件 wait_count = epoll_wait(epfd, event, MAX_EVENT, -1); int i=0; for ( i = 0 ; i < wait_count; i++) { uint32_t events = event[i].events; // IP地址缓存 char host_buf[NI_MAXHOST]; // PORT缓存 char port_buf[NI_MAXSERV]; int __result; // 判断epoll是否发生错误 if ( events & EPOLLERR || events & EPOLLHUP || (! events & EPOLLIN)) { printf("Epoll has error\n"); close (event[i].data.fd); continue; } else if (listenfd == event[i].data.fd) { // listen的 file describe 事件触发, accpet事件 for ( ; ; ) { // 由于采用了边缘触发模式,这里需要使用循环 struct sockaddr in_addr = { 0 }; socklen_t in_addr_len = sizeof (in_addr); int accp_fd = accept(listenfd, &in_addr, &in_addr_len); if (-1 == accp_fd) { perror("Accept"); break; } __result = getnameinfo(&in_addr, sizeof (in_addr), host_buf, sizeof (host_buf) / sizeof (host_buf[0]), port_buf, sizeof (port_buf) / sizeof (port_buf[0]), NI_NUMERICHOST | NI_NUMERICSERV); if (! __result) { printf("New connection: host = %s, port = %s\n", host_buf, port_buf); } __result = make_socket_non_blocking(accp_fd); if (-1 == __result) { return 0; } ev.data.fd = accp_fd; ev.events = EPOLLIN | EPOLLET; // 为新accept的 file describe 设置epoll事件 __result = epoll_ctl(epfd, EPOLL_CTL_ADD, accp_fd, &ev); if (-1 == __result) { perror("epoll_ctl"); return 0; } } continue; } else { // 其余事件为 file describe 可以读取 int done = 0; // 因为采用边缘触发,所以这里需要使用循环。如果不使用循环,程序并不能完全读取到缓存区里面的数据。 for ( ; ;) { ssize_t result_len = 0; char buf[READ_BUF_LEN] = { 0 }; result_len = read(event[i].data.fd, buf, sizeof (buf) / sizeof (buf[0])); if (-1 == result_len) { if (EAGAIN != errno) { perror ("Read data"); done = 1; } break; } else if (! result_len) { done = 1; break; } write(STDOUT_FILENO, buf, result_len); } if (done) { printf("Closed connection\n"); close (event[i].data.fd); } } } } close (epfd); return 0;}
客户端代码:
#include <stdio.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <netdb.h>int main(int argc, char *argv[]){ int sockfd; int n; struct sockaddr_in serv_addr; char buffer[256]; sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { printf("opening socket error! \n"); exit(-1); } bzero(&serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_port = htons(10001); serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); if (connect(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) { printf("connecting error! \n"); exit(-1); } printf("Please enter the message: "); bzero(buffer, 256); fgets(buffer, 255, stdin); n = write(sockfd, buffer, strlen(buffer)); if (n < 0) printf("error writing to socket! \n"); bzero(buffer, 256); n = read(sockfd, buffer, 255); if (n < 0) printf("error reading from socket! \n"); printf("%s \n",buffer); return 0;}
阅读全文
1 0
- IO多路复用--select和epoll详解
- IO多路复用之select、poll、epoll详解
- IO 多路复用之 select、poll、epoll 详解
- IO多路复用之select、poll、epoll详解
- IO多路复用之select、poll、epoll详解
- IO多路复用之select、poll、epoll详解
- IO多路复用之select、poll、epoll详解
- IO多路复用之select、poll、epoll详解
- IO多路复用(select, epoll)
- IO多路复用(select poll epoll)
- IO多路复用:select、poll、epoll
- 聊聊IO多路复用之select、poll、epoll详解
- 聊聊IO多路复用之select、poll、epoll详解
- 聊聊IO多路复用之select、poll、epoll详解
- 聊聊IO多路复用之select、poll、epoll详解
- 聊聊IO多路复用之select、poll、epoll详解
- 服务器基础:IO多路复用之select、poll、epoll详解
- IO多路复用之select和epoll(详细注释)
- logistic回归
- 浮点数在内存中的表示移位存储难点的理解
- TensorFlow使用PyCharm编辑器进行配置和编译在Windows下
- 集合知识点归纳
- JDBC了解(3)-beautils
- IO多路复用--select和epoll详解
- 一句总结设计模式
- C#学习笔记:动态设置控件的背景图片
- Hibernate入门03_单向多对一
- 物理学史二十讲
- [一天几个linux命令] 目录与文件的权限意义
- linux安装apache
- 编程书籍推荐
- 学习淘淘商城第七十九课(LVS+Nginx负载均衡----下篇)