TCP Socket 编程实战:从单连接到线程池,打造高可用网络服务

        TCP(传输控制协议)作为面向连接、可靠的传输层协议,是大多数网络应用的基石。本文基于实战视角,从最基础的 TCP 回显服务器入手,逐步解决 “单连接阻塞” 问题,通过多进程、多线程、线程池三种方案优化并发能力,最终实现支持远程命令执行的 TCP 服务,帮你彻底掌握 TCP Socket 编程的核心逻辑与进阶技巧。

一、TCP 编程基础:核心 API 与连接流程

TCP 与 UDP 的 “无连接” 特性不同,其通信需经过 “三次握手建立连接→数据传输→四次挥手关闭连接” 的完整流程。首先需掌握支撑这一流程的关键 Socket API。

1.1 核心 API 详解

TCP 编程依赖的核心函数均定义在<sys/socket.h>中,各函数的功能与参数如下:

API功能关键参数说明返回值
socket()创建网络通信端点(文件描述符)domain=AF_INET(IPv4)、type=SOCK_STREAM(TCP 流)、protocol=0(默认协议)成功返回文件描述符(≥0),失败返回 - 1
bind()将 socket 绑定到固定 IP 和端口sockfd(socket 描述符)、addr(本地地址结构)、addrlen(地址结构长度)成功返回 0,失败返回 - 1
listen()将 socket 设为监听状态,等待客户端连接sockfd(socket 描述符)、backlog(最大等待连接数,通常设 5-10)成功返回 0,失败返回 - 1
accept()接受客户端连接请求(阻塞)sockfd(监听 socket)、addr(传出客户端地址)、addrlen(地址长度,传入传出)成功返回新的通信 socket,失败返回 - 1
connect()客户端发起连接请求sockfd(客户端 socket)、addr(服务器地址结构)、addrlen(地址长度)成功返回 0,失败返回 - 1
read()/write()读写 TCP 数据(基于文件描述符)sockfd(通信 socket)、buf(数据缓冲区)、count(数据长度)成功返回读写字节数,失败返回 - 1,read返回 0 表示对端关闭

1.2 TCP 通信流程(服务器 - 客户端)

TCP 的通信流程是 “固定套路”,理解这一流程是编写代码的前提:

  1. 服务器端

    • 调用socket()创建监听 socket(_listensock);
    • 调用bind()将监听 socket 绑定到固定端口;
    • 调用listen()将监听 socket 设为 “监听状态”;
    • 循环调用accept(),阻塞等待客户端连接,成功后返回 “通信 socket”(connfd);
    • 通过read()/write()与客户端通信;
    • 通信结束后,关闭connfd
  2. 客户端

    • 调用socket()创建客户端 socket;
    • 调用connect()发起连接(无需手动bind,操作系统自动分配随机端口);
    • 通过read()/write()与服务器通信;
    • 通信结束后,关闭客户端 socket。

1.3 关键注意点

        地址结构初始化:绑定地址时需将struct sockaddr_in清零,并设置sin_family=AF_INETsin_port=htons(端口)(主机字节序转网络字节序)、sin_addr.s_addr=htonl(INADDR_ANY)(绑定所有网卡 IP)。

  SO_REUSEADDR选项:通过setsockopt()设置该选项,避免 “端口占用” 错误(服务重启时,旧连接的 TIME_WAIT 状态会占用端口)。

  accept()的返回值accept()返回的是 “新的通信 socket”,监听 socket(_listensock)仅用于接受连接,不参与数据传输。

二、V1 版本:基础 TCP 回显服务器(单连接)

回显服务器的核心功能是 “接收客户端消息,原样返回”,通过这个案例可掌握 TCP 编程的基础框架。

2.1 核心代码实现

首先定义nocopy基类避免对象拷贝(与 UDP 案例一致,防止 socket 重复关闭),再实现 TCP 服

// nocopy.hpp(禁止拷贝基类)
#pragma once
class nocopy {
public:
    nocopy() = default;
    ~nocopy() = default;
    // 禁止拷贝构造和赋值运算符
    nocopy(const nocopy&) = delete;
    const nocopy& operator=(const nocopy&) = delete;
};

// Comm.hpp(错误码与地址转换宏)
#pragma once
#include <sys/socket.h>
#include <netinet/in.h>
// 错误码定义
enum {
    Usage_Err = 1,
    Socket_Err,
    Bind_Err,
    Listen_Err
};
// 地址结构转换宏(struct sockaddr_in* → struct sockaddr*)
#define CONV(addr_ptr) ((struct sockaddr *)addr_ptr)

// TcpServer.hpp(V1版本:单连接回显服务器)
#pragma once
#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "nocopy.hpp"
#include "Comm.hpp"

const static int default_backlog = 6; // 最大等待连接数

class TcpServer : public nocopy {
public:
    TcpServer(uint16_t port) : _port(port), _isrunning(false), _listensock(-1) {}

    // 初始化:创建socket → 绑定 → 监听
    void Init() {
        // 1. 创建监听socket
        _listensock = socket(AF_INET, SOCK_STREAM, 0);
        if (_listensock < 0) {
            std::cerr << "socket error: " << strerror(errno) << std::endl;
            exit(Socket_Err);
        }

        // 设置SO_REUSEADDR,避免端口占用
        int opt = 1;
        setsockopt(_listensock, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));
        std::cout << "create socket success, sockfd: " << _listensock << std::endl;

        // 2. 绑定地址(IP + 端口)
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;         // IPv4
        local.sin_port = htons(_port);      // 端口转网络字节序
        local.sin_addr.s_addr = htonl(INADDR_ANY); // 绑定所有网卡IP

        if (bind(_listensock, CONV(&local), sizeof(local)) != 0) {
            std::cerr << "bind error: " << strerror(errno) << std::endl;
            close(_listensock);
            exit(Bind_Err);
        }
        std::cout << "bind socket success, sockfd: " << _listensock << std::endl;

        // 3. 设为监听状态
        if (listen(_listensock, default_backlog) != 0) {
            std::cerr << "listen error: " << strerror(errno) << std::endl;
            close(_listensock);
            exit(Listen_Err);
        }
        std::cout << "listen socket success, sockfd: " << _listensock << std::endl;
    }

    // 服务逻辑:接收客户端消息并回显
    void Service(int connfd) {
        char buffer[1024];
        while (true) {
            // 读取客户端消息(阻塞)
            ssize_t n = read(connfd, buffer, sizeof(buffer) - 1);
            if (n > 0) {
                buffer[n] = '\0'; // 确保字符串结束
                std::cout << "client say# " << buffer << std::endl;

                // 回显消息给客户端
                std::string echo_msg = "server echo# " + std::string(buffer);
                write(connfd, echo_msg.c_str(), echo_msg.size());
            } else if (n == 0) {
                // read返回0 → 客户端关闭连接
                std::cout << "client quit..." << std::endl;
                break;
            } else {
                // 读取出错
                std::cerr << "read error: " << strerror(errno) << std::endl;
                break;
            }
        }
        close(connfd); // 关闭通信socket
    }

    // 启动服务:循环接受连接
    void Start() {
        _isrunning = true;
        while (_isrunning) {
            struct sockaddr_in peer; // 客户端地址
            socklen_t peer_len = sizeof(peer);

            // 接受客户端连接(阻塞,成功返回通信socket)
            int connfd = accept(_listensock, CONV(&peer), &peer_len);
            if (connfd < 0) {
                std::cerr << "accept error: " << strerror(errno) << std::endl;
                continue;
            }
            std::cout << "accept success, new connfd: " << connfd << std::endl;

            // 处理当前连接(单连接阻塞,无法处理其他连接)
            Service(connfd);
        }
        close(_listensock); // 关闭监听socket
    }

private:
    uint16_t _port;        // 服务端口
    int _listensock;       // 监听socket描述符
    bool _isrunning;       // 服务运行状态
};

2.2 客户端实现

客户端逻辑简单:创建 socket → 发起连接 → 循环发送 / 接收消息:

// TcpClient.cpp
#include <iostream>
#include <string>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "Comm.hpp"

// 打印使用帮助
void Usage(const std::string& process) {
    std::cout << "Usage: " << process << " server_ip server_port" << std::endl;
}

int main(int argc, char* argv[]) {
    if (argc != 3) {
        Usage(argv[0]);
        return Usage_Err;
    }

    // 解析服务器地址
    std::string server_ip = argv[1];
    uint16_t server_port = std::stoi(argv[2]);

    // 1. 创建客户端socket
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        std::cerr << "socket error: " << strerror(errno) << std::endl;
        return Socket_Err;
    }

    // 2. 发起连接(无需手动bind,OS自动分配端口)
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(server_port);
    inet_pton(AF_INET, server_ip.c_str(), &server.sin_addr); // 字符串IP转网络字节序

    if (connect(sockfd, CONV(&server), sizeof(server)) < 0) {
        std::cerr << "connect error: " << strerror(errno) << std::endl;
        close(sockfd);
        return 1;
    }

    // 3. 循环发送/接收消息
    std::string msg;
    char buffer[1024];
    while (true) {
        std::cout << "Please Enter# ";
        std::getline(std::cin, msg);

        // 发送消息给服务器
        write(sockfd, msg.c_str(), msg.size());

        // 接收服务器回显
        ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);
        if (n > 0) {
            buffer[n] = '\0';
            std::cout << "server echo: " << buffer << std::endl;
        } else if (n == 0 || n < 0) {
            // 服务器关闭或读取出错
            std::cout << "server disconnected..." << std::endl;
            break;
        }
    }

    close(sockfd);
    return 0;
}

2.3 测试与问题

  1. 测试步骤

    • 启动服务器:./tcp_server 8888
    • 启动客户端:./tcp_client 127.0.0.1 8888
    • 客户端输入消息,服务器会原样回显。
  2. 核心问题:V1 版本是 “单连接阻塞” 模型 ——Service(connfd)会阻塞处理当前客户端,直到客户端关闭,期间无法接受新的连接。若同时启动多个客户端,只有第一个客户端能正常通信,其他客户端会卡在 “连接等待” 状态。

三、V2 版本:多进程 TCP 服务器(解决并发)

为解决 “单连接阻塞” 问题,V2 版本通过 “ fork 子进程” 的方式,让每个客户端连接由独立的子进程处理,主进程仅负责接受连接。

3.1 核心思路

        主进程:循环调用accept()接受连接,每接受一个连接就fork()一个子进程;

        子进程:继承主进程的connfd,负责处理该客户端的通信逻辑,处理完后关闭connfd并退出;

        孙子进程优化:子进程fork()后立即退出,让孙子进程成为 “孤儿进程”(由系统进程领养),避免主进程等待子进程回收(减少僵尸进程)。

3.2 关键代码修改

在 V1 基础上,新增ProcessConnection()函数处理连接的 “进程创建” 逻辑,同时引入InetAddr类方便打印客户端地址:

// InetAddr.hpp(客户端地址封装)
#pragma once
#include <iostream>
#include <string>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

class InetAddr {
public:
    InetAddr(struct sockaddr_in& addr) : _addr(addr) {
        _ip = inet_ntoa(addr.sin_addr);       // 网络字节序IP转字符串
        _port = ntohs(addr.sin_port);         // 网络字节序端口转主机字节序
    }

    // 获取IP和端口
    std::string Ip() const { return _ip; }
    uint16_t Port() const { return _port; }

    // 打印客户端地址(如"127.0.0.1:54321")
    std::string PrintDebug() const {
        return _ip + ":" + std::to_string(_port);
    }

private:
    std::string _ip;               // 客户端IP字符串
    uint16_t _port;                // 客户端端口(主机字节序)
    struct sockaddr_in _addr;      // 原生地址结构
};

// TcpServer.hpp(V2版本:多进程修改)
#include "InetAddr.hpp"
#include <sys/wait.h> // 用于waitpid()

// 新增:处理连接——创建子进程处理客户端
void ProcessConnection(int connfd, struct sockaddr_in& peer) {
    pid_t id = fork(); // 主进程fork子进程
    if (id < 0) {
        // fork失败,关闭连接
        std::cerr << "fork error: " << strerror(errno) << std::endl;
        close(connfd);
        return;
    } else if (id == 0) {
        // 子进程:关闭监听socket(子进程无需监听)
        close(_listensock);

        // 子进程再fork,让孙子进程处理业务(避免僵尸进程)
        if (fork() > 0) {
            exit(0); // 子进程退出,孙子进程成为孤儿进程
        }

        // 孙子进程:处理客户端通信
        InetAddr client_addr(peer);
        std::cout << "new client connected: " << client_addr.PrintDebug() << std::endl;
        Service(connfd); // 复用V1的Service逻辑
        close(connfd);
        exit(0); // 处理完后退出孙子进程
    } else {
        // 主进程:关闭通信socket(主进程仅负责accept)
        close(connfd);
        // 非阻塞回收子进程(避免僵尸进程)
        waitpid(id, nullptr, WNOHANG);
    }
}

// 修改Start()函数:调用ProcessConnection处理连接
void Start() {
    _isrunning = true;
    while (_isrunning) {
        struct sockaddr_in peer;
        socklen_t peer_len = sizeof(peer);
        int connfd = accept(_listensock, CONV(&peer), &peer_len);
        if (connfd < 0) {
            std::cerr << "accept error: " << strerror(errno) << std::endl;
            continue;
        }

        // 交给子进程处理,主进程继续accept
        ProcessConnection(connfd, peer);
    }
    close(_listensock);
}

3.3 测试与问题

  1. 测试效果:启动多个客户端,每个客户端都能独立与服务器通信,主进程可实时接受新连接。

  2. 核心问题:进程创建的 “开销大”—— 每个连接对应一个进程,进程的内存占用、上下文切换成本远高于线程,在高并发场景(如千级连接)下会导致系统资源耗尽。

四、V3 版本:多线程 TCP 服务器(轻量级并发)

为降低并发开销,V3 版本用 “线程” 替代 “进程”—— 每个客户端连接由独立的线程处理,线程的内存共享特性和轻量级上下文切换能支持更高并发。

4.1 核心思路

        主进程:循环accept()接受连接,每接受一个连接就创建一个线程;

        线程:通过 “线程数据结构”(ThreadData)传递connfd和客户端地址,处理完通信后关闭connfd并释放资源;

        线程分离:通过pthread_detach()设置线程分离,避免主进程调用pthread_join()回收线程(减少阻塞)。

4.2 关键代码修改

在 V2 基础上,修改ProcessConnection()为线程创建逻辑,新增ThreadData结构体传递线程参数:

// TcpServer.hpp(V3版本:多线程修改)
#include <pthread.h> // 线程相关头文件

// 线程数据结构:传递connfd和客户端地址
class ThreadData {
public:
    ThreadData(int sockfd, struct sockaddr_in addr) 
        : _sockfd(sockfd), _addr(addr) {}
    ~ThreadData() = default;

    int _sockfd;                  // 通信socket描述符
    InetAddr _addr;               // 客户端地址
};

// 新增:线程执行函数(必须是void*返回值,void*参数)
static void* ThreadExecute(void* args) {
    // 设置线程分离,无需主进程回收
    pthread_detach(pthread_self());

    // 解析线程参数
    ThreadData* td = static_cast<ThreadData*>(args);
    if (td == nullptr) return nullptr;

    // 处理客户端通信(复用Service逻辑,需修改Service参数)
    Service(td->_sockfd, td->_addr);

    // 释放资源
    close(td->_sockfd);
    delete td;
    return nullptr;
}

// 修改Service函数:接收客户端地址,打印日志
void Service(int connfd, InetAddr client_addr) {
    char buffer[1024];
    while (true) {
        ssize_t n = read(connfd, buffer, sizeof(buffer) - 1);
        if (n > 0) {
            buffer[n] = '\0';
            std::cout << "client[" << client_addr.PrintDebug() << "] say# " << buffer << std::endl;

            std::string echo_msg = "server echo# " + std::string(buffer);
            write(connfd, echo_msg.c_str(), echo_msg.size());
        } else if (n == 0) {
            std::cout << "client[" << client_addr.PrintDebug() << "] quit..." << std::endl;
            break;
        } else {
            std::cerr << "read error from client[" << client_addr.PrintDebug() << "]: " << strerror(errno) << std::endl;
            break;
        }
    }
}

// 修改ProcessConnection:创建线程处理连接
void ProcessConnection(int connfd, struct sockaddr_in& peer) {
    // 创建线程数据
    ThreadData* td = new ThreadData(connfd, peer);
    if (td == nullptr) {
        std::cerr << "new ThreadData error" << std::endl;
        close(connfd);
        return;
    }

    // 创建线程
    pthread_t tid;
    if (pthread_create(&tid, nullptr, ThreadExecute, td) != 0) {
        std::cerr << "pthread_create error: " << strerror(errno) << std::endl;
        delete td;
        close(connfd);
        return;
    }
    std::cout << "create thread for client[" << td->_addr.PrintDebug() << "], tid: " << tid << std::endl;
}

4.3 扩展:远程命令执行服务

基于多线程模型,可快速扩展业务逻辑 —— 将 “回显” 改为 “执行客户端发送的命令并返回结果”。新增Command类封装命令执行逻辑:

// Command.hpp(命令执行封装)
#pragma once
#include <iostream>
#include <string>
#include <set>
#include <unistd.h>
#include <stdio.h> // 用于popen()/pclose()

class Command {
public:
    Command(int sockfd) : _sockfd(sockfd) {
        // 安全措施:仅允许执行指定的安全命令
        _safe_commands = {"ls", "pwd", "ls -l", "ll", "who", "whoami", "touch"};
    }

    // 检查命令是否安全(避免恶意命令如"rm -rf /")
    bool IsSafe(const std::string& cmd) const {
        return _safe_commands.count(cmd) > 0;
    }

    // 接收客户端发送的命令
    std::string RecvCommand() const {
        char buf[1024];
        ssize_t n = read(_sockfd, buf, sizeof(buf) - 1);
        if (n > 0) {
            buf[n] = '\0';
            return buf;
        }
        return "";
    }

    // 执行命令并返回结果
    std::string Execute(const std::string& cmd) const {
        if (!IsSafe(cmd)) return "error: unsafe command\n";

        // 使用popen执行命令,读取输出
        FILE* fp = popen(cmd.c_str(), "r");
        if (fp == nullptr) return "error: execute command failed\n";

        char buf[1024];
        std::string result;
        while (fgets(buf, sizeof(buf), fp) != nullptr) {
            result += buf;
        }
        pclose(fp);

        // 无结果时返回提示
        return result.empty() ? "execute success (no output)\n" : result;
    }

    // 发送命令执行结果给客户端
    void SendResult(const std::string& result) const {
        write(_sockfd, result.c_str(), result.size());
    }

private:
    int _sockfd;                          // 通信socket
    std::set<std::string> _safe_commands; // 安全命令集合
};

// 修改Service函数:替换为命令执行逻辑
void Service(int connfd, InetAddr client_addr) {
    Command cmd_handler(connfd);
    while (true) {
        // 接收命令
        std::string cmd = cmd_handler.RecvCommand();
        if (cmd.empty()) {
            std::cout << "client[" << client_addr.PrintDebug() << "] quit..." << std::endl;
            break;
        }
        std::cout << "client[" << client_addr.PrintDebug() << "] execute command: " << cmd << std::endl;

        // 执行命令并返回结果
        std::string result = cmd_handler.Execute(cmd);
        cmd_handler.SendResult(result);
    }
}

4.4 测试与问题

  1. 测试效果:客户端发送 “ls”,服务器返回当前目录文件列表;发送 “pwd”,返回当前工作目录。

  2. 核心问题:线程创建的 “动态开销”—— 高并发场景(如万级连接)下,频繁创建 / 销毁线程会产生大量开销,且线程数量过多会导致 CPU 上下文切换频繁,系统性能下降。

五、V4 版本:线程池 TCP 服务器(高可用并发)

为解决 “动态线程开销” 问题,V4 版本引入 “线程池”—— 提前创建固定数量的线程,通过任务队列接收客户端连接任务,线程循环从队列中取任务处理,避免频繁创建线程。

5.1 核心思路

        线程池:初始化时创建 N 个线程(如 4、8 个,根据 CPU 核心数调整),线程阻塞等待任务队列中的任务;

        任务队列:主进程accept()接受连接后,将 “处理连接” 封装为任务(std::function),加入任务队列;

        线程同步:用互斥锁保护任务队列的读写,用条件变量唤醒等待的线程(避免线程空轮询)。

5.2 线程池实现(ThreadPool.hpp)

首先实现通用线程池类,支持任意类型的任务(基于std::function):

// ThreadPool.hpp(通用线程池)
#pragma once
#include <iostream>
#include <vector>
#include <queue>
#include <functional>
#include <pthread.h>
#include "nocopy.hpp"

template <typename Task>
class ThreadPool : public nocopy {
public:
    // 单例模式:确保全局只有一个线程池
    static ThreadPool* GetInstance() {
        static ThreadPool instance;
        return &instance;
    }

    // 初始化线程池:指定线程数量
    void Init(size_t thread_num = 4) {
        _thread_num = thread_num;
        _is_running = true;

        // 创建线程
        for (size_t i = 0; i < _thread_num; ++i) {
            pthread_t tid;
            if (pthread_create(&tid, nullptr, ThreadFunc, this) != 0) {
                std::cerr << "pthread_create error" << std::endl;
                _is_running = false;
                break;
            }
            _threads.push_back(tid);
        }
        std::cout << "thread pool init success, thread num: " << _thread_num << std::endl;
    }

    // 向任务队列添加任务
    void Push(const Task& task) {
        pthread_mutex_lock(&_mutex);
        _task_queue.push(task);
        pthread_mutex_unlock(&_mutex);
        pthread_cond_signal(&_cond); // 唤醒一个等待的线程
    }

    // 停止线程池
    void Stop() {
        _is_running = false;
        pthread_cond_broadcast(&_cond); // 唤醒所有线程

        // 等待所有线程退出
        for (pthread_t tid : _threads) {
            pthread_join(tid, nullptr);
        }

        // 释放资源
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond);
    }

private:
    ThreadPool() {
        // 初始化互斥锁和条件变量
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_cond, nullptr);
    }

    ~ThreadPool() { Stop(); }

    // 线程执行函数:循环取任务处理
    static void* ThreadFunc(void* args) {
        ThreadPool* pool = static_cast<ThreadPool*>(args);
        while (pool->_is_running) {
            pthread_mutex_lock(&pool->_mutex);

            // 任务队列为空时,阻塞等待
            while (pool->_task_queue.empty() && pool->_is_running) {
                pthread_cond_wait(&pool->_cond, &pool->_mutex);
            }

            // 线程池停止时退出
            if (!pool->_is_running) {
                pthread_mutex_unlock(&pool->_mutex);
                break;
            }

            // 取出任务并执行
            Task task = pool->_task_queue.front();
            pool->_task_queue.pop();
            pthread_mutex_unlock(&pool->_mutex);

            task(); // 执行任务
        }
        return nullptr;
    }

private:
    size_t _thread_num;                // 线程数量
    bool _is_running;                  // 线程池运行状态
    std::vector<pthread_t> _threads;   // 线程ID列表
    std::queue<Task> _task_queue;      // 任务队列
    pthread_mutex_t _mutex;            // 保护任务队列的互斥锁
    pthread_cond_t _cond;              // 唤醒线程的条件变量
};

5.3 TCP 服务器集成线程池

修改TcpServerProcessConnection(),将 “处理连接” 封装为任务,加入线程池:

// TcpServer.hpp(V4版本:线程池修改)
#include "ThreadPool.hpp"
#include <functional> // 用于std::function

// 修改ProcessConnection:向线程池提交任务
void ProcessConnection(int connfd, struct sockaddr_in& peer) {
    InetAddr client_addr(peer);

    // 封装任务:绑定this指针、connfd和客户端地址
    using Task = std::function<void()>;
    Task task = std::bind(&TcpServer::Service, this, connfd, client_addr);

    // 向线程池提交任务
    ThreadPool<Task>::GetInstance()->Push(task);
    std::cout << "push task for client[" << client_addr.PrintDebug() << "], connfd: " << connfd << std::endl;
}

// 修改Init函数:初始化线程池
void Init() {
    // (复用V1的socket创建、绑定、监听逻辑)

    // 初始化线程池(4个线程)
    ThreadPool<std::function<void()>>::GetInstance()->Init(4);
}

// 修改Start函数:无需额外处理,主进程仅accept
void Start() {
    _isrunning = true;
    while (_isrunning) {
        struct sockaddr_in peer;
        socklen_t peer_len = sizeof(peer);
        int connfd = accept(_listensock, CONV(&peer), &peer_len);
        if (connfd < 0) {
            std::cerr << "accept error: " << strerror(errno) << std::endl;
            continue;
        }

        // 提交任务到线程池
        ProcessConnection(connfd, peer);
    }

    // 停止线程池
    ThreadPool<std::function<void()>>::GetInstance()->Stop();
    close(_listensock);
}

5.4 测试效果

        启动服务器后,线程池会提前创建 4 个线程;

        同时启动多个客户端,主进程将每个连接封装为任务加入队列;

        线程池中的线程循环取任务处理,无需频繁创建线程,系统资源占用稳定。

六、总结与进阶方向

本文通过四个版本的迭代,完整覆盖了 TCP Socket 编程的核心演进路径:

  1. V1(单连接):掌握 TCP 基础流程,但无法处理并发;
  2. V2(多进程):解决并发,但进程开销大;
  3. V3(多线程):降低开销,支持更高并发,但动态线程仍有开销;
  4. V4(线程池):消除动态线程开销,支持高可用并发,是生产环境的主流方案。

进阶方向

  1. IO 多路复用:结合select/poll/epoll(Linux),实现 “单线程处理多连接”,进一步提升并发能力(适合高并发、低数据量场景,如聊天服务器);
  2. 协议定制:当前版本基于 “裸 TCP” 传输,可自定义应用层协议(如添加消息长度字段),解决 “粘包” 问题;
  3. 安全加固:引入 TLS/SSL(如 OpenSSL 库),实现 HTTPS-like 的加密通信;
  4. 服务化改造:将服务器封装为可配置、可监控的服务,支持日志轮转、信号处理(如SIGINT优雅退出)。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值