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\n",strerror(errno));
    return -1;
}
opts=opts | O_NONBLOCK;
if(fcntl(st,F_SETFL,opts)<0)
{
    printf("fcntl failed ! error message :%s\n",strerror(errno));
        return -1;
}
//fcntl函数调用设置阻塞socket
if(fcntl(st,F_SETFL,0)<0)
{
    printf("fcntl failed ! error message :%s\n",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使用场景:客户端链接量大,可是每一个客户端发送的数据量少,而且向服务器发送消息的次数不多
多线程使用场景:客户端链接量小,每一个客户端发送的数据量大,且长时间链接发送消息
相关文章
相关标签/搜索