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

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 <sys/select.h>
#include <sys/time.h>

int 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 <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>

#define FD_SET_SIZE 1024
#define LISTEN_PORT 8003
#define MAX_BUF_LEN 1024

int 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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值