运维开发网
广告位招商联系QQ:123077622
 
广告位招商联系QQ:123077622

Unix网络编程—— I/O复用之select

运维开发网 https://www.qedev.com 2021-05-18 09:23 出处:51CTO 作者:Annnnnnnnd
Unix网络编程—— I/O复用之select,Unix的五种I/O模型阻塞式I/O非阻塞式I/OI/O复用(select poll)信号驱动式I/O(SIGIO)异步I/O(POSIX的aio系列函数)阻塞与非阻塞I/O最流行的I/O模型是阻塞式I/O,一般默认情况下所有套接字都是阻塞的,但是进程可以把一个套接字设置成非阻塞式I/O,以通知内核——当所请求的I/O操作必须把当前进程投入睡眠时才能完成时,不要把当前进程投入睡眠,而是返回一个错误

Unix的五种I/O模型

  • 阻塞式I/O
  • 非阻塞式I/O
  • I/O复用(select poll)
  • 信号驱动式I/O(SIGIO)
  • 异步I/O(POSIX的aio系列函数)

阻塞与非阻塞I/O

最流行的I/O模型是阻塞式I/O,一般默认情况下所有套接字都是阻塞的,但是进程可以把一个套接字设置成非阻塞式I/O,以通知内核——当所请求的I/O操作必须把当前进程投入睡眠时才能完成时,不要把当前进程投入睡眠,而是返回一个错误。

I/O复用

当我们使用select或者poll将进程阻塞在这两个系统调用之上而不是阻塞在真正的I/O调用上时,成为I/O复用。I/O复用的好处是,可以让一个进程同时对多个套接字描述符的状态进行监控。而传统的阻塞式一个进程监控一个套接字的状态。

select函数

select函数可以使一个进程同时等待多个事件中的任何一个发生,并且只在有一个或多个事件发生或经历一段制定的时间后才唤醒它。

注意:select函数监听的set集合在每次返回后非激活状态的套接字位在set集合中被清空,因此每次重新select时需要更新fd_set

#include#includeint select(int maxfd1,
fd_set *readset,                // 读描述符监控fd_set *writeset,               // 写描述符监控fd_set *exceptset,              // 异常条件描述符监控const struct timeval *timeout   // 超时等待设置);//返回:若有就绪套接字则返回其数目,超时返回0,失败返回-1

对于exceptset目前支持2中条件:

  1. 某个套接字的带外数据到达
  2. 某个已置为分组模式的伪终端存在可从其主机读取的控制状态信息(不作讨论)

fd_set及其操作

通过FD_SET、FD_CLR、FD_ISSET、FD_ZERO等宏,可以使fd_set结构的每一位与要监控的描述符绑定、清除绑定、判定绑定、清空结构体。在Ubuntu16.4上使用sizeof测试,得到其长度为128字节,也就是1024位,为其同时支持的最多的描述符个数。

void FD_SERO(fd_set *fdset);        // 清空fdset中的所有位void FD_SET(int fd,fd_set *fdset);  // 将描述符fd设置为监听状态void FD_CLR(int fd,fd_set *fdset);  //将fd从fdset中清除void fd_ISSET(int fd,fd_set *fdset);//判断fd是否为集合fdset中的监听套接字

描述符就绪条件

读套接字就绪条件:

写套接字就绪条件:

异常条件就绪:

struct timeval结构体

struct timeval{        long tv_sec;    // seconds
        long tv_usec;   // micro seconds}

用于指定等待超时的秒数和微秒数。这个参数分为三种情况:

  • 永远等待,直到有一个描述符准备就绪才返回。此时该参数设置为NULL
  • 等待固定时间:在有一个描述符准备就绪时提前返回,否则超时返回0
  • 不等待:将定时器的值都设置为0

shutdown函数

终止网络连接的方式通常是调用close函数。不过close函数有2个限制,可以通过使用shutdown来避免。

  • close把描述符的引用计数减一,仅在该数变为0的时候才关闭套接字。使用shutdown可以不管引用计数,直接激发TCP的正常连接终止序列。
  • close终止读和谐两个方向的数据传送。既然TCP连接是全涮工搞的,有时候我们需要告知对端我们已经完成了数据的发送,及时对端仍然有数据要发送给我们,此时如果调用close将不能收到对端的数据,而使用shutdown则可以选择关闭读、写或者读写全部关闭。

    int shutdown(int sockfd,int howto);

    //返回:成功返回0,失败返回-1

howto的可选项:

  • SHUT_RD:关闭连接的读——套接字中不再读取数据,而且套接字接受缓冲区中的数据也会被丢弃。进程不能再对这个套接字调用任何读取函数。
  • SHUT_WR:关闭连接的写——对于TCP套接字,这成为半关闭。当前留在套接字发送缓冲区中的数据将被发送掉,然后TCP的正常连接终止序列。无论这个套接字描述符的引用计数是否为0,shutdown都会激发TCP终止序列,以后进程不能再对这个套接字调用任何写函数。
  • SHUT_RDWR:关闭读、写——相当于分别调取了shutdown两次并传递参数SHUT_RD和SHUT_WR

使用select和shutdown的网络服务器/客户端例子

// serv_select.c#include#include#include#include#include#include#include#include#include#include#define FD_SET_SIZE 1024#define LISTEN_PORT 8003#define MAX_BUF_LEN 1024int main(){ 
    int i,imaxfd,imax,listenfd,connectfd,nready,nread,iaddrlen;    struct sockaddr_in listen_addr,client_addr;    char buf[MAX_BUF_LEN];    int all_clientfd[FD_SET_SIZE];    for(i = 0;i < FD_SET_SIZE;++i)
        all_clientfd[i] = -1;

    listenfd = socket(AF_INET,SOCK_STREAM,0);
    listen_addr.sin_family = AF_INET;
    listen_addr.sin_port = htons(LISTEN_PORT);
    listen_addr.sin_addr.s_addr = htonl(INADDR_ANY);    int ret = bind(listenfd,(struct sockaddr *)&listen_addr,sizeof(listen_addr));
    ret = listen(listenfd,LISTENQ);

    fd_set all_set,read_set;
    FD_ZERO(&all_set);
    FD_SET(listenfd,&all_set);
    imax = -1;
    imaxfd = listenfd;    while(1){
        read_set = all_set;
        nready = select(imaxfd + 1,&read_set,NULL,NULL,NULL);        if(nready < 0){
            perror("select");            exit(1);
        }        if(FD_ISSET(listenfd,&read_set)){
            iaddrlen = sizeof(listen_addr);
            connectfd = accept(listenfd,(struct sockaddr*)&listen_addr,&iaddrlen);            for(i = 0;i < FD_SET_SIZE;++i){                if(all_clientfd[i] == -1){
                    all_clientfd[i] = connectfd;                    break;
                }
            }            if(i == FD_SET_SIZE){                printf("out of max listen fd count!\n");                continue;
            }            if(i > imax){
                imax = i;
            }
            FD_SET(connectfd,&all_set);            if(connectfd > imaxfd) imaxfd = connectfd;            if(--nready <= 0) continue;
        }        for(i = 0;i < imax;++i){            if(all_client[i] == -1) continue;            if(FD_ISSET(all_client[i],&read_set)){                if(nread = read(all_client[i],buf,MAX_BUF_LEN) <= 0){
                    close(all_client[i]);
                    FD_CLR(all_client[i],&all_set);
                    all_client[i] = -1;                 
                }                else {
                    write(all_client[i],buf,nread);
                }                if(--nready <= 0) break;
            }

        }
    }
}

// client_select.c

               

扫码领视频副本.gif

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号