一、TCP 服务器与客户端基础

网络编程的核心之一就是建立可靠的通信通道,而 TCP 协议因其稳定性和可靠性成为首选。在 C++ 中,我们可以使用系统提供的套接字(socket)接口来实现 TCP 服务器和客户端。

1.1 TCP 服务器示例

下面是一个简单的 TCP 服务器实现,使用标准 C++ 和 POSIX socket API(技术栈:Linux C++ Socket API)。

#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <cstring>

int main() {
    // 1. 创建 socket
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd == -1) {
        std::cerr << "Socket creation failed" << std::endl;
        return -1;
    }

    // 2. 绑定 IP 和端口
    struct sockaddr_in address;
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;  // 监听所有网卡
    address.sin_port = htons(8080);        // 监听 8080 端口

    if (bind(server_fd, (struct sockaddr*)&address, sizeof(address)) < 0) {
        std::cerr << "Bind failed" << std::endl;
        return -1;
    }

    // 3. 监听连接
    if (listen(server_fd, 5) < 0) {  // 最大等待队列 5
        std::cerr << "Listen failed" << std::endl;
        return -1;
    }

    std::cout << "Server listening on port 8080..." << std::endl;

    // 4. 接受客户端连接
    int client_socket;
    socklen_t addr_len = sizeof(address);
    client_socket = accept(server_fd, (struct sockaddr*)&address, &addr_len);
    if (client_socket < 0) {
        std::cerr << "Accept failed" << std::endl;
        return -1;
    }

    // 5. 读取客户端数据
    char buffer[1024] = {0};
    read(client_socket, buffer, 1024);
    std::cout << "Received: " << buffer << std::endl;

    // 6. 发送响应
    const char* response = "Hello from server!";
    send(client_socket, response, strlen(response), 0);

    // 7. 关闭连接
    close(client_socket);
    close(server_fd);

    return 0;
}

1.2 TCP 客户端示例

对应的 TCP 客户端代码如下:

#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <cstring>

int main() {
    // 1. 创建 socket
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock == -1) {
        std::cerr << "Socket creation failed" << std::endl;
        return -1;
    }

    // 2. 连接服务器
    struct sockaddr_in serv_addr;
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(8080);
    inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr);  // 连接本地服务器

    if (connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
        std::cerr << "Connection failed" << std::endl;
        return -1;
    }

    // 3. 发送数据
    const char* message = "Hello from client!";
    send(sock, message, strlen(message), 0);

    // 4. 接收服务器响应
    char buffer[1024] = {0};
    read(sock, buffer, 1024);
    std::cout << "Server response: " << buffer << std::endl;

    // 5. 关闭连接
    close(sock);

    return 0;
}

1.3 应用场景

  • 即时通讯:如聊天服务器。
  • 文件传输:可靠的数据传输。
  • 远程控制:如 SSH、远程桌面。

1.4 优缺点

  • 优点:可靠、有序、面向连接。
  • 缺点:三次握手开销较大,不适合高频短连接场景。

二、IO 多路复用:select/poll/epoll

单线程处理多个客户端连接时,IO 多路复用技术可以大幅提升效率。

2.1 select 示例

#include <iostream>
#include <sys/select.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <vector>

int main() {
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in address;
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(8080);

    bind(server_fd, (struct sockaddr*)&address, sizeof(address));
    listen(server_fd, 5);

    fd_set read_fds;
    FD_ZERO(&read_fds);
    FD_SET(server_fd, &read_fds);

    while (true) {
        fd_set tmp_fds = read_fds;
        int max_fd = server_fd;

        // 等待事件
        select(max_fd + 1, &tmp_fds, nullptr, nullptr, nullptr);

        // 检查是否有新连接
        if (FD_ISSET(server_fd, &tmp_fds)) {
            int client_socket = accept(server_fd, nullptr, nullptr);
            FD_SET(client_socket, &read_fds);
            max_fd = std::max(max_fd, client_socket);
        }

        // 检查客户端数据
        for (int fd = 0; fd <= max_fd; fd++) {
            if (fd != server_fd && FD_ISSET(fd, &tmp_fds)) {
                char buffer[1024] = {0};
                int bytes_read = read(fd, buffer, sizeof(buffer));
                if (bytes_read <= 0) {
                    close(fd);
                    FD_CLR(fd, &read_fds);
                } else {
                    std::cout << "Received: " << buffer << std::endl;
                    send(fd, "ACK", 3, 0);
                }
            }
        }
    }

    close(server_fd);
    return 0;
}

2.2 epoll 示例(更高效)

#include <iostream>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>

int main() {
    int server_fd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in address;
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(8080);

    bind(server_fd, (struct sockaddr*)&address, sizeof(address));
    listen(server_fd, 5);

    int epoll_fd = epoll_create1(0);
    struct epoll_event event;
    event.events = EPOLLIN;
    event.data.fd = server_fd;
    epoll_ctl(epoll_fd, EPOLL_CTL_ADD, server_fd, &event);

    while (true) {
        struct epoll_event events[10];
        int num_events = epoll_wait(epoll_fd, events, 10, -1);

        for (int i = 0; i < num_events; i++) {
            if (events[i].data.fd == server_fd) {
                int client_socket = accept(server_fd, nullptr, nullptr);
                event.events = EPOLLIN;
                event.data.fd = client_socket;
                epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_socket, &event);
            } else {
                char buffer[1024] = {0};
                int bytes_read = read(events[i].data.fd, buffer, sizeof(buffer));
                if (bytes_read <= 0) {
                    close(events[i].data.fd);
                } else {
                    std::cout << "Received: " << buffer << std::endl;
                    send(events[i].data.fd, "ACK", 3, 0);
                }
            }
        }
    }

    close(server_fd);
    return 0;
}

2.3 应用场景

  • 高并发服务器:如 Web 服务器(Nginx)。
  • 实时数据处理:如股票行情推送。

2.4 优缺点

  • select:跨平台,但效率低。
  • epoll:Linux 专属,高性能。

三、异步 IO 模型

异步 IO(如 io_uring)进一步提升性能,适用于超高并发场景。

3.1 io_uring 示例

// 示例代码较复杂,通常结合 liburing 库使用,此处仅作概念介绍。

3.2 应用场景

  • 高性能代理:如反向代理服务器。
  • 大规模微服务:如 Kubernetes 服务网格。

3.3 优缺点

  • 优点:零拷贝、极高吞吐量。
  • 缺点:实现复杂,兼容性较差。

四、总结

  1. TCP 基础:适合可靠通信,但握手开销大。
  2. IO 多路复用:提升单线程并发能力,epoll 优于 select
  3. 异步 IO:性能极致,但实现复杂。

选择合适的模型取决于具体场景,普通应用 epoll 足够,超高并发可考虑 io_uring