linux网络编程---IO多路复用
IO模型:
阻塞等待模型(BIO):
1)优点:不占用CPU时间片,阻塞时,cpu时间片交给别人,缺点:但同时只能处理同一个操作。
2)可以使用多线程/多进程实现并发处理多个客户端请求,每个线程对应一个客户端,线程里进行读写操作,主线程则可以继续运行监听客户端。 (但每个线程里 其实也会存在阻塞问题)
while(1)
{
accept(lfd,...); (blocking)
create thread->read/write; (blocking)
}
但是缺点:a.线程/进程会消耗资源,b.调度线程/进程会消耗CPU资源。
非阻塞IO模型:(忙轮询 NIO)
1)accept read write 非阻塞,没读到继续往下执行,不断轮询。优点:提高了程序执行效率。缺点:需要占用更多的CPU和系统资源。
while(1)
{
accept(lfd,...);
读写现有的已accept的fd, 遍历轮询read/write; ->浪费系统资源。
}
2)使用IO多路复用技术来改进。
IO多路复用:
使程序能够同时监听多个文件描述符(监听内核缓冲区),提高程序性能。(可以判断同时有多少客户端来和我通信)。(告诉内核要监听的fd集合,而非自己对所有fd轮询read write).
select/poll
1.select
但是select poll内核不会告诉使用者具体是哪些快递到了,只会告诉总数。底层使用二进制位表示某个fd有无数据,相比read write判断有无数据效率更高。
1. 将要监听的fd添加到fd列表。
2. 调用select(),内核会监听列表中的fd,当有某个/多个fd进行IO操作,函数返回。 (阻塞函数)
3. 返回时,会告诉进程有多少fd要进行IO操作。
select()将我们想监听的读写异常fd交给内核去检测。 (fd集合最大1024个)

对fd集合二进制位的操作

工作过程
readfds等是指针,指明要检测的fd的集合,如0010.内核检测后会将其改变为检测到的结果。如第二个有数据可以读,第3个没有,则修改为0100;
缺点:
1)每次调用select,要将fds从用户态拷贝到内核态。fds很大时开销会很大。
2)每次调用select,内核要对fds进行遍历。fds很大时开销会很大。再拷贝回用户态,也开销很大。 用户态又要遍历判断是哪些fd发生了改变。 O(n)
3)select支持的fd最大数量1024,(fd_set 数组大小128字节,1024位)太小(最多支持1024客户端)。
4)fd集合不能重用(被内核修改了),为了下次检测每次都需要重置。
代码实现:
client.cpp
#include
#include
#include
#include
#include
#include
using namespace std;int main()
{int fd=socket(AF_INET,SOCK_STREAM,0);if(fd==-1){perror("socket:");exit(-1);}sockaddr_in serveraddr;inet_pton(AF_INET,"127.0.0.1",&serveraddr.sin_addr.s_addr);serveraddr.sin_port=htons(9090);serveraddr.sin_family=AF_INET;int ret=connect(fd,(sockaddr*)&serveraddr,sizeof(serveraddr));if(ret==-1){perror("connect: ");exit(-1);}int n=0;while(1){if(n<9) n++;else n=0;char sendBuf[50]="hello,this is ";sendBuf[14]=(char)(n+'0');write(fd,sendBuf,strlen(sendBuf));char readBuf[80]="client received ";int len=read(fd,sendBuf,sizeof(sendBuf));if(len==0){cout<<"server closed\n";break;}else if(len==-1){perror("read: ");exit(-1);}strcat(readBuf,sendBuf);cout<
select.cpp
#include
#include
#include
#include
#include
#include
#include
#include
#include int main()
{int lfd=socket(PF_INET,SOCK_STREAM,0);struct sockaddr_in saddr;saddr.sin_port=htons(9090);saddr.sin_family=AF_INET;saddr.sin_addr.s_addr=INADDR_ANY;int ret=bind(lfd,(sockaddr*)&saddr,sizeof(saddr));if(ret==-1){perror("bind");exit(-1);}ret=listen(lfd,8);if(ret==-1){perror("listen");exit(-1);}fd_set rdset,tmp_set;//要检测的fd集合FD_ZERO(&rdset);//初始化FD_SET(lfd,&rdset);//添加要检测的fdint maxfd=lfd;while(1){tmp_set=rdset; //使内核修改fd不影响我们要检测的fd//让内核检测fd集合 永久阻塞,直到有变化int ret=select(maxfd+1,&tmp_set,nullptr,nullptr,nullptr);if(ret==-1){perror("select");exit(-1);}//检测到有fd对应的缓冲区的数据发生了改变。//判断监听的lfd 是否有客户端要连接if(FD_ISSET(lfd,&tmp_set)){sockaddr_in clientaddr;int len=sizeof(clientaddr);int cfd=accept(lfd,(sockaddr*)&clientaddr,(socklen_t*)&len);//将通信fd加入rdset。FD_SET(cfd,&rdset);maxfd = maxfd>cfd ? maxfd : cfd; }//遍历其他的cfd,判断是否有数据读 从监听的lfd+1开始遍历。for(int i=lfd+1;i

2.poll
通过events和revents,内核修改时会修改revents,使得events可以重用。并且没有1024的限制。


缺点:
1)基本和select一致,但是没有1024最大连接数的限制。(基于链表存储)
2)events集合可重用。
3)水平触发。如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd。
代码实现
#include
#include
#include
#include
#include
#include
#include
#include
#include int main()
{int lfd=socket(PF_INET,SOCK_STREAM,0);struct sockaddr_in saddr;saddr.sin_port=htons(9090);saddr.sin_family=AF_INET;saddr.sin_addr.s_addr=INADDR_ANY;int ret=bind(lfd,(sockaddr*)&saddr,sizeof(saddr));if(ret==-1){perror("bind");exit(-1);}ret=listen(lfd,8);if(ret==-1){perror("listen");exit(-1);}pollfd fdset[2048]; //要监听的fd集合结构体for(int i=0;i<2048;i++){fdset[i].fd=-1;fdset[i].events=POLLIN;//检测读事件}fdset[0].fd=lfd; //将监听lfd加入集合。int maxfd=0;while(1){//让内核检测fd集合 阻塞,直到有变化int ret=poll(fdset,maxfd+1,-1);if(ret==-1){perror("poll");exit(-1);}//检测到有fd对应的缓冲区的数据发生了改变。//判断监听的lfd 是否有客户端要连接if(fdset[0].revents & POLLIN){sockaddr_in clientaddr;int len=sizeof(clientaddr);int cfd=accept(lfd,(sockaddr*)&clientaddr,(socklen_t*)&len);//将通信fd加入rdset。 遍历是为了重用在连接过程中已使用又断开的fd位置。for(int i=1;i<2048;i++){if(fdset[i].fd==-1){fdset[i].fd=cfd;fdset[i].events=POLLIN;maxfd = maxfd>i ? maxfd : i; //更新最大的fd索引break;}}}//遍历其他的cfd,判断是否有数据读 从监听的lfd+1开始遍历。for(int i=1;i
epoll

用epoll_create直接在内核创建一个epoll实例eventpoll,通过epoll_create返回的fd控制这个实例。减少select poll将fd集合从用户态拷贝到内核态的切换。
rbr 记录需要检测的fd, 底层红黑树,遍历效率非常快。
rdlist 就绪fd的链表。可以告诉用户具体改变的fd。
epoll_ctl 管理epoll实例
epoll_data中的fd可以在遍历变化的fd作判断时用到
代码实现
#include
#include
#include
#include
#include
#include
#include
#include
#include int main()
{int lfd=socket(PF_INET,SOCK_STREAM,0);struct sockaddr_in saddr;saddr.sin_port=htons(9090);saddr.sin_family=AF_INET;saddr.sin_addr.s_addr=INADDR_ANY;int ret=bind(lfd,(sockaddr*)&saddr,sizeof(saddr));if(ret==-1){perror("bind");exit(-1);}ret=listen(lfd,8);if(ret==-1){perror("listen");exit(-1);}//创建eventpoll实例 参数>0就可以int epfd=epoll_create(100);//将监听Lfd相关信息加入epfd实例epoll_event epev;epev.events=EPOLLIN;epev.data.fd=lfd;epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&epev);//被监听后,发生改变的fd集合epoll_event epevs[1024];while(1){//让内核检测epev实例 阻塞,直到有变化int ret=epoll_wait(epfd,epevs,1024,-1);if(ret==-1){perror("epoll_wait");exit(-1);}//检测到有ret个fd对应的缓冲区的数据发生了改变。printf("ret = %d\n",ret);for(int i=0;i
epoll的工作模式:
1) LT (默认)
水平触发,支持阻塞和非阻塞socket。内核通知用户某fd就绪后,用户未操作或只操作了一部分数据,下次epoll_wait仍会通知用户该fd就绪,直到用户操作完缓冲区数据,fd变为未就绪。
2)ET
边沿触发,只支持非阻塞socket。内核通知用户某fd就绪后,用户未操作或只操作了一部分数据,下次epoll_wait不会通知用户该fd就绪,直到用户操作后使fd变为非就绪,下次fd再次就绪才会通知用户。
所以在用户操作该fd时,需要循环读取完其所有数据,否则数据未读完下次不被通知就会一直处理不到。在读取过程中,为了不断read时不会阻塞,ET只支持非阻塞SOCKET。
优点:减少了epoll事件被重复触发的次数,效率高于LT。
#include
#include
#include
#include
#include
#include
using namespace std;int main()
{int fd=socket(AF_INET,SOCK_STREAM,0);if(fd==-1){perror("socket:");exit(-1);}sockaddr_in serveraddr;inet_pton(AF_INET,"127.0.0.1",&serveraddr.sin_addr.s_addr);serveraddr.sin_port=htons(9090);serveraddr.sin_family=AF_INET;int ret=connect(fd,(sockaddr*)&serveraddr,sizeof(serveraddr));if(ret==-1){perror("connect: ");exit(-1);}int n=0;while(1){// if(n<9) // n++;// else // n=0;// char sendBuf[50]="hello,this is ";// sendBuf[14]=(char)(n+'0');char sendBuf[1024]={0};fgets(sendBuf,sizeof(sendBuf),stdin);write(fd,sendBuf,strlen(sendBuf)+1);char readBuf[80]="client received ";int len=read(fd,sendBuf,sizeof(sendBuf));if(len==0){cout<<"server closed\n";break;}else if(len==-1){perror("read: ");exit(-1);}strcat(readBuf,sendBuf);cout<
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
int main()
{int lfd=socket(PF_INET,SOCK_STREAM,0);struct sockaddr_in saddr;saddr.sin_port=htons(9090);saddr.sin_family=AF_INET;saddr.sin_addr.s_addr=INADDR_ANY;int ret=bind(lfd,(sockaddr*)&saddr,sizeof(saddr));if(ret==-1){perror("bind");exit(-1);}ret=listen(lfd,8);if(ret==-1){perror("listen");exit(-1);}//创建eventpoll实例 参数>0就可以int epfd=epoll_create(100);//将监听Lfd相关信息加入epfd实例epoll_event epev;epev.events=EPOLLIN;epev.data.fd=lfd;epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&epev);//被监听后,发生改变的fd集合epoll_event epevs[1024];while(1){//让内核检测epev实例 阻塞,直到有变化int ret=epoll_wait(epfd,epevs,1024,-1);if(ret==-1){perror("epoll_wait");exit(-1);}//检测到有ret个fd对应的缓冲区的数据发生了改变。printf("ret = %d\n",ret);for(int i=0;i0){std::cout<<"read buf= "<
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!








