TCP/IP编程之select函数详解


前述:

linux下的I/O复用模型目前很多都已经不用select函数了,而是用epoll,但是为什么还需要了解select编程呢,其实是从两个方面考虑的:一是为了通过select去理解epoll,而是非阻塞的connect函数也会用到select函数。


函数原型:

SELECT(2)                  Linux Programmer's Manual                 SELECT(2)

NAME
       select,  pselect,  FD_CLR,  FD_ISSET, FD_SET, FD_ZERO - synchronous I/O
       multiplexing

SYNOPSIS
       /* According to POSIX.1-2001 */
       #include <sys/select.h>

       /* According to earlier standards */
       #include <sys/time.h>
       #include <sys/types.h>
       #include <unistd.h>

       int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);

       void FD_CLR(int fd, fd_set *set);
       int  FD_ISSET(int fd, fd_set *set);
       void FD_SET(int fd, fd_set *set);
       void FD_ZERO(fd_set *set);



相关的函数比较多,先来说说select函数的参数:

(1) timeout:超时时间

struct timeval {

    long tv_sec; //秒

    long tv_usec; //微妙

}

这个参数有三种可能

a) 永久等待下去,将该参数设置为空指针

b) 等待固定一段时间,指针不为空且设置的秒或者毫秒值不为0,如果超时,select函数会返回0

c) 根本不等待,指针不为空且设置的秒或者毫秒值均为0


(2) 中间的三个参数readfds、writefds、exceptfds指定我们要让内核检测读、写和异常条件的描述符,如果我们对某一个的条件不感兴趣,就可以把它设置空指针。

目前支持的异常条件只有两个(下面的异常条件可以暂时不用理解):

a) 某个套接字的外带数据的到达

b) 某个已置为分组模式的伪终端等信息


select使用描述符集,通常是一个整数数组,其中每个整数中的每一位对应一个描述符。所以fd_set类型本质是其实是一个数组类型。

举个例子,以下代码用于定义一个fd_set类型的变量,然后设置需要让select检测的描述符1、4、5:

fd_set rset;

FD_ZERO(&rset); //初始化set,位的值为0

FD_SET(1, &rest); //设置,位的值为1

FD_SET(4, &rest);

FD_SET(5, &rest);


(3) nfds参数是指定待检测的描述符的个数,它的值是待检测的最大描述符加1,举个例子,需要检测的描述符为1、4、5、11,那么nfds的值就为12。


select函数返回值:

返回:若有就绪描述符则为其数目,若超时则为0,若出错则为-1


我们使用FD_ISSET宏来检测fd_set数据类型中的描述符,描述符集内任何与为就绪对应的位返回均清成0。为此,每次重新调用select函数时,我们都得再次把所有的描述符集内所关心的位置为1.


描述符就绪条件:


这里要注意的是,即使select返回套接字可读,也需要使用getsockopt检查是否有错误发生,因为如图示,有待处理的错误也是返回可读就绪。



最后,可以通过一个支持connect超时的函数来理解下select函数,或者查阅《unix网络编程》·卷1·第6章:I/O复用:

#include <sys/types.h>  
#include <sys/time.h>  
#include <sys/socket.h>  
#include <netinet/in.h>    
#include <arpa/inet.h>   
#include <unistd.h>  
#include <fcntl.h>  
#include <stdio.h>  
#include <errno.h>  
#include <stdlib.h>  
#include <string.h>  
  
int connect_nonb(int sockfd, const struct sockaddr *addr, socklen_t addrlen, int nsec)  
{  
    int flags, n, error;  
    socklen_t len;  
    fd_set rset, wset;  
    struct timeval tval;  
  
    /* 调用fcntl把套接字设置为非阻塞 */  
    flags = fcntl(sockfd, F_GETFL, 0);  
    fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);  
  
    /* 发起非阻塞connect。期望的错误是EINPROGRESS,表示连接建立已经启动但是尚未完成  */  
    error = 0;  
    if ( (n = connect(sockfd, addr, addrlen)) < 0)  
        if (errno != EINPROGRESS)  
            return(-1);  
      
    /* 如果非阻塞connect返回0,那么连接已经建立。当服务器处于客户端所在主机时这种情况可能发生 */  
    if (n == 0)  
        goto done; /* connect completed immediately */  
  
    /* 调用select等待套接字变为可读或可写,如果select返回0,那么表示超时 */  
    FD_ZERO(&rset);  
    FD_SET(sockfd, &rset);  
    wset = rset;  
    tval.tv_sec = nsec;  
    tval.tv_usec = 0;  
  
    if ( (n = select(sockfd+1, &rset, &wset, NULL, nsec ? &tval : NULL)) == 0) {  
        close(sockfd); /* timeout */  
        errno = ETIMEDOUT;  
        return(-1);  
    }  
  
    /* 检查可读或可写条件,调用getsockopt取得套接字的待处理错误,如果建立成功,该值将为0 */  
    if (FD_ISSET(sockfd, &rset) || FD_ISSET(sockfd, &wset)) {  
        len = sizeof(error);  
        if (getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len) < 0)  
            return(-1); /* Solaris pending error */  
    } else {  
        perror("select error: sockfd not set");  
        exit(1);  
    }  
  
    /* 恢复套接字的文件状态标志并返回 */  
done:  
    fcntl(sockfd, F_SETFL, flags); /* restore file status flags */  
    if (error) {  
        close(sockfd); /* just in case */  
        errno = error;  
        return(-1);  
    }  
    return(0);  
}  
  
int main()  
{  
    // socket    
    struct sockaddr_in servaddr;      
    short port = 9999;      
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);      
    servaddr.sin_family = AF_INET;      
    servaddr.sin_addr.s_addr = inet_addr("113.107.231.211");      
    servaddr.sin_port = htons(port);  
    int timeout = 3;  
      
    if (connect_nonb(sockfd, (sockaddr *) &servaddr, sizeof(sockaddr_in), 2) < 0) {    
        perror("connect fail: ");  
        return(-1);  
    }  
    printf("connect success!\n");  
  
    return 0;  
}  



参考:《unix网络编程》·卷1

  • 5
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值