浅析tcp中select使用(思路源于工作)

        虽然说poll和epoll要比select好很多,但是还是有很多地方select稳健运行着,而且select相关的资料也多。接下来就简单阐述下工作中route模块对这些的使用。

         在服务端使用tcp的一般流程是:socket->bind->listen->select->accept->read/write

         listen函数的第二个参数backlog值是表示期待连接的描述符的个数,假设传5,实际放列队的个数会稍微大于5,不同操作系统有不同的实现。放到三次握手流程里说,就是client发了个SYN过来和client发SYN+ACK,对应的server端状态是SYN_RECV和ESTABLISED。如果队列满了,client端调用connect请求就会返回-1。如果backlog不知道用什么值,可以用系统函数getenv("LISTENQ")取环境变量中设置的值。这个函数没有超时设置,不是阻塞,所以监控是否有连接请求是内核做的事情,存放描述符到队列也是内核做的,这个函数也就是触发内核要做的事情。

        接下来用select函数来获得可用的描述符。在收到connect的请求时,驱动通知内核,内核完成3次握手后把描述符放入数组中,select遍历数组就能拿到该描述符了(linux系统环境变量一般设置这个数组的大小是1024)。然后调用accept接收一个套接字中已建立的连接。这个连接是双全工管道,既可以读也可以写。但是,工作中有些模块不是这么用的。以工作中的Route模块为例,它是先accept,再select,总觉得哪里不对,为啥?因为跟教科书中不符。其实,仔细分析下,也是对的。理由如下:

       1.在listen之后,内核就在收集客户端连接请求。accept去接收一个已建立的连接,如果连接建立成功后,即长连接了,就创建一个新线程用于收发数据。每次收数据之前都会使用select来校验下描述符是否可用,如果是断开状态,就不收数据。

       2.如果select->accept后建一个线程去处理收发数据,那么recv数据时候就不知道描述符是否还是可用的了。如果接着再调用selec也是可以的。

       这块业务看似是一个线程一个链接,其实是一个线程多个tcp连接,还是用到了I/O复用。主线程接收N个客户端的连接,然后每个连接建立一个线程去处理收发数据。

      缺点是这个Route模块是单进程的,linux下最多可以接受1024个连接。如果修改环境变量把值变大,在连接数很大的时候,select会比较慢,影响效率。现在的方案都是在环境用中起好几个route程序,并做级联对应。而且处理收发数据的线程相当于是阻塞的。还有个缺点是一个系统所能创建的线程数量有限,而且线程在cpu中频繁切换也是耗费资源,达到某个阀值后性能就大大降低了。所以后来又做了个升级版。

     Route模块大致结构图如下:

                                     

主要就是解析数据然后转发。

# select的一个简单用法
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/select.h>
#include <unistd.h>
#include<netinet/in.h>
#include <iostream>
using namespace std;

const int PORT = 9000;
#define BACKLOG 5     // how many pending connections queue will hold
#define BUF_SIZE 1024

int
Listen(int fd, int backlog)
{
    char    *ptr;

        /*4can override 2nd argument with environment variable */
    if ( (ptr = getenv("LISTENQ")) != NULL)
        backlog = atoi(ptr);

    return listen(fd, backlog);
}

int main(int argc, char *argv[]) {

    // socket bind listen select accept read
    struct sockaddr_in addr;
    struct sockaddr_in cli_addr;
    int fd, newfd;
    int conn_amount = 0;
    char szBuf[BUF_SIZE];

    //protocol=0会自动选择协议
    if ((fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
        printf("socket调用发生错误\n");
        return -1;
    }

    int yes = 1;
    if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1) {
        printf("setsockopt错误\n");
        exit(1);
    }

    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = htonl(INADDR_ANY);
    addr.sin_port = htons(PORT);
    if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
        printf("bind调用发生错误\n");
        return -1;
    }

    if (Listen(fd, BACKLOG) == -1) {
        printf("listen调用发生错误\n");
        return -1;
    }

    fd_set fset;
    int ret = 0, maxfd = fd;
    struct timeval tv;
    int fd_A[BACKLOG] = {0};
    socklen_t cli_len = sizeof(cli_addr);
    printf("fd[%d]\n", fd);

    for (;;) {
        memset(szBuf, 0, sizeof(szBuf));
        FD_ZERO(&fset);
        FD_SET(fd, &fset);
        tv.tv_sec = 5;
        tv.tv_usec = 0;

        for (int i = 0; i < BACKLOG; i++) {
            if (fd_A[i] != 0)
                FD_SET(fd_A[i], &fset);
        }

        ret = select(maxfd+1, &fset, NULL, NULL, &tv);

        if (ret < 0) {
            printf("select调用发生错误\n");
            break;
        }
        else if (ret == 0) {
            printf("select timeout\n");
            continue;
        }
        else {
            printf("select normal\n");
        }

        for (int i = 0; i < BACKLOG; i++) {
            if (fd_A[i] && FD_ISSET(fd_A[i], &fset)) {
                printf("recv before\n");
                if ((ret = recv(fd_A[i], szBuf, sizeof(szBuf), 0)) == 0) {
                    close(fd_A[i]);
                    FD_CLR(fd_A[i], &fset);
                    fd_A[i] = 0;
                    conn_amount--;
                }
                else {
                    printf("fd_A[%d]:%s", i, szBuf);
                }
            }
        }

        if (FD_ISSET(fd, &fset)) {
            newfd = accept(fd, (struct sockaddr *)&cli_addr, &cli_len);
            if (newfd <= 0) {
                printf("accept出错\n");
                continue;
            }
            else
                printf("accept normal\n");

            if (conn_amount < BACKLOG - 1) {
                for (int i = 0; i <BACKLOG; i++) {
                    if (fd_A[i] == 0) {
                        fd_A[i] = newfd;
                        conn_amount++;
                        break;
                    }
                }

                if (newfd > maxfd) {
                    maxfd = newfd;
                }
            }
            else {
                send(newfd, "test", 5, 0);
                //close(newfd);
                continue;
            }
        }
    }

    //close all connections
    for (int i = 0; i < BACKLOG; i++)
    {
        if (fd_A[i] != 0)
        {
            close(fd_A[i]);
        }
    }

    return 0;
}
View Code

 

 

 

   

 

 

    

posted @ 2017-09-06 14:16  超龄码农  阅读(1035)  评论(0编辑  收藏  举报