Linux网络编程(三)多路IO转接服务器
来源:互联网 发布:nginx 静态页面 编辑:程序博客网 时间:2024/06/06 00:32
一、select
采用的是集合的方式,将关心的事件放置集合队列(最多监听1024个)中,轮询访问(每次都会检测所有的句柄)拿到一个已就绪的就会返回,(内核态到用户态的切换来拿事件),内部再使用位运算,将可读,可写,异常三个事件分开来,
1、select能监听的文件描述符个数受限于FD_SETSIZE,一般为1024,单纯的改变进程打开的文件描述符个数,并不能改变select监听的文件个数。
2、解决了1024以下客户端使用select 是很合适的,select采用的是轮询模式,但如果连接客户过多,会大大降低服务器的响应效率。
3、循环次数过多,每次有一个文件描述符准备好就会返回,可能就会一直处于激活状态,因为有文件描述符的拷贝(每次都要扫描注册的文件描述符集合,将已准备好的文件描述符返回给用户),系统从内核态切换用户态的次数会过多,造成性能下降
int select(int nfds,fd_set* readfds,fd_set* writefds,fd_set* exceptfds,struct timeval* timeout);//nfds: 监控的文件描述符的个数//exceptfds 监控异常发生到达文件描述符集合//timeout:定时阻塞监控时间 1、null 永久等下去 2、设置timeval ,等待固定时间 3、将timeval中时间设置为0,检查描述字后立即返回,轮询
二、poll
poll 比select 能 好一点,也是在指定时间内轮询一定数量的文件描述符,以测试是否有文件描述符就绪
三、epoll
把用户关心的文件描述符直接放置内核里的一个事件表(红黑树)中,不会像select与poll那样,每次调用都要传入文件描述符集或事件集,epoll_wait()函数当检测到就绪事件时,会将已准备好的文件描述符拷贝到它第二个参数指向的数组(链表)中,系统只需要从数组中取事件即可。用户态与内核态切换的次数并不多,极大地提高了效率
三个系统调用int epoll_create(int 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); 首先要调用epoll_create建立一个epoll对象。参数size是内核保证能够正确处理的最大句柄数,多于这个最大数时内核可不保证效果。epoll_ctl可以操作上面建立的epoll,例如,将刚建立的socket加入到epoll中让其监控,或者把 epoll正在监控的某个socket句柄移出epoll,不再监控它等等。epoll_wait在调用时,在给定的timeout时间内,当在监控的所有句柄中有事件发生时,就返回用户态的进程。
#include <stdio.h>#include <sys/types.h>#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <string.h>#include <stdlib.h>#include <sys/epoll.h>#include <unistd.h>#define SIZE 64const char* msg = "HTTP/1.0 200 OK\r\n\r\n<html><h1>hello epoll!<h1></html>\r\n";static void Usage(const char* proc){ printf("Usage: \n\t %s [local_ip][local_port]\n\n",proc);}int startup(const char *ip,int port){ int sock = socket(AF_INET,SOCK_STREAM,0);//创建套结字 if(sock<0) { perror("socket"); exit(2); } //当服务器异常关闭时,清除套结字,就可再次重启 int opt = 1; setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(int)); //设置结构体,填充自己ip与端口号 struct sockaddr_in local; local.sin_family = AF_INET; local.sin_port = htons(port);//端口号的转换 local.sin_addr.s_addr = inet_addr(ip);//ip if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)//绑定套结字 { perror("bind"); exit(3); } if(listen(sock,10)<0)//监听队列,里面有10个文件描述符,返回已经准备好链接的那一个 { perror("listen"); exit(4); } return sock;}int main(int argc,char* argv[]){ if(argc!=3) { Usage(argv[0]); return 1; } int listen_sock = startup(argv[1],atoi(argv[2]));//把得到的套结字作为监听套结字 int epfd = epoll_create(256);//创建epoll模型 if(epfd < 0) { perror("epoll_create is failed\n"); return 5; } printf("listen_sock:%d\n",listen_sock); //设置结构体事件,并把事件与套结字放在就绪队列中 struct epoll_event ev; ev.events = EPOLLIN;//表示对应的文件描述符可以读 ev.data.fd = listen_sock; epoll_ctl(epfd,EPOLL_CTL_ADD,listen_sock,&ev);//事件注册函数,将监听套结字加入监听事件 int nums = -1; int timeout = 10000;//可设置-1阻塞0非阻塞 //struct timeval timeout = {1,0}; //设置为0,0 非阻塞状态 struct epoll_event revs[SIZE]; //#################################################################################################### //对已连接的客户端进行数据处理 while(1) { switch((nums = epoll_wait(epfd,revs,SIZE,timeout)))//监听并判断是否有文件描述符属性发生改变 //后三个参数为输出形,等文件描述符就位,返回0 -1 或着已就位的文件描述符的个数 { case 0: printf("timeout...\n");break; case -1:perror("epoll");break; default: { int i = 0; for(i=0;i<nums;i++) { int fd = revs[i].data.fd;//从就绪队列拿出已就绪好的fd if(fd == listen_sock && (revs[i].events &EPOLLIN))//检测监听套结字是否存在链接 { //listen socket ready! struct sockaddr_in client;// socklen_t len = sizeof(client); int rw_sock=accept(listen_sock,(struct sockaddr*)&client,&len) ;//客户端向服务器发出链接请求 //服务器利用accept()来接受请求,建立连接,并拿到客户端套结字 if(rw_sock <0) { perror("accept failed"); continue; } printf("get a new client :[%s:%d]\n",inet_ntoa(client.sin_addr),ntohs(client.sin_port));//输出客户端ip与端口号 ev.events = EPOLLIN;//设置文件描述符为可读 ev.data.fd = rw_sock;//监听此套结字 epoll_ctl(epfd,EPOLL_CTL_ADD,rw_sock,&ev);//将拿到的套结字加入监听事件 } else if(fd != listen_sock) // { if(revs[i].events & EPOLLIN)//有数据来临时,(接收客户端数据) { //read ready char buf[1024]; ssize_t s = read(fd,buf,sizeof(buf)-1); if(s>0) // read success { buf[s] = 0; printf("client# %s\n",buf);//输出读的数据 ev.events = EPOLLOUT;//重置事件为有数据要写 ev.data.fd = fd; epoll_ctl(epfd,EPOLL_CTL_MOD,fd,&ev); } else if(s ==0)//没有读到数据 { printf("client is quit!\n"); close(fd);//关闭文件描述符 epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL);//删除 } else { perror("read"); close(fd); epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL); } } else if(revs[i].events & EPOLLOUT)//有数据要写时 { //write write(fd,msg,strlen(msg));//写msg内容 close(fd);//关闭并删除 epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL); } } } } } } return 0; }
从上面的调用方式就可以看到epoll比select/poll的优越之处:因为后者每次调用时都要传递你所要监控的所有socket给select/poll系统调用,这意味着需要将用户态的socket列表copy到内核态,如果以万计的句柄会导致每次都要copy几十几百KB的内存到内核态,非常低效。而我们调用epoll_wait时就相当于以往调用select/poll,但是这时却不用传递socket句柄给内核,因为内核已经在epoll_ctl中拿到了要监控的句柄列表。所以,实际上在你调用epoll_create后,内核就已经在内核态开始准备帮你存储要监控的句柄了,每次调用epoll_ctl只是在往内核的数据结构里塞入新的socket句柄。当epoll_wait调用时,仅仅观察这个list链表里有没有数据即可。有数据就返回,没有数据就sleep,等到timeout时间到后即使链表没数据也返回。所以,epoll_wait非常高效。
epoll______LT(电平触发) 默认的,相当于高效的poll
在事件就绪时,epoll_wait()会通知你,你可以不对此事件作出反应。当下次再调用epoll_wait()时,当你没有对此事作出反应时,还用通知你
ET(边缘触发)epoll的高效工作模式,(需向epoll内核事件表中注册一fd上的EPOLLET事件)
当事件就绪,epoll_wait()通知你,你必须处理此事件,因为下次不会再通知你
三种I/O复用区别:
从原理上看:
select 与poll都是采用了轮询方式去访问文件描述符集,每次返回准备就绪的一个,时间复杂度为O(n),epoll_wait()采用回调方式,当检测到有就绪描述符时,会触发回调函数,将就绪描述符加载到就绪队列中,内核会在合适的时机将事件拷贝到用户空间,时间复杂度为O(1)
从内存消耗:
select/poll将所监控的文件描述符从用户态copy到内核态,切换频率高,效率低下
文件描述符上限
epoll几乎没有上限(与内存有关)
- Linux网络编程(三)多路IO转接服务器
- day12 多路IO转接服务器
- Linux c==网络编程、循环服务器、并发服务器、I/O多路转接 (23)
- Linux c==网络编程、循环服务器、并发服务器、I/O多路转接
- 【Linux网络编程】I/O多路转接之 epoll 高性能简洁http服务器模型
- Linux【网络编程】——I/O多路转接之epoll服务器
- Linux【网络编程】——I/O多路转接之Poll服务器
- Linux【网络编程】——I/O多路转接之Select服务器
- Linux网络编程(三) IO非阻塞操作
- Linux网络编程(三) IO非阻塞操作 .
- Linux网络编程( epoll函数的使用) 多路IO 高并发服务器
- Linux网络编程(三) TCP客户/服务器程序示例
- linux服务器开发三(网络编程)
- (五十三)高并发服务器——多路IO转接机制Select模型
- (五十四)高并发服务器——多路IO转接机制poll模型
- (五十五)高并发服务器——多路IO转接机制epoll模型
- Linux网络编程【五】:TCP协议高性能服务器(http)模型之I/O多路转接select
- 【Linux网络编程】基于TCP协议 I/O多路转接(select) 的高性能回显服务器客户端模型
- TensorFlow 整体架构 / 第1节:TensorFlow 概要
- 多个线程按顺序循环打印线程独有的字符串
- 详解MySql的慢查询分析及开启慢查询日志
- 用户账户的创建和管理
- Linux-内核通信之netlink机制-详解
- Linux网络编程(三)多路IO转接服务器
- fatal: cannot resume: .git/rebase-apply/final-commit does not exist.
- ArrayList和Vector的扩容机制
- Spring事务管理
- mysql 不是主键自增长
- 怎么把CAD文件转为pdf文件
- git使用记录
- VS x86 x64 anycpu 编译运行对照表
- list嵌套需要注意的问题