select实现并发服务器

select

select函数是一个用于在一组文件描述符上进行异步I/O多路复用的系统调用。它可以同时监视多个文件描述符,等待其中任何一个文件描述符准备就绪,然后进行相应的操作。

以下是select函数的原型:

#include <sys/select.h>

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

参数说明:

  • nfds:待监视的最大文件描述符加1。
  • readfds:指向一个可读文件描述符集合的指针,用于指定要监视读事件的文件描述符。
  • writefds:指向一个可写文件描述符集合的指针,用于指定要监视写事件的文件描述符。
  • exceptfds:指向一个异常文件描述符集合的指针,用于指定要监视异常事件的文件描述符。
  • timeout:指向一个表示超时时间的结构体指针,用于设置select的超时时间。如果为NULLselect将一直阻塞,直到有事件发生。

返回值:

  • 如果超时时间内有文件描述符就绪或有错误发生,select函数返回就绪文件描述符的总数。
  • 如果超时时间到达而没有文件描述符就绪,select函数返回0。
  • 如果出现错误,select函数返回-1,并设置errno来指示具体的错误类型。

select函数主要用于实现多路复用的I/O操作,它允许同时监视多个文件描述符,以避免使用阻塞式I/O时每个文件描述符都需要单独的线程。通过select函数,可以有效地管理并发连接、处理I/O事件和提高系统性能。

示例代码

下面是一个用C语言编写的使用select函数实现的并发服务器的示例代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>

#define MAX_CLIENTS 10
#define BUFFER_SIZE 1024

int main() {
    int server_fd, client_fds[MAX_CLIENTS], max_fd;
    struct sockaddr_in server_addr, client_addr;
    char buffer[BUFFER_SIZE];

    // 创建服务器套接字
    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd == -1) {
        perror("Socket creation failed");
        exit(EXIT_FAILURE);
    }

    // 初始化服务器地址结构
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = INADDR_ANY;
    server_addr.sin_port = htons(8080);

    // 绑定服务器套接字到指定地址和端口
    if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1) {
        perror("Bind failed");
        exit(EXIT_FAILURE);
    }

    // 监听连接请求
    if (listen(server_fd, MAX_CLIENTS) == -1) {
        perror("Listen failed");
        exit(EXIT_FAILURE);
    }

    printf("Server listening on port 8080\n");

    fd_set read_fds, all_fds;
    int i, fd;

    FD_ZERO(&all_fds);
    FD_SET(server_fd, &all_fds);
    max_fd = server_fd;

    // 初始化客户端套接字数组
    for (i = 0; i < MAX_CLIENTS; i++) {
        client_fds[i] = -1;
    }

    while (1) {
        read_fds = all_fds;

        // 使用 select 监听读事件
        if (select(max_fd + 1, &read_fds, NULL, NULL, NULL) == -1) {
            perror("Select failed");
            exit(EXIT_FAILURE);
        }

        // 检查服务器套接字是否有新连接
        if (FD_ISSET(server_fd, &read_fds)) {
            socklen_t addr_len = sizeof(client_addr);
            // 接受新连接
            int new_fd = accept(server_fd, (struct sockaddr *)&client_addr, &addr_len);
            if (new_fd == -1) {
                perror("Accept failed");
                exit(EXIT_FAILURE);
            }

            // 将新连接添加到客户端套接字数组
            for (i = 0; i < MAX_CLIENTS; i++) {
                if (client_fds[i] == -1) {
                    client_fds[i] = new_fd;
                    break;
                }
            }

            // 更新最大文件描述符
            if (new_fd > max_fd) {
                max_fd = new_fd;
            }

            printf("New client connected. Socket fd: %d, IP address: %s, Port: %d\n",
                   new_fd, inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
        }

        // 检查客户端套接字是否有数据可读
        for (i = 0; i < MAX_CLIENTS; i++) {
            fd = client_fds[i];
            if (fd != -1 && FD_ISSET(fd, &read_fds)) {
                // 读取客户端数据
                int bytes_read = read(fd, buffer, BUFFER_SIZE);
                                if (bytes_read <= 0) {
                    // 连接关闭或发生错误,移除客户端套接字
                    printf("Client disconnected. Socket fd: %d\n", fd);
                    close(fd);
                    FD_CLR(fd, &all_fds);
                    client_fds[i] = -1;
                } else {
                    // 处理客户端数据
                    printf("Received data from client. Socket fd: %d\n", fd);
                    // 在此处添加处理客户端数据的代码
                }
            }
        }
    }

    // 关闭服务器套接字
    close(server_fd);

    return 0;
}
06-16 12:16