服务器设置和教程 · 28 4 月, 2025

Linux环境下TCP 网络编程:TCP 服务端实现

TCP 网络编程:TCP 服务端实现

在本篇文章中,我们将深入探讨如何使用 C++ 编写一个基本的 TCP 服务端程序。具体内容涵盖了如何创建套接字、绑定地址、监听端口以及如何接收和处理客户端连接请求。此外,我们还将讨论如何优化性能,支持多进程和多线程模式,以提升服务器的并发能力。

1. 创建 TCP 服务端

1.1 服务端基础配置

服务端程序的基本结构包括以下几个核心成员变量:

int _listensock;   // 监听的文件描述符
string _ip;        // 服务端 IP 地址
uint16_t _port;    // 服务端端口号
bool _isrunning;   // 服务端是否在运行

1.2 初始化服务器

在创建服务器时,主要步骤包括创建套接字、绑定地址和监听端口。下面我们详细介绍这些步骤。

1.2.1 创建套接字

通过 socket() 函数创建套接字,具体代码如下:

_listensock = socket(AF_INET, SOCK_STREAM, 0);
if (_listensock < 0) {
    lg(Fatal, "create socket failed, errno: %d, errstring: %s", errno, strerror(errno));
    exit(SocketError);
}
  • AF_INET:指定协议族(IPv4)。
  • SOCK_STREAM:指定传输类型为 TCP 流。
  • 返回值为创建的套接字文件描述符,若失败返回 -1。
1.2.2 绑定地址

调用 bind() 函数将套接字与指定的 IP 地址和端口号绑定,代码如下:

struct sockaddr_in local;
bzero(&local, sizeof(local)); // 清空结构体
local.sin_family = AF_INET;
local.sin_port = htons(_port); // 端口号转为网络字节序
inet_aton(_ip.c_str(), &local.sin_addr); // 转换 IP 地址

if (bind(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0) {
    lg(Fatal, "bind failed, errno: %d, errstring: %s", errno, strerror(errno));
    exit(BindError);
}
  • 结构体 sockaddr_in 用于指定绑定的地址信息。
  • htons() 函数用于将端口号从主机字节序转换为网络字节序。
1.2.3 开始监听

通过 listen() 函数将套接字置于监听状态,等待客户端的连接请求:

if (listen(_listensock, backlog) < 0) {
    lg(Fatal, "listen failed, errno: %d, errstring: %s", errno, strerror(errno));
}
  • backlog:定义监听队列的最大长度。

2. 运行 TCP 服务端

2.1 接受客户端连接请求

当客户端向服务端发起连接时,服务端需要通过 accept() 函数接受连接,并返回一个新的套接字,用于后续与客户端的通信。

int client_sockfd = accept(_listensock, (struct sockaddr *)&client_addr, &client_len);
if (client_sockfd < 0) {
    lg(Fatal, "accept failed, errno: %d, errstring: %s", errno, strerror(errno));
}
  • client_sockfd:返回的客户端连接套接字。
  • client_addr:客户端的地址信息。

2.2 多进程处理客户端请求

为了提高并发处理能力,服务端可以采用多进程模型。通过 fork() 创建子进程来处理每个客户端的请求。父进程继续监听新连接,子进程负责与客户端的交互。

pid_t pid = fork();
if (pid == 0) {
    close(_listensock);  // 子进程关闭监听套接字
    handle_client(client_sockfd);
    exit(0);
}
  • fork() 返回值:子进程返回 0,父进程返回子进程的 PID。
  • handle_client():处理与客户端的交互。

2.3 多线程处理客户端请求

与多进程相比,多线程模型更加轻量级。在多线程模式下,可以为每个客户端请求创建一个独立线程进行处理。

pthread_t thread_id;
pthread_create(&thread_id, NULL, handle_client, (void *)&client_sockfd);
  • pthread_create():创建一个新的线程来处理客户端请求。

3. 性能优化:线程池

为了避免频繁创建和销毁线程的开销,可以使用线程池来管理多个线程,提升性能。线程池通过预先创建一定数量的线程来处理任务,避免了线程频繁创建和销毁的问题。

ThreadPool<Task> *tp = ThreadPool<Task>::GetInstance();
tp->Push(task);
  • ThreadPool:管理工作线程。
  • Push():将任务添加到线程池的任务队列中。

4. 处理客户端退出

在服务器与客户端交互时,如果客户端提前退出,服务端写入时可能会遇到问题。可以通过处理 SIGPIPE 信号来避免服务端崩溃。

signal(SIGPIPE, SIG_IGN);
  • SIGPIPE:当写入关闭的 socket 时,系统会发送 SIGPIPE 信号,默认会终止进程。

5. 结论

通过以上步骤,我们实现了一个简单而高效的 TCP 服务端程序。根据实际需求,可以选择多进程、多线程或线程池模型来提升并发性能。同时,合理处理客户端退出等异常情况,确保服务端的稳定运行。如果你正在寻找高效稳定的服务器解决方案,可以考虑选择香港服务器,如香港VPS、香港云服务器或香港独立服务器等,访问服务器官网获取更多信息和优惠。