(二)io多路复用
上一篇中我们利用多线程实现了一个可以接受并处理多个连接的TCP服务器,这样的实现非常简单,又很好理解,但缺点是资源占用较高,因此我们尝试采用新的方法来实现
select
使用思路
select是比较老的多路复用函数,其使用的思路也很简单。首先我们需要知道,每个fd它可读、可写、出错都是可以知道的(即使我们不知道,Linux也可以知道),那么select利用的就是这一点
对于socket的fd,当有新的连接进来时,是可读状态,有新的数据可以读取,也是可读状态。那么我们将所有的fd都添加到select中,select将轮询这些fd,我们可以进行检查,当需要处理时进行相应的处理即可
实现思路
我们要轮询的是服务器socket fd和各个连接进来的客户端连接fd,我们可以简单使用一个数组来保存客户端fd,然后在循环中调用select
TODO
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/select.h>
#define RECV_PORT 63432
#define CONN_COUNT_MAX 10
#define RECV_BUFFER_MAX 1024
int main()
{
// 创建socket文件描述符
int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if (sock_fd == -1)
{
printf("socket failed: %s(%d)\n", strerror(errno), errno);
return -1;
}
// 绑定到本地IP的63432端口上
struct sockaddr_in laddr = {
.sin_family = AF_INET,
.sin_addr = htonl(INADDR_ANY),
.sin_port = htons(RECV_PORT),
};
if (bind(sock_fd, (struct sockaddr *)&laddr, sizeof(struct sockaddr_in)) < 0)
{
printf("bind failed: %s(%d)\n", strerror(errno), errno);
return -1;
}
printf("server at: %s:%d\n", inet_ntoa(laddr.sin_addr), ntohs(laddr.sin_port));
// 开始监听
if (listen(sock_fd, CONN_COUNT_MAX) == -1)
{
printf("listen failed: %s(%d)\n", strerror(errno), errno);
return -1;
}
int conn_fds[CONN_COUNT_MAX] = {0};
while (1)
{
// init fd_set
fd_set read_fds;
FD_ZERO(&read_fds);
// add server fd
FD_SET(sock_fd, &read_fds);
int max_fd = sock_fd;
// add client fd
for (int i = 0; i < CONN_COUNT_MAX; ++i)
{
if (conn_fds[i] == 0)
continue;
FD_SET(conn_fds[i], &read_fds);
max_fd = max_fd > conn_fds[i] ? max_fd : conn_fds[i];
}
int ret = select(max_fd + 1, &read_fds, NULL, NULL, NULL);
// accept sock_fd for new connection
if (FD_ISSET(sock_fd, &read_fds))
{
struct sockaddr_in raddr = {0};
socklen_t raddr_len = sizeof(struct sockaddr_in);
int conn_fd = accept(sock_fd, (struct sockaddr *)&raddr, &raddr_len);
if (conn_fd == -1)
{
printf("accept failed: %s\n", strerror(errno));
continue;
}
printf("new connection, remote: %s:%d\n", inet_ntoa(raddr.sin_addr), ntohs(raddr.sin_port));
// [fixme] check connection count
for (int i = 0; i < CONN_COUNT_MAX; ++i)
{
if (conn_fds[i] != 0)
continue;
conn_fds[i] = conn_fd;
break;
}
}
// check and recv from client
for (int i = 0; i < CONN_COUNT_MAX; ++i)
{
if (conn_fds[i] == 0)
continue;
if (!FD_ISSET(conn_fds[i], &read_fds))
continue;
char buffer[RECV_BUFFER_MAX] = {0};
int recv_len = recv(conn_fds[i], buffer, RECV_BUFFER_MAX, 0);
if (recv_len <= 0)
{
// [fixme] check detial
printf("disconnected\n");
close(conn_fds[i]);
conn_fds[i] = 0;
continue;
}
printf("recv: %s\n", buffer);
}
}
getchar();
}

浙公网安备 33010602011771号