【C语言网络编程基础】TCP并发网络编程:一请求一线程模型

在实际开发中,一个 TCP 服务器往往要同时为多个客户端提供服务。最简单直观的方式,就是采用“一请求一线程”模型 —— 每当有客户端连接进来,服务器就创建一个新线程专门负责这个客户端的收发任务。

本文将介绍如何使用 C语言 + TCP + pthread 多线程 实现一个并发 TCP 服务器。

 一、TCP 服务器的典型通信流程

  1. 创建 socket

  2. 绑定 IP 和端口(bind)

  3. 开始监听连接请求(listen)

  4. 接收连接(accept)

  5. 接收与发送数据(recv / send)

  6. 关闭连接(close)

二、TCP服务器的一请求一线程模型

在基于 TCP 的服务端模型中,“一请求一线程”是最经典的并发模型之一。其核心原理如下:

  • TCP 是面向连接的协议,在通信前需先建立连接(3 次握手)。服务端需要长期监听一个端口,接收客户端的连接请求。

  • 每当有一个客户端连接,主线程使用 accept() 接收连接。

  • 为了不阻塞主线程,服务器为每个连接创建一个子线程(或进程)进行数据通信。

  • 子线程读取客户端请求,处理完成后关闭连接。

这一模型具备清晰的线程分离与逻辑隔离特性,便于调试与维护。

三、核心代码讲解

模块一:socket 初始化与监听

int sockfd = socket(AF_INET, SOCK_STREAM, 0);       // 创建一个 TCP socket,使用 IPv4 地址族,面向流的通信

struct sockaddr_in addr;                            // 定义一个 sockaddr_in 结构体,用于存储本地地址信息
memset(&addr, 0, sizeof(addr));                     // 初始化该结构体为 0,清除脏数据
addr.sin_family = AF_INET;                          // 设置地址族为 IPv4
addr.sin_port = htons(port);                        // 设置端口号,使用 htons 转为网络字节序
addr.sin_addr.s_addr = INADDR_ANY;                  // 接收任意网卡发来的连接请求(绑定 0.0.0.0)

bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)); // 将 socket 与本地地址绑定
listen(sockfd, 5);                                   // 开始监听客户端连接请求,最多允许排队 5 个连接
  • 使用 socket() 创建 TCP 套接字

  • 使用 bind() 将 socket 绑定到本地地址与端口

  • 使用 listen() 开始监听客户端连接请求

模块二:接收连接并创建线程

while (1) {
    struct sockaddr_in client_addr;                            // 用于保存客户端地址信息
    socklen_t client_len = sizeof(client_addr);                // 客户端地址结构大小

    int clientfd = accept(sockfd, (struct sockaddr*)&client_addr, &client_len); // 阻塞等待客户端连接,成功则返回新的 socket 文件描述符

    pthread_t thread_id;                                       // 定义线程 id
    pthread_create(&thread_id, NULL, client_routine, &clientfd); // 创建新线程,处理当前连接,传入 clientfd
}
  • accept():阻塞等待客户端连接

  • 每次连接成功后,用 pthread_create() 创建线程处理

  • 一连接一线程模型:每个连接独立分配线程,互不干扰

模块三:子线程处理函数

void *client_routine(void *arg) {
    int clientfd = *(int *)arg;                         // 获取客户端 socket 文件描述符

    while (1) {
        char buffer[1024] = {0};                        // 接收缓冲区,初始化为 0
        int len = recv(clientfd, buffer, 1024, 0);      // 从客户端读取数据

        if (len <= 0) {                                 // 如果读取失败或客户端关闭连接
            close(clientfd);                            // 关闭客户端连接
            break;                                      // 退出循环,线程结束
        }
        printf("Recv: %s, %d byte(s)\n", buffer, len);   // 打印接收到的消息内容和长度
    }
}
  • 使用 recv() 从客户端读取数据

  • 若客户端断开或读取错误,则 recv() 返回 0 或负值,关闭连接

  • 否则打印接收到的数据

四、TCP 并发服务器文字流程图

启动程序
   ↓
调用 socket() 创建监听 socket
   ↓
bind() 绑定 IP 和端口
   ↓
listen() 开始监听客户端连接
   ↓
========= 循环开始 =========
   ↓
accept() 等待并接受客户端连接
   ↓
为每个连接创建线程 pthread_create()
   ↓
→ 在线程中调用 recv() 接收客户端数据
→ 打印接收内容
→ 如果断开或错误则 close() 并退出线程
   ↓
========= 循环继续 =========

五、完整代码

#include 
#include 
#include 

#include 
#include 
#include 
#include 

#include 
#include 

#define BUFFER_LENGTH       1024

// 子线程函数:处理客户端通信
void *client_routine(void *arg){
    int clientfd = *(int *)arg;                 // 获取传入的客户端 socket 描述符

    while (1){
        char buffer[BUFFER_LENGTH] = {0};       // 接收缓冲区,初始化为0
        int len = recv(clientfd, buffer, BUFFER_LENGTH, 0); // 接收数据

        if(len <= 0){                           // 出错或客户端关闭连接
            close(clientfd);                    // 关闭连接socket
            break;                             // 退出线程
        } else {
            printf("Recv: %s, %d byte(s)\n", buffer, len);  // 打印接收到的数据及长度
        }
    }
    return NULL;
}

int main(int argc, char *argv[]){
    if (argc < 2){
        printf("Param Error\n");                // 端口参数未提供,退出
        return -1;
    }

    int port = atoi(argv[1]);                   // 获取监听端口号

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);  // 创建 TCP socket
    if(sockfd < 0){
        perror("socket");
        return -1;
    }

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));             // 初始化地址结构
    addr.sin_family = AF_INET;                   // IPv4
    addr.sin_port = htons(port);                 // 端口转网络字节序
    addr.sin_addr.s_addr = INADDR_ANY;           // 监听所有本机网卡地址

    if(bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)) < 0){  // 绑定地址和端口
        perror("bind");
        return 2;
    }

    if(listen(sockfd, 5) < 0){                   // 开始监听,允许最大5个等待队列
        perror("listen");
        return 3;
    }

    while (1){
        struct sockaddr_in client_addr;
        memset(&client_addr, 0, sizeof(client_addr));
        socklen_t client_len = sizeof(client_addr);

        int clientfd = accept(sockfd, (struct sockaddr*)&client_addr, &client_len); // 阻塞等待客户端连接
        if(clientfd < 0){
            perror("accept");
            continue;
        }

        pthread_t thread_id;
        pthread_create(&thread_id, NULL, client_routine, &clientfd); // 创建线程处理该客户端
        pthread_detach(thread_id);  // 线程分离,防止资源泄露
    }

    close(sockfd);
    return 0;
}

https://github.com/0voice

你可能感兴趣的:(网络,tcp/ip,网络协议)