问题:使用 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
#include
#include
#include
#include
#include
#include
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() 系统特有的吗?