zoukankan      html  css  js  c++  java
  • 浅析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调用发生错误
    ");
            return -1;
        }
    
        int yes = 1;
        if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)) == -1) {
            printf("setsockopt错误
    ");
            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调用发生错误
    ");
            return -1;
        }
    
        if (Listen(fd, BACKLOG) == -1) {
            printf("listen调用发生错误
    ");
            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]
    ", 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调用发生错误
    ");
                break;
            }
            else if (ret == 0) {
                printf("select timeout
    ");
                continue;
            }
            else {
                printf("select normal
    ");
            }
    
            for (int i = 0; i < BACKLOG; i++) {
                if (fd_A[i] && FD_ISSET(fd_A[i], &fset)) {
                    printf("recv before
    ");
                    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出错
    ");
                    continue;
                }
                else
                    printf("accept normal
    ");
    
                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

       

        

  • 相关阅读:
    HTTP状态码
    HTTP详解教程 / HTTP 响应头信息 HTTP 响应头信息
    HTTP请求方法
    HTTP 消息结构
    HTTP 简介
    Session 工作原理
    CSS 布局
    css float 浮动
    CSS 布局
    css position定位
  • 原文地址:https://www.cnblogs.com/ikel/p/7483841.html
Copyright © 2011-2022 走看看