中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

epoll實現socket通信

發布時間:2020-07-10 12:17:19 來源:網絡 閱讀:1771 作者:小楊楊雪松 欄目:編程語言

 epoll是Linux特有的I/O復用函數,它在實現和使用上與select和poll有很大差異。epoll使用一組函數來完成任務,而不是單個函數。epoll把用戶關心的文件描述符上的事件放在內核的一個事件表中,無需像select和poll那樣每次調用都要重復傳入文件描述符集或事件集,但epoll需要一個額外的文件描述符,來唯一標示內核中的這個事件表,這個文件描述符使用epoll_create函數來創建。

  epoll是一種高效的管理socket的模型,相對于select和poll來說具有更高的效率和易用性。epoll的性能不會隨socket數量的增加而下降。

下面我們來說說epoll的使用:

  epoll所使用的數據結構如下:

epoll實現socket通信


   結構體epoll_event被用于注冊感興趣的事件和回傳所發生待處理的事件,epoll_data聯合體用來保存觸發事件的某個文件描述符相關的數據。例如一個client連接到服務器,服務器通過調用accept函數可以得到這個client對應的socket文件描述符,可以把這個文件描述符賦給epoll_data的fd字段,以便以后的讀寫操作在這個文件描述符上進行。epoll_data_t是一個聯合體,其中4個成員最多用的就是fd,它指定事件所從屬的目標文件描述符,ptr成員可用來指定與fd相關的用戶數據。但由于epoll_data_t是一個聯合體,我們不能同時使用其ptr成員和fd成員,因此我們要將文件描述符和用戶數據關聯起來,以實現快熟的數據訪問,只能使用其他手段,我們在下面的程序中自定義了個結構體,里面有我們所關心的fd和保存用戶數據的buf。

  events字段是表示感興趣的事件,被觸發的事件的可能取值為:

      EPOLLIN:表示對應的文件描述符可以讀;

      EPOLLOUT:表示對應的文件描述符可以寫;

      EPOLLPRI:表示對應的文件描述符有緊急的數據可讀;

      EPOLLERR:表示對應的文件描述符發生錯誤;

      EPOLLHUP:表示對應的文件描述符被掛斷;

      EPOLLET:表示將EPOLL設置為邊緣觸發模式;

      EPOLLONESHOT:只監聽一次事件,當監聽完這次事件之后,如果還需要繼續監聽這個socket的話,需要再次把這個socket加入到EPOLL隊列里。

所用到的函數有如下三個:

  1.epoll_create函數:

     原型:int epoll_create(int size)

     該函數生成一個epoll專用的文件描述符,size參數指定生成描述符的最大范圍。size參數現在并不起作用,使用紅黑樹來管理所有的文件描述符,該函數返回的文件描述符將用作其他所有的epoll系統調用的第一個參數,

  2.epoll_ctl函數:

     原型:int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event)

     該函數用于控制某個文件描述符上的事件,可以注冊事件,修改事件,刪除事件。

     參數:epfd:由epoll_create生成的epoll專用的文件描述符

          op:要進行的操作,可能的取值有:

              EPOLL_CTL_ADD 注冊

              EPOLL_CTL_MOD 修改

              EPOLL_CTL_DEL 刪除

          fd:關聯的文件描述符

          event:指向epoll_event的指針

    調用成功返回0,失敗返回-1;

 3.epoll_wait函數:

      原型:int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout)

      該函數用于輪詢I/O事件的發生,調用成功時返回就緒的文件描述符的個數,失敗時返回-1,并設置errno。該函數如果檢測到事件,就將所有就緒的事件從內核表中(由epfd參數指定)復制到它的第二個參數events指向的數組中,這個數組只用于輸出epoll_wait檢測到的就緒事件。

     參數:

        epfd:由epoll_create生成的epoll專用的文件描述符

        events:用于回傳待處理的數組

        maxevent:每次能處理的事件數

        timeout:與poll接口的timeout參數相同,是超時時間,0會立即返回,-1是永久阻塞。

     如果該函數調用成功,返回對應I/O上已準備好的文件描述符數目,如果返回0表示已超時。

 

接下來我們來說說epoll的工作原理:

  epoll同樣只告訴那些已就緒的文件描述符,而且當我們調用epoll_wait獲得就緒文件描述符時,返回的不是實際的描述符,而是一個代表就緒描述符數量的值,我們只需去epoll指定的數組中依次取得相應數量的文件描述符即可,這里使用內存映射技術,節省了系統調用時的開銷。兩一個本質的改進在與epoll采用基于事件的就緒通知方式,在select/poll中,進程只有在調用一定的方法后,內核才對所有監視的文件描述符進行掃描,而epoll事先通過epoll_ctl來注冊一個文件描述符,一旦某個文件描述符就緒時,內核會采用回調機制,循序激活這個文件描述符,當進程調用epoll_wait時便得到通知。

 

LT模式和ET模式:

  epoll對于文件描述符的操作有兩種模式,LT(水平觸發)和ET(邊緣觸發)。LT模式是默認的工作模式,這種模式下epoll相當于一個效率較高的poll。當往epoll內核事件表中注冊一個文件描述符上的EPOLLET事件時,epoll將以ET模式來操作該文件描述符。ET模式是epoll的高效工作模式。

  對于采用LT工作模式的文件描述符,當epoll_wait檢測到其上有事件發生并將此事件通知應用程序后,應用程序可以不立即處理該事件。這樣,當應用程序下一次調用epoll_wait時,epoll_wait還會再次向應用程序告知該事件,直到該事件被處理。而采用ET模式的文件描述符,當epoll_wait檢測到其上有事件發生并將此事件通知應用程序后,應用程序必須立即處理該事件,因為后續的epoll_wait調用將不再向應用程序通知這一事件,。這樣,ET模式在很大程度上降低了同一個epoll事件被重復觸發的次數,因此效率比LT模式要高。

 

我們在下面的編程中還用到了一個fcntl函數,該函數原型如下:

  int fcntl(int fd,int cmd,...)

  該函數可以執行各種描述符控制操作,它提供的與網絡編程相關的特性如下:

    1.非阻塞式I/O。通過使用F_SETFL命令設置O_NONBLOCK文件狀態標志,我們可以把一個套接字設置為非阻塞。

    2.信號驅動式I/O。通過使用F_SETFL命令設置O_ASYNC文件狀態標志,我們可以把一個套接字設置成一旦其狀態發生變化,內核就產生一個SIGIO信號。

    3.F_SETOWN命令允許我們指定用于接收SIGIO和SIGURG信號的套接字屬主。其中SIGIO信號時套接字被設置為信號驅動式I/O型后產生的,SIGURG信號時在新的帶外數據到達套接字時產生的。F_GETOWN返回套接字的當前屬主。


接下來我們看看基于epoll的socket編程代碼:(基于LT模式下的阻塞模式)

客戶端給服務端發送消息。服務端回顯給客戶端:

  server端:

#include <stdio.h>
  #include <stdlib.h>
  #include <unistd.h>
  #include <assert.h>
  #include <string.h>
  #include <arpa/inet.h>
  #include <netinet/in.h>
  #include <sys/epoll.h>
  #include <sys/types.h>
  #include <sys/socket.h>
  #include<errno.h>
  #include<fcntl.h>
  #define _BACKLOG_ 5
  #define _BUF_SIZE_ 10240
  #define _MAX_ 64
  
  typedef struct _data_buf
  {
      int fd;
      char buf[_BUF_SIZE_];
  }data_buf_t,*data_buf_p;
   static void usage(const char* proc)
  {
      printf("usage:%s[ip][port]\n",proc);
  }
  
  static int start(int port,char *ip)
  {
      assert(ip);
      int sock=socket(AF_INET,SOCK_STREAM,0);
      if(sock<0)
      {
          perror("socket");
          exit(1);
      }
  
      struct sockaddr_in local;
      local.sin_port=htons(port);
      local.sin_family=AF_INET;
      local.sin_addr.s_addr=inet_addr(ip);
  
      int opt=1;  //設置為接口復用
      setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
  
      if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
      {
          perror("bind");
          exit(2);
      }
  
      if(listen(sock,_BACKLOG_)<0)
      {
          perror("listen");
          exit(3);
      }
      return sock;
  }
 
  static int epoll_server(int listen_sock)
  {
      int epoll_fd=epoll_create(256);//生成一個專用的epoll文件描述符
      if(epoll_fd<0)
      {
          perror("epoll_create");
          exit(1);
      }

     struct epoll_event ev;//用于注冊事件
     struct epoll_event ret_ev[_MAX_];//數組用于回傳要處理的事件
     int ret_num=_MAX_;
     int read_num=-1;
     ev.events=EPOLLIN;
     ev.data.fd=listen_sock;
     if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,listen_sock,&ev)<0)//用于控制某個文件描述符上的事件(注冊,修改,刪除)
     {
         perror("epoll_ctl");
         return -2;
     }
 
     int done=0;
     int i=0;
     int timeout=5000;
     struct sockaddr_in client;
     socklen_t len=sizeof(client);
     while(!done)
     {
         switch(read_num=epoll_wait(epoll_fd,ret_ev,ret_num,timeout))//用于輪尋I/O事件的發生
         {
             case0:                                                                          
                 printf("time out\n");
                 break;
             case -1:
                 perror("epoll");
                 exit(2);
             default:
                 {
                     for(i=0;i<read_num;++i)
                     {
                         if(ret_ev[i].data.fd==listen_sock&&(ret_ev[i].events&EPOLLIN))
                         {
 
                             int fd=ret_ev[i].data.fd;
                             int new_sock=accept(fd,(struct sockaddr*)&client,&len);
                             if(new_sock<0)
                             {
                                 perror("accept");
                                 continue;
                             }
 
                             ev.events=EPOLLIN;
                             ev.data.fd=new_sock;
                             epoll_ctl(epoll_fd,EPOLL_CTL_ADD,new_sock,&ev);
                             printf("get a new client...\n");
                         }
                         else  //normal sock
                         {
                             if(ret_ev[i].events&EPOLLIN)
                             {
                                 int fd=ret_ev[i].data.fd;
                                 data_buf_p mem=(data_buf_p)malloc(sizeof(data_buf_t));
                                 if(!mem)
                 perror("malloc");                                                                      
                                     continue;
                                 }
                                 mem->fd=fd;
                                 memset(mem->buf,'\0',sizeof(mem->buf));
                                 ssize_t _s=read(mem->fd,mem->buf,sizeof(mem -> buf)-1);
                                 if(_s>0)
                                 {
                                     mem->buf[_s-1]='\0';
                                     printf("client: %s\n",mem->buf);
                                     ev.events=EPOLLOUT;
                                     ev.data.ptr=mem;
                                     epoll_ctl(epoll_fd,EPOLL_CTL_MOD,fd,&ev);
                                 }
                                 else if(_s==0)
                                 {
                                     printf("client close...\n");
                                     epoll_ctl(epoll_fd,EPOLL_CTL_DEL,fd,NULL);
                                     close(fd);
                                     free(mem);
                                 }
                                 else
                                 {
                                     continue;
                                 }
                             }
                             else if(ret_ev[i].events&EPOLLOUT)  //寫事件準備就緒
                             {
                                     data_buf_p mem=(data_buf_p)ret_ev[i].data.ptr;
                                     int fd=mem->fd;
                                     char *buf=mem->buf;
                                     write(fd,buf,strlen(buf));
                                     ev.events=EPOLLIN;    //寫完,下次關心讀事件
                                     ev.data.fd=fd;
                                     epoll_ctl(epoll_fd,EPOLL_CTL_MOD,fd,&ev);
                             }
                             else{
                                 //....
                             }
                         }
                     }
                 }
                 break;
         }
     }
 
 }
 
 int main(int argc,char* argv[])
 {
     if(argc!=3)
     {
         usage(argv[0]);
         return 1;
     }
 
     int port=atoi(argv[2]);
     char *ip=argv[1];
 
     int listen_sock=start(port,ip);
     epoll_server(listen_sock);
     close(listen_sock);
     return 0;
 }

client端:

   

#include <stdio.h>
   #include <stdlib.h>
   #include <assert.h>
   #include <poll.h>
   #include <string.h>
   #include <unistd.h>
   #include <netinet/in.h>
   #include <sys/types.h>
   #include <sys/socket.h>
  #include <arpa/inet.h>
  
  static void usage(const char* arg)
  {
      printf("usage:%s [ip][port]",arg);
  }
  
  int main(int argc,char *argv[])
  {
      if(argc!=3)
      {
          usage(argv[0]);
          exit(1);
      }
  
      int port=atoi(argv[2]);
      char *ip=argv[1];
  
      int sock=socket(AF_INET,SOCK_STREAM,0);
      if(sock<0)
      {
          perror("socket");                                                                                                                                           
          exit(2);
      }
  
      struct sockaddr_in remote;
      remote.sin_family=AF_INET;
                      remote.sin_port=htons(port);
      remote.sin_addr.s_addr=inet_addr(ip);
  
      int ret=connect(sock,(struct sockaddr*)&remote,sizeof(remote));                                                                                                 
  
      char buf[1024];
      while(1)
      {
          printf("please enter: ");
          fflush(stdout);
          ssize_t _s=read(0,buf,sizeof(buf)-1);
          buf[_s]='\0';
          write(sock,buf,sizeof(buf)-1);
          memset(buf,'\0',sizeof(buf));
          read(sock,buf,sizeof(buf)-1);
          printf("echo:%s\n",buf);
      }
      return 0;
  }

 運行結果:

epoll實現socket通信             

我們可以看到,客戶端發給服務端的數據,被服務端收到后,回顯給客戶端。


接下來我們把程序改為ET模式非阻塞模式:

主要改的地方有:

       1.因為ET模式只通知一次,所以我們在讀取數據的時候必須一次讀完,我們寫的read_data函數就是實現這個功能的;

       2.把所有的描述符都改為非阻塞模式,調用我們的set_no_block函數;

       3.注冊事件的時候,要與上EPOLLET;

具體如下:

server端:


  

 #include <stdio.h>
  #include <stdlib.h>
  #include <unistd.h>
  #include <assert.h>
  #include <string.h>
  #include <arpa/inet.h>
  #include <netinet/in.h>
  #include <sys/epoll.h>
  #include <sys/types.h>
  #include <sys/socket.h>
  #include<errno.h>
  #include<fcntl.h>
  #define _BACKLOG_ 5
  #define _BUF_SIZE_ 10240
  #define _MAX_ 64
  
  typedef struct _data_buf
  {
      int fd;
      char buf[_BUF_SIZE_];
  }data_buf_t,*data_buf_p;
   static void usage(const char* proc)
  {
      printf("usage:%s[ip][port]\n",proc);
  }
  
  static int set_no_block(int fd) //用來設置非阻塞
  {
      int old_fl=fcntl(fd,F_GETFL);
      if(old_fl<0)
      {
          perror("perror");
          return -1;
      }
      if(fcntl(fd,F_SETFL,old_fl|O_NONBLOCK))
      {
          perror("fcntl");
          return -1;
      }
  
      return 0;
  }
     
  int read_data(int fd,char* buf,int size)//ET模式下讀取數據,必須一次讀完
  {
      assert(buf);                                                                                                                                                    
      int index=0;
      ssize_t _s=-1;
      while((_s=read(fd,buf+index,size-index))<size)
      {
          if(errno==EAGAIN)
          {
              break;
          }
         index += _s;
      }
      return index;
  }
  
  static int start(int port,char *ip)
  {
      assert(ip);
      int sock=socket(AF_INET,SOCK_STREAM,0);
      if(sock<0)
      {
          perror("socket");
          exit(1);
      }

      struct sockaddr_in local;
      local.sin_port=htons(port);
      local.sin_family=AF_INET;
      local.sin_addr.s_addr=inet_addr(ip);
  
      int opt=1;  //設置為接口復用
      setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
  
      if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
      {
          perror("bind");
          exit(2);
      }
  
      if(listen(sock,_BACKLOG_)<0)
      {
          perror("listen");
          exit(3);
      }
      return sock;
  }

  static int epoll_server(int listen_sock)
  {
      int epoll_fd=epoll_create(256);//生成一個專用的epoll文件描述符
      if(epoll_fd<0)
      {
          perror("epoll_create");
          exit(1);
      }
     set_no_block(listen_sock);//設置監聽套接字為非阻塞
     struct epoll_event ev;//用于注冊事件
     struct epoll_event ret_ev[_MAX_];//數組用于回傳要處理的事件
     int ret_num=_MAX_;
     int read_num=-1;
     ev.events=EPOLLIN|EPOLLET;
     ev.data.fd=listen_sock;
     if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,listen_sock,&ev)<0)//用于控制某個文件描述符上的事件(注冊,修改,刪除)
     {
         perror("epoll_ctl");
         return -2;
     }

     int done=0;
     int i=0;
     int timeout=5000;
     struct sockaddr_in client;
     socklen_t len=sizeof(client);
     while(!done)
     {
         switch(read_num=epoll_wait(epoll_fd,ret_ev,ret_num,timeout))//用于輪尋I/O事件的發生
         {
             case0:                                                                          
                 printf("time out\n");
                 break;
             case -1:
                 perror("epoll");
                 exit(2);
             default:
                 {
                     for(i=0;i<read_num;++i)
                     {
                         if(ret_ev[i].data.fd==listen_sock&&(ret_ev[i].events&EPOLLIN))
                         {

                             int fd=ret_ev[i].data.fd;
                             int new_sock=accept(fd,(struct sockaddr*)&client,&len);
                             if(new_sock<0)
                             {
                                 perror("accept");
                                 continue;
                             }
                             set_no_block(new_sock);//設置套接字為非阻塞                            
                             ev.events=EPOLLIN|EPOLLET;
                             ev.data.fd=new_sock;
                             epoll_ctl(epoll_fd,EPOLL_CTL_ADD,new_sock,&ev);
                             printf("get a new client...\n");
                         }
                         else  //normal sock
                         {
                             if(ret_ev[i].events&EPOLLIN)
                             {
                                 int fd=ret_ev[i].data.fd;
                                 data_buf_p mem=(data_buf_p)malloc(sizeof(data_buf_t));
                                 if(!mem)
                                 {  
                                     perror("malloc");                                                                      
                                     continue;
                                 }
                                 mem->fd=fd;
                                 memset(mem->buf,'\0',sizeof(mem->buf));
                                 ssize_t _s=read_data(mem->fd,mem->buf,sizeof(mem -> buf)-1);//一次讀完
                                 if(_s>0)
                                 {
                                     mem->buf[_s-1]='\0';
                                     printf("client: %s\n",mem->buf);
                                     ev.events=EPOLLOUT|EPOLLET;
                                     ev.data.ptr=mem;
                                     epoll_ctl(epoll_fd,EPOLL_CTL_MOD,fd,&ev);
                                 }
                                 else if(_s==0)
                                 {
                                     printf("client close...\n");
                                     epoll_ctl(epoll_fd,EPOLL_CTL_DEL,fd,NULL);
                                     close(fd);
                                     free(mem);
                                 }
                                 else
                                 {
                                     continue;
                                 }
                             }
                             else if(ret_ev[i].events&EPOLLOUT)  //寫事件準備就緒
                             {
                                     data_buf_p mem=(data_buf_p)ret_ev[i].data.ptr;
                                     char* msg="http/1.0 200 ok\r\n\r\nhello bit\r\n";
                                      int fd=mem->fd;
                                   
                                     write(fd,msg,strlen(msg));
                                     close(fd);
                                     epoll_ctl(epoll_fd,EPOLL_CTL_DEL,fd,&ev);  //寫完服務端直接退出
                                      free(mem);
                             }
                             else{
                                 //....
                             }
                         }
                     }
                 }
                 break;
         }
     }

 }

 int main(int argc,char* argv[])
 {
     if(argc!=3)
     {
         usage(argv[0]);
         return 1;
     }

     int port=atoi(argv[2]);
     char *ip=argv[1];

     int listen_sock=start(port,ip);
     epoll_server(listen_sock);
     close(listen_sock);
     return 0;
 }

我們在瀏覽器上訪問我們的服務器程序,當服務器發送給瀏覽器數據后,服務端關閉連接關閉連接結果如下:

epoll實現socket通信


至此,完。

    


                                                        



                                                                                                                                                                                                                                                                                                                                                                   


                   



 


向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

靖江市| 莲花县| 历史| 来安县| 化德县| 泾川县| 湾仔区| 思茅市| 宜兴市| 连平县| 深泽县| 虞城县| 沙湾县| 涟水县| 甘孜县| 五台县| 泌阳县| 绥江县| 拜城县| 建平县| 沛县| 普格县| 贵阳市| 刚察县| 崇明县| 菏泽市| 崇仁县| 新龙县| 巴彦淖尔市| 庆阳市| 博客| 自贡市| 清流县| 中西区| 噶尔县| 永丰县| 托克托县| 怀仁县| 九寨沟县| 含山县| 晋州市|