linux多路io复用方法,linux系统特有的机制多路复用

  linux多路io复用方法,linux系统特有的机制多路复用

  套接字接收缓冲区中的字节数大于或等于其低水位线SO_RCVLOWAT。此时,我们可以无阻塞地读取套接字,读取操作返回的字节数大于0。

  套接字的对方关闭连接。此时,对这个套接字的读操作将返回0。

  侦听套接字上有新的连接请求。

  套接字上未处理的错误。此时,我们可以使用getsockop读取并清除错误。

  下列情况下可以编写套接字:

  套接字内核的发送缓冲区中可用的字节数大于或等于其低水位线SO_SNDLOWAT。此时,我们可以无阻塞地写套接字,写操作返回的字节数大于0。

  套接字的写操作被关闭。写入其写入操作被关闭的套接字将触发SIGPIPE信号。

  使用套接字非阻塞连接成功或失败(超时)后。

  套接字上有一个未处理的错误。这时,我们可以使用getsockopt读取并清除错误。

  异常情况:

  在网络中,select只能处理一种异常:在套接字上接收带外数据。

  //两组文件描述符

  fd_set readfds,testfds//readfds用来检测输出是否就绪的文件描述符的集合

  server_sockfd=socket(AF_INET,SOCK_STREAM,0);//建立服务器套接字

  server _地址. sin _家庭=AF _ INET

  server _ address . sin _ addr . s _ addr=htonl(in addr _ ANY);

  server _ address . sin _ port=htons(9000);

  server _ len=sizeof(server _ address);

  bind(server_sockfd,(struct sockaddr*) server_address,server _ len);

  listen(server_sockfd,5);//侦听队列最多可以容纳5个。

  FD _ ZERO(read FDS);//清除空缺0

  FD_SET(server_sockfd,read FDS);//将服务器套接字添加到集合中

  int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct time val * time out);

  while(1){

  char ch

  int fd

  int nread

  testfds=readfds//相当于做了一个备份拷贝,因为调用select后,传入的文件描述符集合会被修改。

  struct timeval my _ time

  my _ time . TV _ sec=2;

  my _ time . TV _ usec=0;

  printf(服务器等待\ n );

  //监控server_sockfd和client_sockfd

  //result=select(FD_SETSIZE,testfds,(fd_set*)0,(fd_set*)0,(struct time val *)0);//无限期阻塞,并测试文件描述符的更改。

  result=select(FD_SETSIZE,testfds,(fd_set*)0,(fd_set*)0,my _ time);//按照my_time中设置的时间等待。如果超过,继续执行。

  If(result 0){//有错误

  perror( select errno );

  出口(1);

  }else if(result==0){//已经超过等待时间,没有响应。

  FD _ ZERO(read FDS);//清除空缺0

  FD_SET(server_sockfd,read FDS);//将服务器套接字添加回集合。

  printf(无连接请求\ n );

  继续;//如果没有响应,就不要往下遍历了

  //扫描所有文件描述符(遍历所有文件句柄)是一件很耗时的事情,严重降低了效率。

  for(FD=0;fd FD _ SETSIZEfd ){

  //找到相关的文件描述符,并确定它是否在文件描述符testfds集合中。

  if(FD_ISSET(fd,testfds)){

  //判断是否是服务器套接字,如果是,说明客户端请求连接。

  if(fd==server_sockfd){

  client_len=sizeof(客户端地址);

  client _ sockfd=accept(server _ sockfd,(struct sockaddr*) client_address,client _ len);

  FD_SET(client_sockfd,read FDS);//将客户端套接字添加到集合中,以监视是否有数据传入。

  printf(在fd %d上添加客户端\n ,client _ sockfd);

  }else{//客户端有消息。

  //获取接收缓冲区中的字节数

  ioctl(fd,FIONREAD,n read);//即从fd中获取多少数据

  //当客户端数据请求完成时,关闭套接字并从集合中清除相应的套接字描述符。

  if(nread==0){

  关闭(FD);

  FD_CLR(fd,read FDS);//移除关闭的fd

  printf(在fd %d\n 上删除客户端,FD);

  }else{//处理客户号请求

  read(fd,ch,1);

  睡眠(5);

  printf(在fd %d\n 上为客户端提供服务,FD);

  ch;

  write(fd,ch,1);

  返回0;

  }

  client_sockfd=socket(AF_INET,SOCK_STREAM,0);//建立客户端套接字

  address.sin _ family=AF _ INET

  address . sin _ addr . s _ addr=inet _ addr( 127 . 0 . 0 . 1 );

  address . sin _ port=htons(9000);

  len=sizeof(地址);

  result=connect(client_sockfd,(struct sockaddr*) address,len);

  if (result==-1){

  perror(哎呀:client 2 );

  出口(1);

  //第一次读写

  write(client_sockfd,ch,1);

  read(client_sockfd,ch,1);

  printf( first time:char from server=% c \ n ,ch);

  睡眠(5);

  //第二次读写

  write(client_sockfd,ch,1);

  read(client_sockfd,ch,1);

  printf(第二次:来自服务器的char=% c \ n ,ch);

  close(client _ sockfd);

  返回0;

  }

  轮询功能

  FDS:一个PollFD类型的数组,它存储了我们感兴趣的文件描述符上发生的所有可读、可写和异常事件。有关结构定义的详细信息,请参见pollfd结构。

  Nfds:数组fds中元素的数量,类型为nfds_t无符号整数。

  超时:超时等待时间。

  -1:保持阻塞,直到某个事件发生。

  0:呼叫后不要等待立即返回。

  0:表示超时。

  \ 0:表示fds中这么多文件描述符都准备好了。即fds中具有非零revents字段的pollfd结构的数量。

  struct poll FD FDS[MAX _ FD];

  int cur _ max _ FD=0;//当前要侦听的最大文件描述符是1,减少了要遍历的数目。

  int main(void){

  int server_sockfd,client _ sockfd

  int server_len,client _ len

  struct sockaddr _ in server _ address,client _ address

  int结果;

  server_sockfd=socket(AF_INET,SOCK_STREAM,0);//服务器套接字

  server _地址. sin _家庭=AF _ INET

  server _ address . sin _ addr . s _ addr=htonl(in addr _ ANY);

  server _ address . sin _ port=htons(9000);

  server _ len=sizeof(server _ address);

  bind(server_sockfd,(struct sockaddr*) server_address,server _ len);

  listen(server_sockfd,5);

  //将被监控文件的描述符添加到fds数组

  fds[server_sockfd]。fd=server _ sockfd

  fds[server_sockfd]。events=POLLIN

  fds[server_sockfd]。revents=0;

  if(cur_max_fd=server_sockfd){

  cur _ max _ FD=server _ sockfd 1;

  while(1){

  char ch

  int i,FD;

  int nread

  printf(服务器等待\ n );

  结果=poll(fds,cur_max_fd,1000);

  if(结果0){

  perror( server 5 );

  出口(1);

  }else if(result==0){

  printf(无连接,结束等待\ n );

  }else{//大于0,返回fds中处于就绪状态的文件描述符的数量。

  //扫描文件描述符

  for(I=0;i cur _ max _ fdi ){

  If(fds[i].revents){//有结果吗?没有结果表示文件描述符上没有发生任何事件。

  fd=fds[i]。FD;

  //判断是否是服务器套接字,如果是,说明客户端请求连接。

  if(fd==server_sockfd){

  client_len=sizeof(客户端地址);

  client _ sockfd=accept(server _ sockfd,(struct sockaddr*) client_address,client _ len);

  客户端_sockfd。fd=client _ sockfd

  客户端_sockfd。events=POLLIN

  客户端_sockfd。revents=0;

  if(cur_max_fd=client_sockfd){

  cur _ max _ FD=client _ sockfd 1;

  printf(在fd %d上添加客户端\n ,client _ sockfd);

  }else{//客户端套接字中有数据请求

  If(fds[i].revents POLLIN){//Read

  nread=read(fd,ch,1);

  if(nread==0){

  关闭(FD);

  memset( fds[i],0,sizeof(struct poll FD));

  printf(在fd %d\n 上删除客户端,FD);

  }else{//写

  睡眠(5);

  printf(在fd %d上为客户端提供服务,接收:%c\n ,fd,ch);

  ch;

  fds[i]。events=POLLOUT//添加写事件监视器

  }埃尔塞夫(FDS[我]。revents poll out){//写入

  write(fd,ch,1);

  fds[i]。events=POLLIN

  返回0;

  }

  Epoll是Linux特有的I/O复用功能。它与select和poll在实现和使用上有很大的不同。

  Epoll使用一组函数来完成一项任务,而不是单一函数。

  Epoll将用户关心的文件描述符上的事件放在内核的一个事件表中,这样就不需要像select和poll一样每次都重复传入文件描述符集或者事件集。Epoll需要使用一个额外的文件描述符来唯一地标识内核中的这个事件表。

  Fd:被操作的文件描述符。

  Event:是指向epoll_event结构的指针。该结构的定义如以下event_event- structure所示:

  成功:返回0。

  失败:返回-1并设置错误号。

  Epoll_data_t是一个联合体,所以我们只能使用fd或ptr成员中的一个。

  如果希望将文件描述符与用户数据相关联,以实现快速数据访问,可以放弃在epoll_data_t中使用fd成员,而是将fd包含在ptr指向的自定义用户数据中。

  当我们调用epoll_wait时,evlist数组中epoll_event的每个数据参数都是我们一开始指定的(即调用epoll_ctl)。比如上面提到的,我们指定了自定义数据ptr,最后一个fd产生了我们监控的事件,这个事件我们可以从它对应的epoll_event的数据中得到。例如下面epoll- simple web服务器中的_ConnectStat结构。

  Epfd: epoll文件描述符,指定内核事件表。

  Evlist:分配的epoll_event结构的数组,epoll会将事件复制到evlist数组中。

  Maxevents:最大监听时间,必须大于0。

  超时:表示没有检测到事件时的最大等待时间(毫秒)。

  0:将立即返回,无需等待。

  -1:表示无限期阻塞,直到事件发生。

  \ 0:阻塞(等待)时间。

  //单独拿出来声明typedef是因为下面的函数指针

  typedef struct _ connect stat connect stat;

  typedef void(* response _ handler)(connect stat * stat);

  //保存自定义数据的结构,调用epoll时使用epoll_data_t中的ptr进行存储。

  struct _ConnectStat {

  int fd//文件描述符

  字符名称[64];//名称

  char年龄[64];//年龄

  struct epoll _ event _ ev//当前文件句柄对应于epoll事件

  int状态;//0-未登录,1-已登录

  response_handler句柄;//不同页面的处理功能

  };

  相关的函数声明和全局变量

  //初始化自定义数据存储结构

  connect stat * stat _ init(int FD);

  //将新链接的客户端fd放入当前epoll对应的内核事件表中。

  void connect _ handle(int new _ FD);

  //请求响应——指定相应的处理函数

  void do _ http _ respone(connect stat * stat);

  //处理http请求

  void do _ http _ request(connect stat * stat);

  //响应处理程序3354的请求,链接返回的内容。

  void welcome _ response _ handler(connect stat * stat);

  //响应处理函数——commit后返回的内容

  void commit _ respone _ handler(connect stat * stat);

  //将新链接的客户端fd放入当前epoll对应的内核事件表中。

  void connect _ handle(int new _ FD);

  //创建监听套接字-省略

  int startup(char* _ip,int _ port);

  //将fd-设置为非阻塞状态,即给指定的fd添加一个状态

  void set _ non block(int FD);

  //打印信息提示ip:port

  void用法(const char * argv);

  //响应标头

  const char * main _ header= HTTP/1.0 200 OK \ r \ n Server:郭瑄瑄服务器\ r \ n content-Type:text/html \ r \ n connection:Close \ r \ n ;

  静态int epfd=0;//epoll文件描述符,对应一个内核事件表。

  初始化自定义数据存储结构

  //初始化自定义数据存储结构

  ConnectStat * stat_init(int fd) {

  ConnectStat * temp=NULL

  temp=(ConnectStat *)malloc(sizeof(ConnectStat));

  如果(!温度){

  fprintf(stderr, malloc失败。原因:% m \ n’);

  返回NULL

  memset(temp, \0 ,sizeof(ConnectStat));

  temp-FD=FD;

  temp-status=0;

  }

  处理http请求

  //解析http请求

  void do _ http _ request(connect stat * stat){

  //读取和解析http请求

  char buf[4096];

  char * pos=NULL

  ssize_t _s=read(stat- fd,buf,sizeof(buf)-1);

  If (_s 0){//读取数据

  buf[_ s]= \ 0 ;

  //printf( receive from client:% s \ n ,buf);//GET/HTTP/1.1

  pos=buf

  //Demo只演示效果,没有详细的协议分析。

  如果(!Rncasecmp (pos, get ,3)){//是Get请求吗?

  stat-handler=welcome _ response _ handler;//设置执行功能

  }else if(!Rncasecmp (pos, post ,4)){//是POST请求吗?

  //获取uri

  //printf(-Post-\ n );

  pos=strlen( Post );

  while(* pos== * pos==/)pos;

  //提交/提交HTTP/1.1

  如果(!Rncasecmp (pos, commit ,6)){//提交

  int len=0;

  //printf( post commit-\ n );

  pos=strstr(buf, \ r \ n \ r \ n );//返回第一个匹配项的位置\r\n\r\n

  char * end=NULL

  //获取姓名和年龄

  if (end=strstr(pos, name=){

  pos=end strlen( name=);

  end=pos

  while(( A =* end * end= Z ) ( A =* end * end= Z ) ( 0 =* end * end= 9 ))end;

  len=结束位置;

  If (len 0) {//在自定义结构中存储名称

  memcpy(统计名称、位置、结束位置);

  stat-name[len]= \ 0 ;

  if (end=strstr(pos, age=){

  pos=end strlen( age=);

  结束=位置

  while (0=*end *end=9 )结束;

  len=结束位置;

  if (len 0) {//将年龄存入自定义结构体中

  memcpy(状态、位置、结束位置);

  stat-age[len]= \ 0 ;

  stat-handle=commit _ respone _ handler;//设置响应函数

  否则{

  stat-handler=welcome _ response _ handler;//设置响应函数

  否则{

  stat-handler=welcome _ response _ handler;//设置响应函数

  stat-_ evevents=epoll out//修改事件类型

  epoll_ctl(epfd,EPOLL_CTL_MOD,stat- fd,stat-_ ev);//修改,交给eoill监视。

  }else if (_s==0){//没有读取到数据,客户端关闭。

  printf(客户端:%d关闭\n ,统计-

  epoll_ctl(epfd,EPOLL_CTL_DEL,stat- fd,NULL);//将对应软驱从对应使用的内核事件表中删除

  关闭(统计- //关闭套接字

  免费(stat);//释放内存

  }else{//读取发生错误

  perror(’读);

  }

  请求响应-根据指定的处理函数

  void do _ http _ respone(connect stat * stat){

  统计处理器(统计);//调用对应设置的函数

  }

  响应处理函数——请求链接返回的内容

  void welcome _ response _ handler(connect stat * stat){

  const char * welcome_content=\

  html lang=zh-CN \n\

  头部\n\

  meta content= text/html;charset=utf-8 http-equiv= Content-Type \ n \

  标题这是一个测试/title \n\

  /head \n\

  正文\n\

  div align=center height= 500 px \ n \

  br/br/br/\n\

  h2 Hello World /h2 br/br/\n\

  表单操作=提交方法=post \n\

  姓名:输入type=text name=name/\n\

  br/年龄:输入类型=密码名称=年龄/\n\

  br/br/br/输入类型=提交值=提交/\n\

  输入类型=重置值=重置/\n\

  /form \n\

  /div \n\

  /body \n\

  /html

  char发送缓冲区[4096];

  char content _ len[64];

  strcpy(sendbuffer,main _ header);//拷贝响应头

  snprintf(content_len,64, Content-Length: %d\r\n\r\n ,(int)strlen(welcome _ Content));

  strcat(sendbuffer,content _ len);

  strcat(sendbuffer,welcome _ content);

  //printf(向客户端发送答复\n%s ,发送缓冲区);

  //写给客户端-即发起请求的浏览器

  write(stat- fd,sendbuffer,strlen(发送缓冲区));

  stat-_ evevents=epoll in//修改关心的事件

  //stat-_ ev。数据。ptr=stat

  epoll_ctl(epfd,EPOLL_CTL_MOD,stat- fd,stat-_ ev);

  }

  响应处理函数——委员会后返回的内容

  void commit _ respone _ handler(connect stat * stat){

  const char * commit_content=\

  html lang=zh-CN \n\

  头部\n\

  meta content= text/html;charset=utf-8 http-equiv= Content-Type \ n \

  标题这是一个测试/title \n\

  /head \n\

  正文\n\

  div align=center height= 500 px \ n \

  br/br/br/\n\

  氘欢迎% s nbsp,年龄nbsp%s!/h2 br/br/\n\

  /div \n\

  /body \n\

  /html

  char发送缓冲区[4096];

  字符内容[4096];

  char content _ len[64];

  int len=0;

  len=snprintf(content,4096,commit_content,stat- name,stat-age);

  strcpy(sendbuffer,main _ header);//响应头

  snprintf(content_len,64, Content-Length: %d\r\n\r\n ,len);

  strcat(sendbuffer,content _ len);

  strcat(sendbuffer,content);

  //printf(向客户端发送答复\n%s ,发送缓冲区);

  write(stat- fd,sendbuffer,strlen(发送缓冲区));

  stat-_ evevents=epoll in//修改关心的事件

  epoll_ctl(epfd,EPOLL_CTL_MOD,stat- fd,stat-_ ev);//交给使用来监视

  }

  打印信息提示ip:端口

  空的用法(const char* argv){

  printf(%s:[ip][port]\n ,argv);

  }

  将fd-设置为非阻塞状态

  void set_nonblock(int fd){

  //这里的文件状态标志旗即打开函数的第二个参数

  int fl=fcntl(fd,F _ GETFL);//获取设置的旗

  fcntl(fd,F_SETFL,fl O _ NONBLOCK);//设置旗

  //fcntl函数https://blog.csdn.net/zhoulaowu/article/details/14057799

  //O _ non块https://blog.csdn.net/cjfeii/article/details/115484558

  }

  创建一个监听套接字

  int startup(char* _ip,int _port){

  int sock=socket(AF_INET,SOCK_STREAM,0);

  if (sock 0){

  perror(’袜子);

  出口(2);

  int opt=1;

  setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,opt,sizeof(opt));

  结构sockaddr _ in local

  本地的。sin _ port=htons(_ port);

  local.sin _ family=AF _ INET

  local . sin _ addr . s _ addr=inet _ addr(_ IP);

  if (bind(sock,(struct sockaddr*) local,sizeof(local)) 0){

  perror( bind );

  出口(3);

  if (listen(sock,5) 0){

  perror(‘听’);

  出口(4);

  退货袜子;//返回套接字

  }

  #include epoll_server.h

  int main(int argc,char *argv[]){

  如果(argc!=3){//检查输入参数的个数是否正确。

  用法(argv[0]);

  出口(1);

  //创建服务器套接字

  int listen_sock=startup(argv[1],atoi(argv[2]);

  //创建一个epoll

  epfd=epoll _ create(256);

  If (epfd 0){//创建失败

  perror( epoll _ create );

  出口(5);

  connect stat * stat=stat _ init(listen _ sock);//自定义数据存储

  struct epoll _ event _ ev//epoll事件结构

  _ ev.events=EPOLLIN//将关注的事件设置为读取事件。

  _ ev . data . ptr=stat;//接收返回值

  //在epfd中加入listen_sock,关心读取事件,有客户端请求链接。

  epoll_ctl(epfd,EPOLL_CTL_ADD,listen_sock,_ ev);

  struct epoll _ event revs[64];//接收生成响应的返回事件

  int time out=-1;//-1无限期阻塞

  int num=0;//就绪请求I/O的数量

  while (1){

  //检测事件

  开关((num=epoll_wait(epfd,revs,64,timeout))){

  0://Listen超时

  printf( time out \ n );

  打破;

  案例1: //错误

  perror( epoll _ wait );

  打破;

  默认:{ //0,即返回要处理的事件数。

  //获取对应的文件描述符

  struct sockaddr _ in peer

  socklen _ t len=sizeof(peer);

  for(int I=0;i numi ){//

  //获取与该fd相关的链接信息

  connect stat * stat=(connect stat *)revs[I]. data . ptr;

  Int rsock=stat- //得到对应的fd,做出如下判断

  If (rsock==listen _ sock (revs [i])。events)epollin){//有客户端链接

  int new_fd=accept(listen_sock,(struct sockaddr*) peer,len);

  if(new _ FD 0){//接受成功

  printf(获取新客户端:%s:%d\n ,inet_ntoa(peer.sin_addr),ntohs(peer . sin _ port));

  connect _ handle(new _ FD);//侦听传入的客户端fd

  }else {//除服务器socket之外的其他fd都准备好了。

  如果(revs [i].events Pollin){//有数据要读取

  do _ http _ request((connect stat *)revs[I]. data . ptr);

  } Elseif (revs [i]。事件poll out){//写入

  do _ http _ respone((connect stat *)revs[I]. data . ptr);//完成响应后,我会再次关心EPOLLIN事件,等待下一个请求。

  }否则{

  打破;

  返回0;

  }

  当被监控文件描述符上发生读写事件时,epoll_wait将通知处理程序进行读写。如果你这次没有一次性读写所有数据(比如读写缓冲区太小),那么下次调用epoll_wait时,它也会通知你在上次没有读写的文件描述符上继续读写。如果你不一直读写它,它会一直通知你。

  如果系统中存在大量你不需要读写的就绪文件描述符,并且每次都会返回,这将大大降低处理器检索它所关心的就绪文件描述符的效率。

  应用程序可能不会立即处理此事件,因为下次调用epoll_wait时,epoll_wait会再次向应用程序通告此事件。

  设置方式:默认水平触发。

  边沿触发(边沿触发):

  当被监控文件描述符上发生读写事件时,epoll_wait将通知处理程序进行读写。如果这次没有读写完所有数据(比如读写缓冲区太小),就不会通知你,也就是只通知你一次,直到文件描述符上出现第二次读写事件才会通知你。

  这种模式比水平触发更高效,系统不会被你不关心的就绪文件描述符淹没,大大减少了同一个epoll事件重复触发的次数。

  同时,应用程序应该立即处理这个事件,因为后续的epoll_wait调用不会通知应用程序这个事件(后续的读写事件会被通知,而这个不会)。

  设置方法(epoll):

  相应文件描述符上要监视的事件设置为events =EPOLLET。

  同时,文件描述符被设置为非阻塞模式。如上面的epoll- simple web服务器所示。

  目的:确保套接字连接在任何时候都只由一个线程处理,从而确保连接的完整性并避免许多可能的争用情况。

  可能的场景:当一个线程(或进程)读完套接字上的数据并开始处理时,在处理过程中可以在套接字上读取新的数据(EPOLLIN再次被触发),然后另一个线程被唤醒读取新的数据。所以就出现了两个线程操作一个套接字的情况。

  用法:使用epoll_ctrl函数在此套接字(文件描述符)上注册EPOLLONESHOT事件。

  一旦用EPOLLONESHOT事件注册的socket被一个线程处理了,这个socket上的EPOLLONESHOT事件就应该被立即复位,这样才能保证这个socket的EPOLLIN事件在下次可读的时候能够被触发,同时也给了其他线程处理这个socket的机会。

  用于监控链接请求的Server_socket不能注册EPOLLONESHOT事件,否则应用程序只能处理一个客户链接,因为后续的客户链接请求将不再触发Server_socket上的EPOLLIN事件。

  如果线程在处理完套接字上的请求后,在套接字上接收到新的客户端请求,则该线程将继续联系套接字。

  struct sockaddr _ in client _ address;

  socklen _ t client _ addrlength=sizeof(client _ address);

  int connfd=accept( listenfd,(struct sockaddr* ) client_address,client _ addr length);

  addfd( epollfd,connfd,true);

  else if(事件[i].事件EPOLLIN ){

  pthread_t线程;

  fds fds _ for _ new _ worker

  FDS _ for _ new _ worker . epollfd=epollfd;

  FDS _ for _ new _ worker . sockfd=sockfd;

  //创建一个线程来处理它

  pthread_create( thread,NULL,worker,(void *)FDS _ for _ new _ worker);

  else printf(发生了别的事\ n );

  }

  在相应的内核事件表中注册指定fd上的事件。

  void addfd( int epollfd,int fd,bool oneshot ){

  epoll_event事件;

  event . data . FD=FD;

  event.events=EPOLLIN EPOLLET

  如果(一次性){

  event.events =EPOLLONESHOT//注册EPOLLONESHOT事件

  epoll_ctl( epollfd,EPOLL_CTL_ADD,fd,event);

  setnonblocking(FD);//设置为非阻塞fd

  }

  将fd设置为非阻塞

  int setnonblocking( int fd ){

  int old_option=fcntl( fd,F _ GETFL);//获取这个fd之前设置的属性。

  int new _ option=old _ option O _ non block;//追加O_NONBLOCK属性

  fcntl( fd,F_SETFL,new _ option);//设置

  返回old _ option//当前示例演示返回无意义且未使用的。

  }

  线程功能

  void* worker( void* arg ){

  int sockfd=((FDS *)arg)-sockfd;

  int epollfd=((FDS *)arg)-epollfd;

  printf(启动新线程以接收fd上的数据:%d\n ,sockfd);

  char buf[BUFFER _ SIZE];

  memset( buf, \0 ,BUFFER _ SIZE);

  而(1 ){//因为是非阻塞的,所以要一次读完,也就是说要马上处理,因为epoll_wait只会提醒你一次。

  int ret=recv( sockfd,buf,BUFFER_SIZE-1,0);

  if( ret==0 ){

  关闭(sockfd);

  printf( foreiner关闭了连接\ n );

  打破;

  else if( ret 0 ){

  If( errno==EAGAIN ){//读出

  reset_oneshot( epollfd,sockfd);//重置注册事件

  printf( read la ter \ n );

  打破;

  否则{

  printf( get content: %s\n ,buf);

  //睡眠5秒,模拟数据处理过程。

  睡眠(5);

  printf(结束在fd上接收数据的线程:%d\n ,sockfd);

  }

  复位fd上记录的事件

  void reset_oneshot( int epollfd,int fd ){

  epoll_event事件;

  event . data . FD=FD;

  event . events=EPOLLIN EPOLLET EPOLLONESHOT;

  epoll_ctl( epollfd,EPOLL_CTL_MOD,fd,event);

  三种I/O多路复用功能的比较

  select的参数类型fd_set只是一组文件描述符,所以select需要这种类型的三个参数来区分可读、可写和异常事件。

  一方面,select不能处理更多类型的事件;另一方面,内核在线修改fd_set,导致应用程序必须在下次调用select之前重置这三个fd_set集合。同时,我们也需要在使用前做好备份。

  投票:

  Pollfd的参数类型pollfd更智能。文件描述符和事件类型是一起定义的。调用后,PollfD结构中的events成员被修改。对于实际检测到的事件,我们设置的事件成员保持不变。再次被调用后,事件将被重置为空。

  每次用SELECTPoll进行轮询后,无论其中的事件是否就绪,都需要遍历用户关心的整组事件,所以应用程序检索就绪文件描述符的时间复杂度为O(n)。

  epoll:

  Epoll管理用户注册事件的方式与上述两种完全不同。它在内核中维护一个事件表,并提供一个独立的系统调用epoll_ctl来添加、删除和修改事件,而不需要从用户空间中重复读取这些事件。

  epoll_wait系统调用的events参数负责保存这些就绪事件,使得应用检索就绪文件描述符的时间复杂度达到O(1)。

  Poll和epoll_wait使用nfds和maxevents参数来指定要侦听的文件描述符和事件的最大数量,这两个参数都可以达到系统允许打开的文件描述符的最大数量——65535。但是,select允许侦听的文件描述符的最大数量通常是有限的。尽管用户可以修改此限制,但这可能会导致意想不到的后果。

  工作模式:

  select poll和SELECT Poll都只能在相对低效的LT模式下工作,而epoll可以在高效的ET模式下工作。

  内核实现:

  Select poll采用轮询方式,每次扫描一整套注册的文件描述符,并将准备好的文件描述符返回给用户程序。检测就绪事件的时间复杂度为O(n)。

  Epoll_wait采用回调方法。当内核检测到一个就绪文件描述符时,它将触发回调函数,该函数将文件描述符上的相应事件插入到内核就绪事件队列中。最后,内核在适当的时候将就绪事件队列的内容复制到用户空间。

  当有很多活动连接时,epoll_wait的效率不一定比select和poll高,因为回调函数触发太频繁。因此,epoll_wait适用于连接多但活动连接少的情况。

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

相关文章阅读

  • Linux计划任务的命令是什么,linux 任务,linux系统中计划任务介绍
  • linux系统设置免密登录,linux免密钥登录
  • linux系统设置免密登录,linux免密钥登录,Linux下实现免密码登录(超详细)
  • Linux系统查看版本,linux系统查看系统版本
  • Linux系统查看版本,linux系统查看系统版本,Linux查看系统版本的方法汇总
  • linux操作系统的安装与配置,linux系统安装与配置教程
  • linux操作系统的安装与配置,linux系统安装与配置教程,Linux操作系统安装图文配置教程详细版
  • Linux基本网络配置命令,linux网络配置,Linux系统配置网络详细介绍
  • linux命令日志,linux系统日志怎么看
  • linux命令日志,linux系统日志怎么看,Linux系统中日志详细介绍
  • linux 服务配置文件,linux服务管理命令,Linux系统配置(服务控制)详细介绍
  • debian 命令行安装,debian安装软件方式,基于Debian的linux系统软件安装命令详解 (推荐)
  • ,,查看Linux系统是32位还是64位的方法总结
  • ,,Linux系统设置PATH环境变量(3种方法)
  • ,,Linux系统的修复模式(单用户模式)
  • 留言与评论(共有 条评论)
       
    验证码: