(二)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();
}
posted @ 2024-10-26 22:02  I加加  阅读(13)  评论(0)    收藏  举报