Select单进程非阻塞TCP echo服务器
1. select 描述
#include <sys/select.h>
  #include <sys/time.h>
  int select( int maxfdp1,   fd_set *readset,  fd_set *writeset,  fd_set *exceptset,  const struct timeval *timeout);
返回:正确:就绪描述符数目;超时: 0;出错: -1
参数说明:
  timeout: 内核等待指定描述符集合中的任意一个就绪的时间。
struct timeval {
long tv_sec; //seconds
                          long tv_usec;    //microseconds
                      }; 
timeout = NULL: 永远等待下去:仅在有一个描述符准备好I/O才返回。
timeout != NULL: 等待timeval中指定的时间返回。
timeout != NULL, timeval == 0 : 不等待:检查描述符后立即返回。这称为轮循(polling);
前两种情况会被信号中断,并从信号处理程序返回。
timeout为const表示函数返回时不会修改timeval,但是有的linux版本会修改timval值。
考虑到可移植性,每次都应对timeout进行初始化。
readset, writeset, exceptset:指定我们让内核测试读写异常条件的描述符:
可读:
(1) 套接字缓冲区数据字数 >= 套接字接受缓冲区低水位标记的当前大小。 不会阻塞,返回>0的值(准备好读入的数据)。
可用SO_RCVLOWAT套接字选项设置标志,TCP和UDP默认1。所以read时可能返回1字节数据,需要反复读。
(2) 接受到FIN,读操作不阻塞返回0(EOF)。
(3) 监听套接字已完成的连接数不为0,新的连接到达。accept不阻塞。
(4) 有一个套接字错误要处理。读不阻塞返回-1。设置errno
可写:
(1) 发送缓冲区可用空间字节数 >= 最低水位。写不阻塞返回>0(例如:传输层接受的字节数)。 TCP和UDP最低水位2048。
(2) 连接的写半部关闭,对这样的套接字写产生SIGPIPE信号。
(3) 非阻塞的connect套接字已建立连接,或者connect已失败告终。
(4) 有一个套接字错误带处理。写不阻塞返回-1.设置errno。
异常:带外数据到达。
maxfdp1: 指定待测试的描述符个数,为最大描述符 + 1, 从0开始。
2. select 宏和相关说明
select使用描述符集fd_set,通常为一个整数数组,其中每个整数中的每一位对应一个描述符。
4个宏设置fd_set:
void FD_ZERO(fd_set *fdset); //清空fdset中所有位
void FD_SET(int fd, fd_set *fdset); //fd_set中fd位置位
void FD_CLR(int fd, fd_set *fdset); //fd_set中fd位复位
void FD_ISSET(int fd, fd_set *fdset); //fd_set中fd是否置位
select中的readset,writeset,exceptset = NULL,表示我们不关心这个。select会修改这三个描述符集和,时值——结果参数。
调用select时指定我们关心的描述符值,返回时指示那些已经就位,未就位的置0。因此每次调用select时都需要把我们关心的描述符置1。
3. select 缺陷
#include <sys/types.h> 定义了FD_SETSIZE,通常为1024。当描述符超出时,可用poll解决。
通过修改内核的FD_SETSIZE大小,需要重新编译内核,可能带来扩展性问题,不推荐使用。
4. 一个echo服务器程序示例
#include "unp.h"
int main(int argc, char **argv)
{
    if (argc != 2) {
        err_quit("Usage: a.out <#port>");
    }
    int listenfd = Tcp_listen(NULL, argv[1], NULL);
    int flags = Fcntl(listenfd, F_GETFL, 0);
    Fcntl(listenfd, F_SETFL, flags | O_NONBLOCK);
    //最大文件描述符
    int maxfd = listenfd;
    //连接的客户端描述符
    int client[FD_SETSIZE];
    memset(client, -1, FD_SETSIZE);
    //client已使用的描述符最大下标
    int maxi = -1;
 //要监听的描述符集合
    fd_set allset;
    FD_ZERO(&allset);
    FD_SET(listenfd, &allset);
    for (;;) {
        //select会修改描述符集合,每次重新初始化
        fd_set rset = allset;
        DPRINTF("Start to select()");
        //select阻塞到可读
        int nready = Select(maxfd + 1, &rset, NULL, NULL, NULL);
//监听描述符可读
        if (FD_ISSET(listenfd, &rset)) {
            DPRINTF("Start to accept a connection");
            //int connfd = Accept(listenfd, NULL, NULL);
            //非阻塞方式忽略若干错误
            int connfd = accept(listenfd, NULL, NULL);
            DPRINTF("connfd %d", connfd);
            if (connfd < 0) {
#ifdef EPROTO
                DPRINTF("EPROTO");
                if (errno == EINTR || errno == EWOULDBLOCK ||
                    errno == ECONNABORTED || errno == EPROTO) {
#else 
                if (errno == EINTR || errno == EWOULDBLOCK ||
                    errno == ECONNABORTED) {
#endif        
                    continue;
                }  else {
                    err_sys("accept() failed");
                }
            }
            DPRINTF("Accept a connection");
            //设置非阻塞
            int flags = Fcntl(connfd, F_GETFL, 0);
            Fcntl(connfd, F_SETFL, flags | O_NONBLOCK);
            int i = 0;
            while (i < FD_SETSIZE) {
                if (client[i] < 0) {
                    client[i] = connfd;
                    break;
                }
                ++i;
            }
            if (i == FD_SETSIZE) {
                err_quit("Too many client");
            }
            FD_SET(connfd, &allset);
            if (connfd > maxfd) {
                maxfd = connfd;
            }
            if (i > maxi) {
                maxi = i;
            }
           //检测是否还有准备好的连接
            if (--nready <= 0) {
                continue;
            }
        } //end if(FD_ISSET)
       //循环检测已连接的客户端
        DPRINTF("maxi = %d", maxi);
        for (int i = 0; i <= maxi; ++i) {
            int sockfd = client[i];
            if (sockfd < 0) {
                continue;
            }
            DPRINTF("client[]: i %d, sockfd %d", i, sockfd);
            if (FD_ISSET(sockfd, &rset)) {
                char line[MAXLINE];
                int n;
again:
                while ((n = read(sockfd, line, MAXLINE)) > 0) {
                    Write(sockfd, line, n);
                }
                if (n < 0 && errno == EINTR) {
                    goto again;
                } else if (n < 0 && errno == EWOULDBLOCK) {
                    ;
                } else if (n <= 0) {        //0: FIN, close by client; -1: RST, error
                    Close(sockfd);
                    FD_CLR(sockfd, &allset);
                    client[i] = -1;    
                    DPRINTF("read n = %d", n);
                    if (n < 0) {
                        perror("n < 0");
                    }
                }
            } 
            if (--nready <= 0) {
                break;
            }
        } //end for(0..maxi)
    } //end for(;;)
    return 0;
}
                          
                             
Select单进程非阻塞TCP echo服务器,布布扣,bubuko.com
原文:http://www.cnblogs.com/hancm/p/3797782.html