Linux网络编程常见服务器模型详解

服务器模型概述

服务器模型决定如何处理并发连接,核心区别在于I/O处理方式和并发策略。主要分为:

  1. 阻塞式模型(单进程/线程)
  2. 多进程/多线程模型
  3. I/O多路复用模型
  4. 异步I/O模型

一、基本服务器模型
1. 单进程服务器

工作原理:

  • 单线程顺序处理请求
  • 阻塞式I/O操作
  • 一次只能服务一个客户端

框架概念:

accept
创建socket
绑定端口
监听端口
等待连接
处理请求
关闭连接

C语言实现:

// 服务端
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define PORT 8888
#define BUFFER_SIZE 1024

int main() {
    int server_fd, new_socket;
    struct sockaddr_in address;
    int addrlen = sizeof(address);
    char buffer[BUFFER_SIZE] = {0};
    
    // 创建socket
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }
    
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);
    
    // 绑定端口
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }
    
    // 监听
    if (listen(server_fd, 3) < 0) {
        perror("listen");
        exit(EXIT_FAILURE);
    }
    
    while(1) {
        printf("Waiting for connection...\n");
        if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
            perror("accept");
            continue;
        }
        
        // 处理请求
        read(new_socket, buffer, BUFFER_SIZE);
        printf("Received: %s\n", buffer);
        send(new_socket, buffer, strlen(buffer), 0);
        close(new_socket);
    }
    return 0;
}

客户端代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define PORT 8888

int main() {
    int sock = 0;
    struct sockaddr_in serv_addr;
    char *hello = "Hello from client";
    char buffer[1024] = {0};
    
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        printf("\n Socket creation error \n");
        return -1;
    }
    
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);
    
    if(inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) {
        printf("\nInvalid address\n");
        return -1;
    }
    
    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        printf("\nConnection Failed \n");
        return -1;
    }
    
    send(sock, hello, strlen(hello), 0);
    read(sock, buffer, 1024);
    printf("Server: %s\n", buffer);
    return 0;
}

优缺点:

  • ✅ 优点:实现简单,资源消耗少
  • ❌ 缺点:无法并发,客户端需排队等待

2. 多进程服务器

工作原理:

  • 主进程接受连接
  • 为每个客户端fork子进程处理请求
  • 父子进程独立地址空间

框架概念:

主进程
accept连接
fork子进程
子进程处理请求
退出进程

C语言实现:

// 服务端
#include <sys/wait.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define PORT 8888
#define BUFFER_SIZE 1024

void handle_client(int client_socket) {
    char buffer[1024];
    read(client_socket, buffer, 1024);
    printf("recv client buffer = %s\n",buffer);
    send(client_socket, buffer, strlen(buffer), 0);
    close(client_socket);
    exit(0);
}

int main() {
    int server_fd, new_socket;
    struct sockaddr_in address;
    int addrlen = sizeof(address);
    char buffer[BUFFER_SIZE] = {0};

    // 创建socket
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);

    // 绑定端口
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    // 监听
    if (listen(server_fd, 3) < 0) {
        perror("listen");
        exit(EXIT_FAILURE);
    }

    signal(SIGCHLD, SIG_IGN);  // 防止僵尸进程

    while(1) {
        int new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen);
        pid_t pid = fork();
        if (pid == 0) {  // 子进程
            close(server_fd);
            handle_client(new_socket);
        } else {  // 父进程
            close(new_socket);
        }
    }
}

优缺点:

  • ✅ 优点:真正并发处理
  • ❌ 缺点:进程创建开销大,进程间通信复杂

3. 多线程服务器

工作原理:

  • 主线程接受连接
  • 为每个客户端创建线程
  • 共享进程地址空间

框架概念:

主线程
accept连接
创建新线程
线程处理请求
线程退出

C语言实现:

// 服务端
#include <pthread.h>
#include <sys/wait.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define PORT 8888
#define BUFFER_SIZE 1024

void *client_handler(void *arg) {
    int sock = *(int*)arg;
    char buffer[1024];
    read(sock, buffer, 1024);
    printf("recv client buffer = %s\n",buffer);
    send(sock, buffer, strlen(buffer), 0);
    close(sock);
    pthread_exit(NULL);
}

int main() {
    int server_fd, new_socket;
    struct sockaddr_in address;
    int addrlen = sizeof(address);
    char buffer[BUFFER_SIZE] = {0};

    // 创建socket
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);

    // 绑定端口
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    // 监听
    if (listen(server_fd, 3) < 0) {
        perror("listen");
        exit(EXIT_FAILURE);
    }

    while(1) {
        int *new_sock = malloc(sizeof(int));
        *new_sock = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen);

        pthread_t thread;
        pthread_create(&thread, NULL, client_handler, (void*)new_sock);
        pthread_detach(thread);
    }
}

优缺点:

  • ✅ 优点:线程开销小于进程
  • ❌ 缺点:需处理线程同步,线程过多影响性能

4. 线程池模型

工作原理:

  • 预创建N个工作线程
  • 任务队列存储客户端连接
  • 线程从队列取任务处理

框架概念:

添加任务
获取任务
主线程
任务队列
线程池
处理请求

C语言实现:

#include <pthread.h>
#include <semaphore.h>
#include <pthread.h>
#include <sys/wait.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define PORT 8888
#define BUFFER_SIZE 1024
#define THREAD_POOL_SIZE 5

typedef struct {
    int client_sock;
} Task;

Task task_queue[256];
int queue_size = 0;
pthread_mutex_t queue_mutex = PTHREAD_MUTEX_INITIALIZER;
sem_t queue_sem;

void *worker_thread(void *arg) {
    while(1) {
        sem_wait(&queue_sem);

        pthread_mutex_lock(&queue_mutex);
        Task task = task_queue[--queue_size];
        pthread_mutex_unlock(&queue_mutex);

        // 处理请求
        char buffer[1024];
        read(task.client_sock, buffer, 1024);
        printf("recv client buffer = %s\n",buffer);
        send(task.client_sock, buffer, strlen(buffer), 0);
        close(task.client_sock);
    }
    return NULL;
}

int main() {
    int server_fd, new_socket;
    struct sockaddr_in address;
    int addrlen = sizeof(address);
    char buffer[BUFFER_SIZE] = {0};

    // 创建socket
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);

    // 绑定端口
    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    // 监听
    if (listen(server_fd, 3) < 0) {
        perror("listen");
        exit(EXIT_FAILURE);
    }

    sem_init(&queue_sem, 0, 0);

    // 创建线程池
    pthread_t pool[THREAD_POOL_SIZE];
    for(int i=0; i<THREAD_POOL_SIZE; i++) {
        pthread_create(&pool[i], NULL, worker_thread, NULL);
    }

    while(1) {
        int client_sock = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen);

        pthread_mutex_lock(&queue_mutex);
        task_queue[queue_size++].client_sock = client_sock;
        pthread_mutex_unlock(&queue_mutex);

        sem_post(&queue_sem);  // 通知工作线程
    }
}

优缺点:

  • ✅ 优点:减少线程创建开销,控制并发量
  • ❌ 缺点:实现复杂,需任务队列管理

二、I/O多路复用服务器模型

核心概念

工作原理:

  • 单线程监控多个文件描述符
  • 当fd就绪时进行I/O操作
  • 事件驱动模式

优势:

  • 高并发场景下资源消耗低
  • 避免进程/线程切换开销

1. select模型

工作原理:
通过fd_set监控文件描述符集合,超时阻塞等待事件发生。

框架流程:

是
超时
初始化fd_set
添加监听fd
select阻塞
有事件?
遍历fd_set处理

C语言实现:

#include <sys/select.h>

int main() {
    // ...socket初始化...
    fd_set readfds;
    int max_sd = server_fd;
    
    while(1) {
        FD_ZERO(&readfds);
        FD_SET(server_fd, &readfds);
        
        // 添加已连接客户端
        for(int i=0; i<MAX_CLIENTS; i++) {
            if(client_sockets[i] > 0) FD_SET(client_sockets[i], &readfds);
            if(client_sockets[i] > max_sd) max_sd = client_sockets[i];
        }
        
        int activity = select(max_sd+1, &readfds, NULL, NULL, NULL);
        
        if(FD_ISSET(server_fd, &readfds)) {
            int new_socket = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen);
            // 添加到客户端数组
        }
        
        for(int i=0; i<MAX_CLIENTS; i++) {
            if(FD_ISSET(client_sockets[i], &readfds)) {
                // 处理客户端请求
            }
        }
    }
}

优缺点:

  • ✅ 优点:跨平台支持
  • ❌ 缺点:fd_set大小受限(通常1024),需遍历所有fd

2. poll模型

工作原理:
使用pollfd结构数组替代fd_set,无文件描述符数量限制。

框架流程:

是
初始化pollfd数组
设置监听事件
poll阻塞
有事件?
遍历数组处理

C语言实现:

#include <poll.h>

int main() {
    struct pollfd fds[MAX_CLIENTS+1];
    // 初始化server_fd
    fds[0].fd = server_fd;
    fds[0].events = POLLIN;
    
    int nfds = 1;
    
    while(1) {
        int ret = poll(fds, nfds, -1);
        if(fds[0].revents & POLLIN) {
            int new_socket = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen);
            fds[nfds].fd = new_socket;
            fds[nfds].events = POLLIN;
            nfds++;
        }
        
        for(int i=1; i<nfds; i++) {
            if(fds[i].revents & POLLIN) {
                // 处理客户端
            }
        }
    }
}

优缺点:

  • ✅ 优点:无fd数量限制
  • ❌ 缺点:仍需遍历所有fd,性能随fd数量线性下降

3. epoll模型

工作原理:
Linux特有高效模型,使用事件驱动机制,仅返回就绪事件。

核心API:

  • epoll_create() 创建实例
  • epoll_ctl() 管理fd
  • epoll_wait() 等待事件

框架流程:

epoll_create
epoll_ctl添加fd
epoll_wait阻塞
处理就绪事件

C语言实现:

#include <sys/epoll.h>

int main() {
    int epoll_fd = epoll_create1(0);
    struct epoll_event event, events[MAX_EVENTS];
    
    // 添加server_fd
    event.events = EPOLLIN;
    event.data.fd = server_fd;
    epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event);
    
    while(1) {
        int n = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
        for(int i=0; i<n; i++) {
            if(events[i].data.fd == server_fd) {
                int new_socket = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen);
                // 添加到epoll
            } else {
                // 处理客户端数据
            }
        }
    }
}

优缺点:

  • ✅ 优点:O(1)时间复杂度,百万级并发支持
  • ❌ 缺点:Linux特有,编程较复杂

4. libevent库

工作原理:
跨平台事件通知库,封装底层I/O复用机制,提供统一API。

核心组件:

  • event_base 事件基座
  • event 事件处理器
  • bufferevent 带缓冲的事件

C语言实现:

#include <event2/event.h>
#include <event2/listener.h>

void echo_cb(struct bufferevent *bev, void *ctx) {
    char buffer[1024];
    int n = bufferevent_read(bev, buffer, sizeof(buffer));
    bufferevent_write(bev, buffer, n);
}

void accept_cb(struct evconnlistener *listener, evutil_socket_t fd,
    struct sockaddr *address, int socklen, void *ctx) {
    struct event_base *base = evconnlistener_get_base(listener);
    struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);
    bufferevent_setcb(bev, echo_cb, NULL, NULL, NULL);
    bufferevent_enable(bev, EV_READ | EV_WRITE);
}

int main() {
    struct event_base *base = event_base_new();
    struct sockaddr_in sin = {
        .sin_family = AF_INET,
        .sin_port = htons(8888)
    };
    
    struct evconnlistener *listener = evconnlistener_new_bind(
        base, accept_cb, NULL, LEV_OPT_REUSEABLE, -1,
        (struct sockaddr*)&sin, sizeof(sin));
    
    event_base_dispatch(base);
    return 0;
}

优缺点:

  • ✅ 优点:跨平台,自动选择最佳I/O复用机制
  • ❌ 缺点:需学习新API,依赖第三方库

模型对比总结

模型类型并发能力资源消耗实现复杂度适用场景
单进程★☆☆☆☆★☆☆☆☆★☆☆☆☆低并发测试
多进程★★★☆☆★★★★☆★★☆☆☆CPU密集型任务
多线程★★★★☆★★★☆☆★★★☆☆I/O密集型任务
线程池★★★★☆★★★☆☆★★★★☆稳定并发场景
select★★☆☆☆★★★☆☆★★★☆☆跨平台低并发
poll★★★☆☆★★★☆☆★★★☆☆中等并发
epoll★★★★★★☆☆☆☆★★★★☆Linux高并发
libevent★★★★★★★☆☆☆★★★☆☆跨平台高并发

性能建议:

  1. Linux平台首选epoll
  2. 跨平台项目用libevent
  3. 短连接服务用线程池
  4. 长连接用I/O多路复用
  5. 简单应用用select/poll

所有代码均可在Linux系统编译运行:

# 编译服务端
gcc server.c -o server -lpthread  # 多线程/线程池需要
gcc server.c -o server -levent   # libevent需要

# 编译客户端
gcc client.c -o client

# 运行
./server
./client

更多推荐