linux 下epoll与线程池结合使用的简单实例(含常用函数解释)

来源:互联网 发布:网络文化市场 编辑:程序博客网 时间:2024/05/24 04:52

文章来源:http://blog.csdn.net/xilango/article/details/52824486

本文针对初学socket epoll和多线程的小伙伴们

对库函数的简单概述:

注释:
        1.socket()函数------作用 :用于根据指定的地址族、数据类型和协议来分配一个套接口的描述字及其所用的资源(创建套接字)
            第一个参数:一个地址描述;(本文用的是AF_INET)。
            第二个参数:指定socket类型(本文用的是SOCK_STREAM)。
            第三个参数:就是指定协议(本文用的是TCP协议)。
            返回值是套接字。
        2.setsockopt()函数 ------作用:用于任意类型、任意状态套接口的设置选项值(端口复用)。
            第一个参数:标识一个套接口的描述字。
            第二个参数:选项定义的层次;支持SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP和IPPROTO_IPV6。
            第三个参数:需设置的选项。
            第四个参数:指针,指向存放选项待设置的新值的缓冲区。
            第五个参数:optval缓冲区长度。
        3.bind()函数------作用:将一本地地址与一套接口捆绑,通过给一个未命名套接口分配一个本地名字来为套接口建立本地捆绑(主机地址/端口号)
            第一个参数:就是socket的返回值(套接字)
            第二个参数:sockaddr_in类型的指针(将地址和端口与套接字绑定在一起)
            第三个参数:sockaddr_in的长度;
            返回值为0成功
        4.listen()函数-----作用:创建一个套接口并监听申请的连接
            第一个参数:用于标识一个已捆绑未连接套接口的描述字(socket()返回值);
            第二个参数 : 等待连接队列的最大长度;
            返回值为0成功
        5.accept()函数------作用 :在一个套接口接受一个连接
            第一个参数:套接字描述符,该套接口在listen()后监听连接。
            第二个参数:指针,指向一缓冲区,其中接收为通讯层所知的连接实体的地址。第二个参数的实际格式由套接口创建时所产生的地址族确定。
            第三个参数:指针,输入参数,配合addr一起使用,指向存有addr地址长度的整型数。
            返回值 :新的套接字。
        6.fcntl()函数------作用:可以改变已打开的文件性质 例:使accept() , recv(),send()等函数变为非阻塞;
            第一个参数:代表欲设置的文件描述符。
            第二个参数:代表打算操作的指令:
            F_DUPFD用来查找大于或等于参数arg的最小且仍未使用的文件描述符,并且复制参数fd的文件描述符。执行成功则返回新复制的文件描述符。
            新描述符与fd共享同一文件表项,但是新描述符有它自己的一套文件描述符标志,其中FD_CLOEXEC文件描述符标志被清除。请参考dup2()。
            F_GETFD取得close-on-exec旗标。若此旗标的FD_CLOEXEC位为0,代表在调用exec()相关函数时文件将不会关闭。
            F_SETFD 设置close-on-exec 旗标。该旗标以参数arg 的FD_CLOEXEC位决定。
            F_GETFL 取得文件描述符状态旗标,此旗标为open()的参数flags。
            F_SETFL 设置文件描述符状态旗标,参数arg为新旗标,但只允许O_APPEND、O_NONBLOCK和O_ASYNC位的改变,其他位的改变将不受影响。
            F_GETLK 取得文件锁定的状态。
            F_SETLK 设置文件锁定的状态。此时flcok 结构的l_type 值必须是F_RDLCK、F_WRLCK或F_UNLCK。如果无法建立锁定,则返回-1,错误代码为EACCES 或EAGAIN。
            F_SETLKW F_SETLK 作用相同,但是无法建立锁定时,此调用会一直等到锁定动作成功为止。若在等待锁定的过程中被信号中断时,会立即返回-1,错误代码为EINTR。
            第三个参数:参数lock指针为flock 结构指针。
            _type 有三种状态:
            F_RDLCK 建立一个供读取用的锁定
            F_WRLCK 建立一个供写入用的锁定
            F_UNLCK 删除之前建立的锁定
            l_whence 也有三种方式:
            SEEK_SET 以文件开头为锁定的起始位置。
            SEEK_CUR 以目前文件读写位置为锁定的起始位置
            SEEK_END 以文件结尾为锁定的起始位置。
            l_start 表示相对l_whence位置的偏移量,两者一起确定锁定区域的开始位置。
            l_len表示锁定区域的长度,若果为0表示从起点(由l_whence和 l_start决定的开始位置)开始直到最大可能偏移量为止。即不管在后面增加多少数据都在锁的范围内。
            返回值 成功返回依赖于cmd的值,若有错误则返回-1,错误原因存于errno.
            
            例::
                      flags = fcntl(sockfd, F_GETFL, 0);                       //获取文件的flags值。
                      fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);   //设置成非阻塞模式;
                      flags  = fcntl(sockfd,F_GETFL,0);
                      fcntl(sockfd,F_SETFL,flags&~O_NONBLOCK);    //设置成阻塞模式;
        
        7.epoll_create()函数-----作用:创建一个 epoll 的句柄(文件描述符),参数要大于 0, 没有太大意义
           返回值是文件描述符。
           
        8.epoll_ctl()函数--------作用:改变被监听的事件的类型。
            它不同与select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。
            第一个参数是epoll_create()的返回值,
            第二个参数表示动作,用三个宏来表示:EPOLL_CTL_ADD:1 注册新的fd到epfd中;EPOLL_CTL_MOD:2 修改已经注册的fd的监听事件;EPOLL_CTL_DEL:3 从epfd中删除一个fd;
            第三个参数就是要监视的套接字;
            第四个参数是告诉内核需要监听什么事
            返回值成功时返回0,返回-1注册失败
        9.epoll_wait()函数------作用:
            等待事件的产生,类似于select()调用。
            第一个参数:文件描述符(epoll_create()返回值);
            第二个参数:用来从内核得到事件的集合,
            第三个参数:表示每次能处理的最大事件数,告之内核这个wait_event有多大,
            第四个参数:是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。
            该函数返回需要处理的事件数目,如返回0表示已超时。
        10.pthread_mutex_lock();------作用:该互斥锁已被锁定。线程调用该函数让互斥锁上锁,如果该互斥锁已被另一个线程锁定和拥有,则调用该线程将阻塞,直到该互斥锁变为可用为止(将线程上锁);
            参数:
            返回值:在成功完成之后会返回零
        11.pthread_mutex_unlock()-------作用:与pthread_mutex_lock成对存在。(释放互斥锁将线程解锁)
             参数:
             返回值:在成功完成之后会返回零
        12.pthread_create()---------作用:创建线程
              第一个参数:为指向线程标识符的指针。(线程id)
              第二个参数:用来设置线程属性。
              第三个参数:是线程运行函数的起始地址。
              第四个参数:是运行函数的参数。
              创建成功返回零

[cpp] view plain copy
  1. #include <iostream>  
  2. #include <pthread.h>  
  3. #include <string.h>  
  4. #include <stdlib.h>  
  5. #include <unistd.h>  
  6. #include <stdio.h>  
  7. #include <fcntl.h>  
  8. #include <arpa/inet.h>  
  9. #include <sys/epoll.h>  
  10. #include <sys/errno.h>  
  11. #include <sys/socket.h>  
  12. #define NUMBER 10  
  13. #define LISTENMAX 20  
  14. #define IP "127.0.0.1"  
  15. #define PORT 80000  
  16. #define LINET 10000  
  17. using namespace std;  
  18. static unsigned int threadParameter[NUMBER][8];//线程参数  
  19. pthread_t threadId[NUMBER];//线程id  
  20. pthread_mutex_t threadLock[NUMBER];//线程锁  
  21. pthread_cond_t count_nonzero[NUMBER];  
  22. int count1[NUMBER]={0};  
  23. static struct  dataPacket  
  24.   {  
  25.        struct epoll_event ev;  
  26.        struct epoll_event waitEvent[LINET];  
  27.        int sockNumber[LINET]={0};  
  28.        int MAX=0;  
  29.        int epfd=0;  
  30.   }ThreaDataPackage;  
  31.   void decrement_count (int i)  
  32.  {  
  33.   
  34.     pthread_mutex_lock (threadLock+i);  
  35.     while(count1[i]==0)  
  36.                 pthread_cond_wait( count_nonzero+i, threadLock+i);  
  37.     count1[i]=0;  
  38.     pthread_mutex_unlock (threadLock+i);  
  39. }  
  40. void increment_count(int i)  
  41. {  
  42.     pthread_mutex_lock(threadLock+i);  
  43.     pthread_cond_signal(count_nonzero+i);  
  44.     count1[i]=1;  
  45.     pthread_mutex_unlock(threadLock+i);  
  46. }  
  47.   void * serverSocket(unsigned int *parameter)//线程主函数  
  48. {   char buf[1024];  
  49.     char buff[1024];  
  50.     pthread_detach(pthread_self());  
  51.     while(1)  
  52.     {  
  53.     decrement_count (parameter[7]);  
  54.     printf("启动线程:%d\n",parameter[7]);  
  55.     memset(buf,0,sizeof(buf));  
  56.     memset(buff,0,sizeof(buff));  
  57.           int len=recv(parameter[1], buf, 1024, MSG_NOSIGNAL);//非阻塞模式的消息接收  
  58.             if(len>0)  
  59.             {  
  60.                 printf("%s\n",buf);  
  61.             }  
  62.             if(len==0)  
  63.             {  
  64.                 for(int i=0;i<LINET;i++)  
  65.                 {  
  66.                     if(parameter[1]==ThreaDataPackage.sockNumber[i])  
  67.                     {   ThreaDataPackage.MAX--;  
  68.                         ThreaDataPackage.sockNumber[i]=0;  
  69.                         close(ThreaDataPackage.sockNumber[i]);  
  70.                         printf("客户端%d下线\n",ThreaDataPackage.MAX);  
  71.                         if (epoll_ctl(ThreaDataPackage.epfd, EPOLL_CTL_DEL,parameter[1], &ThreaDataPackage.ev) < 0)//加入epoll事件集合  
  72.                             {  
  73.                                 perror("epoll_ctl error:");  
  74.   
  75.                             }  
  76.                         break;  
  77.                     }  
  78.                 }  
  79.             }  
  80.             sprintf(buff ,"你好客户端我是第%d您发送的是:",parameter[7]);  
  81.             strcat(buff,buf);  
  82.             len=send(parameter[1],buff,1024,MSG_NOSIGNAL);//非阻塞模式的消息发送  
  83.             memset(buff,0,sizeof(buff));  
  84.             parameter[0]= 0;//设置线程占用标志为"空闲"  
  85.             }  
  86. }  
  87. static int initThreadPool(void)//初始化数据  
  88. {   int a=0;  
  89.     for(int i=0;i<NUMBER;i++)  
  90.     {  
  91.         threadParameter[i][0]=0;  
  92.         threadParameter[i][7]=i;  
  93.         pthread_cond_init(count_nonzero+i,NULL);  
  94.         pthread_mutex_init(threadLock+i,NULL);  
  95.         a= pthread_create( threadId+ i, NULL, (void* (*)(void *))serverSocket,(void *)(threadParameter[i]));  
  96.         if(a!=0)  
  97.         {  
  98.             perror("pthread_create error:");  
  99.             return -1;  
  100.         }  
  101.     }  
  102.     return 0;  
  103. }  
  104. static int  initListen(char*ip,int port,int listenMax)//初始化监听  
  105. {   int a=0;  
  106.     int sockfd=socket(AF_INET,SOCK_STREAM,0);  
  107.  if(sockfd<0)  
  108.     {  
  109.         perror("sockt error:");  
  110.         close(sockfd);  
  111.         return -1;  
  112.     }  
  113.     struct sockaddr_in server_addr;  
  114.     bzero(&server_addr, sizeof(server_addr));  
  115.     server_addr.sin_family=AF_INET;  
  116.     inet_pton(AF_INET,ip,&(server_addr.sin_addr));  
  117.     server_addr.sin_port=htons(port);  
  118.     int opt = 1;  
  119.     setsockopt(sockfd, SOL_SOCKET,SO_REUSEADDR, (const void *) &opt, sizeof(opt));  
  120.     a=bind(sockfd,(struct sockaddr*)&server_addr,sizeof(server_addr));  
  121.     if(a<0)  
  122.     {  
  123.         perror("bind error:");  
  124.         close(sockfd);  
  125.         return -1;  
  126.     }  
  127.     a=listen(sockfd,listenMax);  
  128.     if(a<0)  
  129.     {  
  130.         perror("listen error:");  
  131.         close(sockfd);  
  132.         return -1;  
  133.     }  
  134.     return sockfd;  
  135. }  
  136. bool setNonBlock(int fd)//设置文件描述符为NonBlock  
  137. {  
  138.    int flags = fcntl(fd, F_GETFL, 0);  
  139.    flags |= O_NONBLOCK;  
  140.    if(-1 == fcntl(fd, F_SETFL, flags))  
  141.    {  
  142.        return false;  
  143.    }  
  144.     return true;  
  145. }  
  146. int main()  
  147. {  
  148.     int acceptSockfd=0;//accept返回的套接字  
  149.     int sockfd=0;//服务器套接字  
  150.     int nfds=0;//触发事件的个数  
  151.     socklen_t addrLen; //地址信息长度  
  152.     struct sockaddr_in clinetAddr; //IPv4地址结构  
  153.     if(0!=initThreadPool())  
  154.     {  
  155.         perror("initThreadPool error:");  
  156.         exit(-1);  
  157.     }  
  158.     sockfd=initListen(IP,PORT,LISTENMAX);  
  159.     ThreaDataPackage.sockNumber[0]=sockfd;  
  160.     if(sockfd<0)  
  161.          {  
  162.              perror("initListen error:");  
  163.             exit(-1);  
  164.          }  
  165.     ThreaDataPackage.epfd = epoll_create(8);//生成文件描述符  
  166.     ThreaDataPackage.ev.events = EPOLLIN | EPOLLET;//对应的文件描述符可读并且是et的epoll工作模式  
  167.     ThreaDataPackage.ev.data.fd =sockfd ;  
  168.     if (epoll_ctl(ThreaDataPackage.epfd , EPOLL_CTL_ADD,sockfd, &ThreaDataPackage.ev) < 0)//加入epoll事件集合  
  169.         {  
  170.             perror("epoll_ctl error:");  
  171.             exit(-1);  
  172.         }  
  173.     while(1)  
  174.     {  
  175.         nfds = epoll_wait(ThreaDataPackage.epfd , ThreaDataPackage.waitEvent, ThreaDataPackage.MAX+1, -1);  
  176.         printf("nfds::%d\n",nfds);  
  177.         for(int i=0;i<nfds;i++)  
  178.          {  
  179.           if((sockfd==ThreaDataPackage.waitEvent[i].data.fd)&&(EPOLLIN==ThreaDataPackage.waitEvent[i].events&EPOLLIN))  
  180.             {  
  181.                  addrLen=sizeof(struct sockaddr_in);  
  182.                  bzero(&clinetAddr,addrLen);  
  183.                  for(int j=0;j<LINET;j++)  
  184.                  {  
  185.                      if(ThreaDataPackage.sockNumber[j]==0)  
  186.                      {  
  187.                          ThreaDataPackage.sockNumber[j]= accept(sockfd, (struct sockaddr *)&clinetAddr, &addrLen);  
  188.                          if(ThreaDataPackage.sockNumber[j]<0)  
  189.                          {  
  190.                              perror("accept error:");  
  191.                              continue;  
  192.                          }  
  193.                          else  
  194.                          {  
  195.                             ThreaDataPackage.ev.data.fd = ThreaDataPackage.sockNumber[j];  
  196.                             ThreaDataPackage.ev.events = EPOLLIN|EPOLLET;  
  197.                             if (epoll_ctl(ThreaDataPackage.epfd , EPOLL_CTL_ADD,ThreaDataPackage.sockNumber[j], &ThreaDataPackage.ev) < 0)//加入epoll事件集合  
  198.                                 {  
  199.                                     perror("epoll_ctl error:");  
  200.                                     exit(-1);  
  201.                                 }  
  202.                              setNonBlock(ThreaDataPackage.sockNumber[j]);//设置为非阻塞  
  203.                              ThreaDataPackage.MAX++;  
  204.                              printf("客户端%d上线\n",ThreaDataPackage.MAX);  
  205.                              break;  
  206.                          }  
  207.                      }  
  208.                  }  
  209.             }  
  210.            else if(ThreaDataPackage.waitEvent[i].data.fd>3&&( EPOLLIN == ThreaDataPackage.waitEvent[i].events & (EPOLLIN|EPOLLERR)))  
  211.             {  
  212.                 for(int j=0;j<NUMBER;j++)  
  213.                   {  
  214.                      if(0==threadParameter[j][0])  
  215.                        {  
  216.                         threadParameter[j][0]=1;//设置活动标志为"活动"  
  217.                         threadParameter[j][1]=ThreaDataPackage.waitEvent[i].data.fd;//客户端的套接字  
  218.                         increment_count(j);  
  219.                         break;  
  220.                       }  
  221.                    }  
  222.              }  
  223.          }  
  224.     }  
  225.     return 0;  
  226. }  
  • 阻塞&非阻塞&同步&异步之间的关系



一:阻塞与非阻塞

阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.

阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.

阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。
非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。


1.blockingIO(阻塞):


1.1阻塞调用是指调用结果返回之前,当前线程会被挂起。函数只有在得到结果之后才会返有人也许会把阻塞调用和同步调用等同起来,实际上他是不同的。


1.2对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回而已。
socket
接收数据函数recv是一个阻塞调用的例子。


1.3socket工作在阻塞模式的时候,如果没有数据的情况下调用该函数,则当前线程就会被 挂起,直到有数据为止。


图一就是recv函数在接受信息时的流程图

当用户进程调用了recv这个系统调用,内核就开始了IO的第一个阶段:准备数据。对于networkio来说,很多时候数据在一开始还没有到达,这个时候内核就要等待数据的到来。而在用户进程这边,整个进程会被阻塞。当内核一直等到数据准备好了,它就会将数据从网卡缓存区中拷贝到系统缓存区然后拷贝到用户指定缓存区中,然后内核返回结果,用户进程才解除阻塞状态,重新运行起来所以,blockingIO的特点就是在IO执行的两个阶段都被阻塞了。


2.nonblockingIO(非阻塞):


linux下,可以通过设置socket使其变为non-blocking


2.1非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。


2.2对象是否处于阻塞模式和函数是不是阻塞调用有很强的相关性,但是并不是一一对应的。

2.3;:阻塞对象上可以有非阻塞的调用方式,我们可以通过一定的API去轮询状态,在适当的时候调用阻塞函数,就可以避免阻塞而对于非阻塞对象,调用特殊的函数也可以进入阻塞调用。函数select就是这样的一个例子。




从图二是通过设置socket使其变为非阻塞。可以看出,当用户进程发出recv操作时,如果内核中的数据还没有准备好,那么它并不会阻塞用户进程,而是立刻返回一个error。从用户进程角度讲,它发起一个recv操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送recv操作。一旦内核中的数据准备好了,并且又再次收到了用户进程的recv调用那么它马上就将数据拷贝到了用户内存,然后返回。
所以,用户进程其实是需要不断的主动询问内核数据好了没有。

其实这样过程与阻塞本质是一样的只是需要不断的向内核询问,有数据到没有拷贝方式也和阻塞时一样。图三以select为例,我们每次将所有的文件描述符放入集合中通过select进行遍历进行查询如果有数据到来 集合就保留该文件描述符。这种方式的运行效率与用户的在线量成反比。

二同步与异步

同步是两个对象之间的关系,而阻塞是一个对象的状态。

同步和异步关注的是消息通信机制(synchronouscommunication/ asynchronouscommunication)
所谓同步,就是在发出一个*调用*时,在没有得到结果之前,该*调用*就不返回。但是一旦调用返回,就得到返回值了。
换句话说,就是由*调用者*主动等待这个*调用*的结果。

而异步则是相反,*调用*在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在*调用*发出后,*被调用者*通过状态、通知来通知调用者,或通过回调函数处理这个调用

3.synchronousIO(同步):


同步就是一旦调用那么就必须有结果才返回,在没有得到结果之前会一直等待,可能有些同学会问同步非阻塞呢,同步非阻塞是得到了结果的。只是这个结果表示没有数据或者error

以上的阻塞和非阻塞均是同步状态。

4.asynchronousIO(异步):

如图五所示再用户进程发起recv操作之后,立刻就可以开始去做其它的事。而另一方面,从内核的角度,当它受到一个asynchronousrecv之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,内核会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,内核会给用户进程发送一个signal,告诉它recv操作完成了。


经过上面的介绍,会发现non-blockingIOasynchronousIO的区别还是很明显的。在non-blockingIO中,虽然进程大部分时间都不会被阻塞,但是它仍然要求进程去主动的检查,并且当数据准备完成以后,也需要进程主动的再次调用recv来将数据拷贝到用户内存。而asynchronousIO则完全不同。它就像是用户进程将整个IO操作交给了他人(内核)完成,然后他人做完后发信号通知。在此期间,用户进程不需要去检查IO操作的状态,也不需要主动的去拷贝数据。



最后总结一下:很多人也会把异步和非阻塞混淆,
因为异步操作一般都不会在真正的IO操作处被阻塞,
比如如果用select函数,select返回可读时再去read一般都不会被阻塞
就好比当你的号码排到时一般都是在你之前已经没有人了,所以你再去柜台办理业务就不会被阻塞.
可见,同步/异步与阻塞/非阻塞是两组不同的概念,它们可以共存组合,

而很多人之所以把同步和阻塞混淆,我想也是因为没有区分这两个概念,
比如阻塞的read/write操作中,其实是把消息通知和处理消息结合在了一起,
在这里所关注的消息就是fd是否可读/,而处理消息则是对fd/.
当我们将这个fd设置为非阻塞的时候,read/write操作就不会在等待消息通知这里阻塞,
如果fd不可读/写则操作立即返回.


阅读全文
0 0
原创粉丝点击