1. 服务端不能接收多个客户端连接的瓶颈
默认情况下调用的accept和read函数都是阻塞的,所以server端的程序中当accept成功后,程序就会一直阻塞在recv函数处,当新的连接来到时,不能够直接调用accept产生用于实际通讯的套接字,所以不能完成新的连接。
client = accept(server, (struct sockaddr*)&caddr, &asize);//则塞
~~
do
{
int i = 0;
r = recv(client, buf, sizeof(buf), 0);//则塞
if( r > 0 )
{
len += r;
}
printf("recv %s", buf);
} while ( len <16 );
2. 解决方案
阻塞变轮询:使用select函数对套接字(服务端套接字、以及实际通讯的套接字)进行轮询检测,当检测到服务端套接字有连接到来时,调用accept;当检测到用于通讯的套接字有数据到来时,直接读取数据进行数据处理解析等应用
- 通过select系统函数首先监听服务端套接字server_fd,监听类型为“连接”(读);
- 当有事件发生时(客户端的连接事件),则调用accept函数,生成套接字(client_fd)进行连接;
- 将client_fd加入监听范围,目标事件为数据接收(即“读”)
- 调用select对被监听的文件描述符进行循环检测是否有事件
3. 实现方式
3.1 实现关键:动态调整需要监听的文件描述符
- 当接收到新的客户端连接时,将accept生成的文件描述符加入监听范围中(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);
}
3.2 代码
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netinet/in.h>
#include <arpa/inet.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
{
ret = -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);
}
}
}
}
}
}
close(server);
return 0;
}
如图所示服务端程序接收到了两个连接,且进行了回声处理功能