问题:应用 select() 函数能够扩大服务端性能吗? 如果能够,具体怎么实现?


目前服务端的瓶颈剖析

服务端大多数时候处于期待状态,无奈施展主机(设施)的最大性能

while (1) {    // 阻塞,期待客户端连贯    client = accept(server, (struct sockaddr*)&caddr, &asize);    printf("client: %d\n", client);    do {        // 阻塞,期待客户端数据        r = recv(client, buf, sizeof(buf), 0);        if (r > 0) {            printf("Receive: %s\n", buf);            if (strcmp(buf, "quit") != 0) {                len = send(client, buf, r, 0);            }            else {                break;            }        }     } while (r > 0);    close(client);}

解决方案:阻塞变轮询

  • 通过 select() 函数首先监听服务端 server_fd, 指标事件为 “连贯”(读)
  • 当事件产生(客户端连贯),则调用 accept() 承受连贯
  • 将 client_fd 退出监听范畴,指标事件为“数据接管”(读)
  • 循环查看各个被监听的文件描述符是否有事件产生
实现形式

实现逻辑

while (1) {    rset = reads;    num = select(max + 1, &rset, 0, 0, &timeout);    if (num > 0) {        int i = 0;        for (i=1; i<=max; ++i) {  // 留神, 0 被命令行占用,下标从 1 开始遍历            if (FD_ISSET(i, &rset)) {                if (i == server) {                    // accept and add client to fd_set                } else {                    // read data from client by i (fd)                }            }        }    }}

实现要害

  • 动静调整须要监督的文件描述符

    • 当接管到客户端连贯时,将客户端文件描述符退出监听变量 (fd_set) 中
    • 当发现客户端断开时,在监听变量 (fd_set) 剔除客户端文件描述符
// 增加监听if (client > -1) {    FD_SET(client, &reads);    max = (client > max) ? client : max;    printf("client: %d\n", client);}// 剔除监听if (r == -1) {    FD_CLR(i, &reads);    close(i);}
  • 动静调整须要监督的文件描述符数量
  • 保障每个须要监督的文件描述符可能被轮询
  • max = (client > max) ? client : max

编程试验:改良后的服务端

#include <sys/types.h>#include <sys/socket.h>#include <sys/select.h>#include <netinet/in.h>#include <stdio.h>#include <unistd.h>#include <string.h>int server_handler (int server){    struct sockaddr_in addr = {0};    socklen_t asize = sizeof(addr);    return accept(server, (struct sockaddr*)&addr, &asize);}int client_handler(int client){    char buf[32] = {0};    int ret = read(client, buf, sizeof(buf) - 1);    if (ret > 0) {        buf[ret] = 0;        printf("Receive: %s\n", buf);        if (strcmp(buf, "quit") != 0) {            ret = write(client, buf, ret);        } else {            return -1;        }    }    return ret;}int main(){    int server = 0;    struct sockaddr_in saddr = {0};    int max = 0;    int num = 0;    fd_set reads = {0};    fd_set temps = {0};    struct timeval timeout = {0};    server = socket(PF_INET, SOCK_STREAM, 0);    if (server == -1) {        printf("server socket error\n");        return -1;    }    saddr.sin_family = AF_INET;    saddr.sin_addr.s_addr = htonl(INADDR_ANY);    saddr.sin_port = htons(8888);    if (bind(server, (struct sockaddr*)&saddr, sizeof(saddr)) == -1) {        printf("server bind error\n");        return -1;    }    if (listen(server, 1) == -1) {        printf("server listen error\n");        return -1;    }    printf("server start success\n");    FD_ZERO(&reads);    FD_SET(server, &reads);    max = server;    while (1) {        temps = reads;        timeout.tv_sec = 0;        timeout.tv_usec = 10000;        num = select(max+1, &temps, 0, 0, &timeout);        if (num > 0) {            int i = 0;            for (i=1; i<=max; ++i) {                if (FD_ISSET(i, &temps)) {                    if (i == server) {                        int client = server_handler(server);                        if (client > -1) {                            FD_SET(client, &reads);                            max = (client > max) ? client : max;                            printf("accept client: %d\n", client);                        }                    }                    else {                        int r = client_handler(i);                        if (r == -1) {                            FD_CLR(i, &reads);                            close(i);                        }                    }                }            }        }    }    return 0;}

思考:改良后的服务端是否还有优化的空间? select() 是 Linux() 零碎特有的吗?