问题:应用 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() 零碎特有的吗?