0.服务器端模型选择
大体思路是:在主线程中使用epoll承受高并发连接,在主线程中开辟工作线程用来处理就绪的描述符。嗯,这个原来有官名,叫反应者模式。事件驱动编程。另外工作线程处理的就绪的文件描述符最好是非阻塞的。
1.阻塞与非阻塞
【概念】
【区别】阻塞与非阻塞都是对于IO而言的,即阻塞式IO和非阻塞式IO。没有什么阻塞的read和非阻塞的read。以read(fd,MAXnum)为例,read阻塞式IO(文件描述符),表示只能完全读完该文件描述符的MAXnum个字符才能从read调用中返回;而read非阻塞式IO,表示当文件描述符中没有信息时,可以立即返回。
使用阻塞式IO的问题在于:如果缓冲区大小只有100bytes,但是用户有500bytes要发送,当需要read读这个阻塞式的文件描述符A时,先读了100bytes,发现缓冲区空了,但是剩下的400bytes还在路上,那么read阻塞IOA时,就只能等在这个调用上了,只有完完整整的将500bytes读完才能从read返回。而其中等剩余400bytes的过程中,文件描述符B中即使有数据可读,也只能傻看着,没法读!!这样等的时间就浪费掉了。这段时间大家一起等,有数据也得等。换句话说:如果read(设备A)是阻塞的,那么只要设备A没有数据到达就会一直阻塞在设备A的read调用上,即使设备B有数据到达也不能处理,使用非阻塞I/O就可以避免设备B得不到及时处理。
而使用非阻塞式IO时:文件描述符A中读完100bytes,剩下的400bytes没来,那么可以先从read A返回;这时B描述符如果缓冲区有数据,可以先去读B,这样就谁都不耽误了。
while(leftchnum>0) { n=read(非阻塞描述符A); if(n<0&&errno==EAGAIN) continue; else leftchnum-=n; }
2.epoll分水平触发和边缘触发两种方式。
Level_triggered(水平触发):当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据一次性全部读写完(如读写缓冲区太小),那么下次调用 epoll_wait()时,它还会通知你在上没读写完的文件描述符上继续读写,当然如果你一直不去读写,它会一直通知你!!!如果系统中有大量你不需要读写的就绪文件描述符,而它们每次都会返回,这样会大大降低处理程序检索自己关心的就绪文件描述符的效率!!
Edge_triggered(边缘触发):当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用epoll_wait()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你!!!这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符!!
所以最好使用边缘触发,减少不关心的就绪文件描述符。在线程中使用非阻塞描述符,不妨碍其他就绪文件描述符的操作。
3.epoll的使用
【epoll的使用三步走】
【epoll的使用实例】1.int epoll_create(int size):生成一个epoll专用的描述符,size是epoll想要监听的最大的描述字数,这里我们设置为服务器监听的最大连接数+1
int epollfd=epoll_create(LISTENQ+1);
2.epoll_ctl(epoll的描述字,注册的事件的类型,注册的文件描述符,注册的其他信息)
3.int epoll_wait(epollfd的描述字,事先申请好的存放事件的事件数组,前面那数组的大小,超时等待时间)struct epoll_event ev; ev.data.fd=listenfd; ev.events=EPOLLIN|EPOLLET;//套接字缓冲区可读触发,触发方式边缘触发 epoll_ctl(epollfd,EPOLL_CTL_ADD,listenfd,&ev);//注册事件
EPOLLMAX即事先用于申请的,用于保存从内核态拷贝的就绪事件信息的,epoll_event数组。这个大小限制了epoll一次性从内核缓冲区拷贝回的事件数。它不同于create中epoll能够监听的事件数!!!
epoll_wait(epollfd,events,EPOLLMAX,-1);
大致思路是:先把监听套接字注册到epoll中,然后epoll_wait,有连接时分辨是否是listenfd,如果是,那么accept连接,并将连接注册到epoll中。该连接的套接字设置成非阻塞。
epoll会将事件按注册时的事件类型分门别类的返回给我们,让我们的处理更加有目的性
int main(int argc, char **argv) { int listenfd; struct sockaddr_in servaddr; listenfd=socket(AF_INET,SOCK_STREAM,0); bzero(&servaddr,sizeof(servaddr)); servaddr.sin_family=AF_INET; servaddr.sin_addr.s_addr=htonl(INADDR_ANY); servaddr.sin_port=htons(SERV_PORT); bind(listenfd,(struct sockaddr *)&servaddr,sizeof(servaddr)); listen(listenfd,LISTENQ);//LISTENQ:服务器同时可接受的TCP连接数 int epollfd=epoll_create(LISTENQ+1); struct epoll_event ev; ev.data.fd=listenfd; ev.events=EPOLLIN|EPOLLET;//套接子缓冲区有内容时触发,触发方式边缘触发 epoll_ctl(epollfd,EPOLL_CTL_ADD,listenfd,&ev); struct epoll_event events[EPOLLMAX]; while(1) { int fdnum=epoll_wait(epollfd,events,EPOLLMAX,-1); for(int i=0;i<fdnum;++i) { if(events[i].data.fd==listenfd) { struct sockaddr_in clientaddr; socklen_t clilen; int connfd=accept(listenfd,(sockaddr *)&clientaddr,&clilen); if(connfd>0) { unpluo_setnoblocking(connfd); ev.data.fd=connfd; ev.events=EPOLLIN|EPOLLET; epoll_ctl(epollfd,EPOLL_CTL_ADD,connfd,&ev); } } else if(events[i].events&EPOLLIN) { int sockfd=events[i].data.fd; if(sockfd>0) { // servluo_match(sockfd); } } } } }