zoukankan      html  css  js  c++  java
  • Linux 网络编程七(非阻塞socket:epoll--select)

    阻塞socket
    --阻塞调用是指调用结果返回之前,当前线程会被挂起。函数只有在得到结果之后才会返回。
    --对于文件操作 read,fread函数调用会将线程阻塞(平常使用read感觉不出来阻塞,
    因为以前的程序read都是从本机上读取数据,所以速度很快,无法感觉出来,但是从网络上读取就会有阻塞现象)。
    --对于socket来讲,accept与recv、recvfrom函数调用会将线程阻塞。 --为了避免整个进程被阻塞后挂起,所以在阻塞模式下,往往需要采用多线程技术。 --一个进程中可并发的线程总数是有限的,在处理大量客户端socket连接(比如上万个client socket),通过线程并发处理socket处理socket并不方便,效率也不高。
    非阻塞socket
    --非阻塞调用是指调用立刻返回。
    --在非阻塞模式下,accept与recv、recvfrom函数调用会立刻返回。
    --在nonblocking状态下调用accept函数,如果没有客户端socket连接请求,那么accept函数返回-1,同时errno值为EAGAIN或者EWOULDBLOCK,这两个宏定义都为整数11.
    --在nonblocking状态下调用recv、recvfrom函数,如果没有数据,函数返回-1,同时errno值为11(EINPROGRESS)。如果socket已经关闭,函数返回0.
    --在nonblocking状态下对一个已经关闭的socket调用send函数,将引发一个SIGPIPE信号,进程必须捕捉这个信号,因为SIGPIPE系统默认的处理方式是关闭进程。
    fcntl函数调用
    fcntl函数可以将文件或者socket描述符设置为阻塞或者非阻塞状态
    int fcntl(int fd,int cmd,.../*arg*/);
    参数fd为要设置的文件描述符或者socket。
    参数cmd,F_GETFL为得到目前状态,F_SETFL为设置状态。
    宏定义O_NONBLOCK代表非阻塞,0代表阻塞。
    成功返回值为描述符当前状态,失败返回-1,并且设置errno。
    //fcntl函数调用设置非阻塞socket
    int opts=fcntl(st,F_GETFL);
    if(opts<0)
    {
        printf("fcntl failed ! error message :%s
    ",strerror(errno));
        return -1;
    }
    opts=opts | O_NONBLOCK;
    if(fcntl(st,F_SETFL,opts)<0)
    {
        printf("fcntl failed ! error message :%s
    ",strerror(errno));
            return -1;
    }
    //fcntl函数调用设置阻塞socket
    if(fcntl(st,F_SETFL,0)<0)
    {
        printf("fcntl failed ! error message :%s
    ",strerror(errno));
        return -1;
    }
    场景解释:现在将服务器端的st和客户端传到服务器端的client_socket都设置为非阻塞,
    但是这种设置针对的是服务器,所以在服务器端设置客户端client_socket非阻塞,并不会影响客户端的socket,客户端的socket还是阻塞的。
    epoll的系统调用函数
    --epoll_create    epoll_create用来创建一个epoll文件描述符。
    --epoll_ctl       epoll_clt用来添加|修改|删除需要侦听的文件描述符及其事件
    --epoll_wait      epoll_wait接收发生在被侦听的描述符上的,用户感兴趣的IO事件
    --epoll文件描述符用完后,需要用close关闭
    --每次添加|修改|删除文件描述符都需要调用epoll_ctl,所以要尽量少的调用epoll_ctl
    --epoll只适用与Linux 内核 2.6版本或者其以上的版本,并不适用于Unix和window
    epoll_create
    --int epoll_create(int size);
    --epool_create创建一个epoll句柄
    --参数size指定epoll所支持的最大句柄数
    --函数成功会返回一个新的epoll句柄,之后的所有操作将通过这个句柄来进行操作,函数失败返回-1,并且设置errno。
    --在用完句柄之后,需要用close()来关闭着创建出来的epoll句柄。
    epoll_ctl:
    --int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event);
    --参数epfd是epoll_create()的返回值
    --参数op表示动作,用三个宏来表示
        EPOLL_CTL_ADD:注册新的fd到epfd中
        EPOLL_CTL_MOD:修改已经注册的fd的监听事件
        EPOLL_CTL_DEL:从epfd中删除一个fd
    --参数fd是需要监听的socket描述符
    --参数event通知内核需要监听什么事件
    epoll_ctl()函数的第四个参数可以是一个临时变量,epoll似乎会拷贝这个参数而不是直接使用
    //union epoll_data共用体
    typedef union epoll_data
    {
        void *ptr;
        int fd;
        _uint32_t u32;
        _uint64_t u64;
    }epoll_data_t;
    
    //struct epoll_event结构
    struct epoll_event
    {
        _uint32_t events;/*Epoll events*/
        epoll_data_t data;/*User data variable */
    };
    events定义
    --EPOLLIN     表示对应的文件描述符可以读(包括对端SOCKET正常关闭)
    --EPOLLOUT    表示对应的文件描述符可以写
    --EPOLLPRI    表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来)
    --EPOLLERR    表示对应的文件描述符发生错误;
    --EPOLLHUP    表示对应的文件描述符被挂断
    --EPOLLET     将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的
    --EPOLLONESHOT    只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
    关于ET、LT两种工作模式
    LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket
    --在LT模式中,内核通知一个文件描述符是否就绪了,然后可以对这个就绪的fd进行IO操作
    --如果你不作任何操作,内核还是会继续通知你的,所以这种模式编程出错误可能性要小一点。
    
    ET(edge-triggered)是高速工作方式,只支持no-block socket
    --在ET模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。
    --ET模式会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,知道你做了某些操作导致那个文件描述符不再为就绪状态了
    --如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知。
    
    ET和LT的区别:
    --LT事件不会丢弃,而是只要读buffer里面有数据可以让用户读,则不断的通知你
    --ET则只在事件发生之时通知。可以简单理解为LT是水平触发,而ET则为边缘触发。
    epoll_wait
    --int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout);
    --参数epfd是epoll_create()的返回值。
    --参数events是一个epoll_event*指针,一般是一个事件数组,当epoll_wait这个函数操作成功之后,epoll_events里面将存储所有的读写事件。
    --参数maxevents是当前需要监听的所有socket句柄数。
    --参数timeout是epoll_wait的超时,为0的时候表示马上返回,为-1的时候表示一致等下去,直到有事件返回,正整数表示等这么长的时间。
    --一般如果网络主循环是单独的线程的话,可以用-1来等,这样可以保证一些效率,如果是和主逻辑在同一线程的话,则可以用0来保证主循环的效率。
    --函数成功返回值是有消息的socket的数目,失败返回-1,并且设置errno。
    epoll_wait返回之后应该是一个循环,遍历所有的事件。
    epoll所能容纳的文件描述符无上限,但是poll和select所容纳的文件描述符最大只能是1024
    select
    --int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct tomeval *timeout);
    --参数nfds为最大socket的值加1,(socket都是int类型)
    --参数readfds是读事件的socket集合
    --参数writefds是写事件的socket集合
    --参数exceptfds是出错事件的socket集合
    --每个事件数组都支持1024个文件描述符
    --参数timeout如果为NULL,表示永远阻塞,如果是一个具体的时间类型,表示等待的事件。
    --select也是阻塞的,只要放入readfds池中的socket有事件发生或者放入writefds池中的socket有事件发生
    或者放入exceptfds池中的socket有事件发生,select函数都会立刻返回。readfds和epoll_wait中的事件数组很相似
    不关心的事件数组可以设置为NULL
    --当readfds、writefds、exceptfds都设置为NULL,并且参数timeout设置了时间,那么select函数的效果就会等同于sleep()函数
    sleep()函数有一定延迟,但是select比sleep()更加精确,并且设置的时间范围更加广泛,可以精确到微秒。
    --select最大支持1024个文件描述符
    --void FD_ZERO(fd_set *set);    初始化(清空)一个select事件数组,不可以使用memset()
    --void FD_SET(int fd,fd_set *set);    在select事件数组里添加一个文件描述符,添加重复的socket对事件数组没有影响
    --void FD_CLR(int fd,fd_set *set);    从select事件数组中删除一个文件描述符
    --void FD_ISSET(int fd,fd_set *set);    判断一个文件描述符是否在select的某个事件数组中    
    --函数成功返回有消息的socket的数目,失败返回-1,并且设置errno
    epoll和select使用场景:客户端连接量大,但是每个客户端发送的数据量少,并且向服务器发送消息的次数很少
    多线程使用场景:客户端连接量小,每个客户端发送的数据量大,且长时间连接发送消息
  • 相关阅读:
    Apollo配置中心环境搭建(Linux)
    SpringBoot整合Swagger2
    DevExpress ASP.NET v18.2新功能详解(四)
    UI控件Telerik UI for WinForms发布R1 2019|附下载
    DevExpress WinForms使用教程:Data Grid
    开发框架DevExtreme全新发布v18.2.6|附下载
    DevExpress ASP.NET v18.2新功能详解(三)
    .NET界面控件DevExpress全新发布v18.2.6|附下载
    DevExpress WinForms使用教程:图表控件
    VCL界面控件DevExpress VCL Controls发布v18.2.4|附下载
  • 原文地址:https://www.cnblogs.com/zhanggaofeng/p/5895166.html
Copyright © 2011-2022 走看看