Skip to content

C++ 网络编程

概述

网络编程是现代应用程序的重要组成部分。虽然C++标准库目前没有内置网络支持,但可以通过Socket API、第三方库如Boost.Asio等进行网络编程。本章介绍Socket基础、TCP/UDP编程、异步网络编程等内容。

🔌 Socket基础

TCP客户端和服务器

cpp
#include <iostream>
#include <string>
#include <vector>
#include <thread>
#include <chrono>

#ifdef _WIN32
    #include <winsock2.h>
    #include <ws2tcpip.h>
    #pragma comment(lib, "ws2_32.lib")
    typedef int socklen_t;
    #define close closesocket
#else
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <netdb.h>
#endif

class SocketUtils {
public:
    static bool initializeWinsock() {
#ifdef _WIN32
        WSADATA wsaData;
        int result = WSAStartup(MAKEWORD(2, 2), &wsaData);
        if (result != 0) {
            std::cerr << "WSAStartup失败: " << result << std::endl;
            return false;
        }
#endif
        return true;
    }
    
    static void cleanupWinsock() {
#ifdef _WIN32
        WSACleanup();
#endif
    }
    
    static std::string getLastErrorString() {
#ifdef _WIN32
        int error = WSAGetLastError();
        char* msg = nullptr;
        FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
                      nullptr, error, 0, (LPSTR)&msg, 0, nullptr);
        std::string result = msg ? msg : "未知错误";
        LocalFree(msg);
        return result;
#else
        return std::string(strerror(errno));
#endif
    }
};

class TCPServer {
private:
    int server_socket_;
    int port_;
    bool running_;
    
public:
    TCPServer(int port) : port_(port), running_(false), server_socket_(-1) {}
    
    ~TCPServer() {
        stop();
    }
    
    bool start() {
        if (!SocketUtils::initializeWinsock()) {
            return false;
        }
        
        // 创建socket
        server_socket_ = socket(AF_INET, SOCK_STREAM, 0);
        if (server_socket_ < 0) {
            std::cerr << "创建socket失败: " << SocketUtils::getLastErrorString() << std::endl;
            return false;
        }
        
        // 设置地址重用
        int opt = 1;
        setsockopt(server_socket_, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, sizeof(opt));
        
        // 绑定地址
        sockaddr_in server_addr{};
        server_addr.sin_family = AF_INET;
        server_addr.sin_addr.s_addr = INADDR_ANY;
        server_addr.sin_port = htons(port_);
        
        if (bind(server_socket_, (sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
            std::cerr << "绑定失败: " << SocketUtils::getLastErrorString() << std::endl;
            close(server_socket_);
            return false;
        }
        
        // 监听
        if (listen(server_socket_, 5) < 0) {
            std::cerr << "监听失败: " << SocketUtils::getLastErrorString() << std::endl;
            close(server_socket_);
            return false;
        }
        
        running_ = true;
        std::cout << "服务器在端口 " << port_ << " 启动" << std::endl;
        return true;
    }
    
    void run() {
        while (running_) {
            sockaddr_in client_addr{};
            socklen_t client_len = sizeof(client_addr);
            
            int client_socket = accept(server_socket_, (sockaddr*)&client_addr, &client_len);
            if (client_socket < 0) {
                if (running_) {
                    std::cerr << "接受连接失败: " << SocketUtils::getLastErrorString() << std::endl;
                }
                continue;
            }
            
            // 获取客户端地址
            char client_ip[INET_ADDRSTRLEN];
            inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, INET_ADDRSTRLEN);
            std::cout << "客户端连接: " << client_ip << ":" << ntohs(client_addr.sin_port) << std::endl;
            
            // 在新线程中处理客户端
            std::thread client_thread(&TCPServer::handleClient, this, client_socket);
            client_thread.detach();
        }
    }
    
    void stop() {
        running_ = false;
        if (server_socket_ >= 0) {
            close(server_socket_);
            server_socket_ = -1;
        }
        SocketUtils::cleanupWinsock();
    }
    
private:
    void handleClient(int client_socket) {
        char buffer[1024];
        
        while (running_) {
            int bytes_received = recv(client_socket, buffer, sizeof(buffer) - 1, 0);
            if (bytes_received <= 0) {
                break;
            }
            
            buffer[bytes_received] = '\0';
            std::string message(buffer);
            
            std::cout << "收到消息: " << message << std::endl;
            
            // 回声服务器:发送相同消息回去
            std::string response = "回声: " + message;
            send(client_socket, response.c_str(), response.length(), 0);
        }
        
        close(client_socket);
        std::cout << "客户端断开连接" << std::endl;
    }
};

class TCPClient {
private:
    int client_socket_;
    
public:
    TCPClient() : client_socket_(-1) {}
    
    ~TCPClient() {
        disconnect();
    }
    
    bool connect(const std::string& host, int port) {
        if (!SocketUtils::initializeWinsock()) {
            return false;
        }
        
        // 创建socket
        client_socket_ = socket(AF_INET, SOCK_STREAM, 0);
        if (client_socket_ < 0) {
            std::cerr << "创建socket失败: " << SocketUtils::getLastErrorString() << std::endl;
            return false;
        }
        
        // 解析主机地址
        sockaddr_in server_addr{};
        server_addr.sin_family = AF_INET;
        server_addr.sin_port = htons(port);
        
        if (inet_pton(AF_INET, host.c_str(), &server_addr.sin_addr) <= 0) {
            // 尝试通过主机名解析
            hostent* he = gethostbyname(host.c_str());
            if (!he) {
                std::cerr << "主机名解析失败: " << host << std::endl;
                close(client_socket_);
                return false;
            }
            memcpy(&server_addr.sin_addr, he->h_addr_list[0], he->h_length);
        }
        
        // 连接服务器
        if (::connect(client_socket_, (sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
            std::cerr << "连接失败: " << SocketUtils::getLastErrorString() << std::endl;
            close(client_socket_);
            return false;
        }
        
        std::cout << "已连接到 " << host << ":" << port << std::endl;
        return true;
    }
    
    bool sendMessage(const std::string& message) {
        if (client_socket_ < 0) {
            return false;
        }
        
        int bytes_sent = send(client_socket_, message.c_str(), message.length(), 0);
        return bytes_sent > 0;
    }
    
    std::string receiveMessage() {
        if (client_socket_ < 0) {
            return "";
        }
        
        char buffer[1024];
        int bytes_received = recv(client_socket_, buffer, sizeof(buffer) - 1, 0);
        
        if (bytes_received > 0) {
            buffer[bytes_received] = '\0';
            return std::string(buffer);
        }
        
        return "";
    }
    
    void disconnect() {
        if (client_socket_ >= 0) {
            close(client_socket_);
            client_socket_ = -1;
            SocketUtils::cleanupWinsock();
        }
    }
};

UDP编程

cpp
#include <iostream>
#include <string>
#include <vector>

class UDPServer {
private:
    int socket_;
    int port_;
    bool running_;
    
public:
    UDPServer(int port) : port_(port), running_(false), socket_(-1) {}
    
    ~UDPServer() {
        stop();
    }
    
    bool start() {
        if (!SocketUtils::initializeWinsock()) {
            return false;
        }
        
        // 创建UDP socket
        socket_ = socket(AF_INET, SOCK_DGRAM, 0);
        if (socket_ < 0) {
            std::cerr << "创建UDP socket失败: " << SocketUtils::getLastErrorString() << std::endl;
            return false;
        }
        
        // 绑定地址
        sockaddr_in server_addr{};
        server_addr.sin_family = AF_INET;
        server_addr.sin_addr.s_addr = INADDR_ANY;
        server_addr.sin_port = htons(port_);
        
        if (bind(socket_, (sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
            std::cerr << "UDP绑定失败: " << SocketUtils::getLastErrorString() << std::endl;
            close(socket_);
            return false;
        }
        
        running_ = true;
        std::cout << "UDP服务器在端口 " << port_ << " 启动" << std::endl;
        return true;
    }
    
    void run() {
        char buffer[1024];
        sockaddr_in client_addr{};
        socklen_t client_len = sizeof(client_addr);
        
        while (running_) {
            int bytes_received = recvfrom(socket_, buffer, sizeof(buffer) - 1, 0,
                                        (sockaddr*)&client_addr, &client_len);
            
            if (bytes_received < 0) {
                if (running_) {
                    std::cerr << "UDP接收失败: " << SocketUtils::getLastErrorString() << std::endl;
                }
                continue;
            }
            
            buffer[bytes_received] = '\0';
            
            char client_ip[INET_ADDRSTRLEN];
            inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, INET_ADDRSTRLEN);
            
            std::cout << "收到UDP消息从 " << client_ip << ":" << ntohs(client_addr.sin_port)
                      << " - " << buffer << std::endl;
            
            // 发送回复
            std::string response = "UDP回声: " + std::string(buffer);
            sendto(socket_, response.c_str(), response.length(), 0,
                   (sockaddr*)&client_addr, client_len);
        }
    }
    
    void stop() {
        running_ = false;
        if (socket_ >= 0) {
            close(socket_);
            socket_ = -1;
        }
        SocketUtils::cleanupWinsock();
    }
};

class UDPClient {
private:
    int socket_;
    sockaddr_in server_addr_;
    
public:
    UDPClient() : socket_(-1) {}
    
    ~UDPClient() {
        disconnect();
    }
    
    bool connect(const std::string& host, int port) {
        if (!SocketUtils::initializeWinsock()) {
            return false;
        }
        
        // 创建UDP socket
        socket_ = socket(AF_INET, SOCK_DGRAM, 0);
        if (socket_ < 0) {
            std::cerr << "创建UDP socket失败: " << SocketUtils::getLastErrorString() << std::endl;
            return false;
        }
        
        // 设置服务器地址
        server_addr_.sin_family = AF_INET;
        server_addr_.sin_port = htons(port);
        
        if (inet_pton(AF_INET, host.c_str(), &server_addr_.sin_addr) <= 0) {
            hostent* he = gethostbyname(host.c_str());
            if (!he) {
                std::cerr << "UDP主机名解析失败: " << host << std::endl;
                close(socket_);
                return false;
            }
            memcpy(&server_addr_.sin_addr, he->h_addr_list[0], he->h_length);
        }
        
        std::cout << "UDP客户端准备就绪,目标: " << host << ":" << port << std::endl;
        return true;
    }
    
    bool sendMessage(const std::string& message) {
        if (socket_ < 0) {
            return false;
        }
        
        int bytes_sent = sendto(socket_, message.c_str(), message.length(), 0,
                               (sockaddr*)&server_addr_, sizeof(server_addr_));
        return bytes_sent > 0;
    }
    
    std::string receiveMessage() {
        if (socket_ < 0) {
            return "";
        }
        
        char buffer[1024];
        sockaddr_in from_addr{};
        socklen_t from_len = sizeof(from_addr);
        
        int bytes_received = recvfrom(socket_, buffer, sizeof(buffer) - 1, 0,
                                     (sockaddr*)&from_addr, &from_len);
        
        if (bytes_received > 0) {
            buffer[bytes_received] = '\0';
            return std::string(buffer);
        }
        
        return "";
    }
    
    void disconnect() {
        if (socket_ >= 0) {
            close(socket_);
            socket_ = -1;
            SocketUtils::cleanupWinsock();
        }
    }
};

🌐 HTTP客户端示例

简单HTTP请求

cpp
#include <iostream>
#include <string>
#include <sstream>
#include <map>

class HTTPClient {
private:
    struct HTTPResponse {
        int status_code;
        std::map<std::string, std::string> headers;
        std::string body;
    };
    
public:
    static HTTPResponse get(const std::string& host, int port, const std::string& path) {
        HTTPResponse response;
        
        TCPClient client;
        if (!client.connect(host, port)) {
            response.status_code = -1;
            return response;
        }
        
        // 构造HTTP GET请求
        std::ostringstream request;
        request << "GET " << path << " HTTP/1.1\r\n";
        request << "Host: " << host << "\r\n";
        request << "Connection: close\r\n";
        request << "\r\n";
        
        std::string request_str = request.str();
        std::cout << "发送HTTP请求:\n" << request_str << std::endl;
        
        if (!client.sendMessage(request_str)) {
            response.status_code = -2;
            return response;
        }
        
        // 接收响应
        std::string full_response;
        std::string chunk;
        
        while (!(chunk = client.receiveMessage()).empty()) {
            full_response += chunk;
        }
        
        // 解析HTTP响应
        parseHTTPResponse(full_response, response);
        
        return response;
    }
    
private:
    static void parseHTTPResponse(const std::string& raw_response, HTTPResponse& response) {
        std::istringstream stream(raw_response);
        std::string line;
        
        // 解析状态行
        if (std::getline(stream, line)) {
            std::istringstream status_stream(line);
            std::string version;
            status_stream >> version >> response.status_code;
        }
        
        // 解析头部
        while (std::getline(stream, line) && line != "\r" && !line.empty()) {
            size_t colon_pos = line.find(':');
            if (colon_pos != std::string::npos) {
                std::string key = line.substr(0, colon_pos);
                std::string value = line.substr(colon_pos + 1);
                
                // 去除前后空格
                value.erase(0, value.find_first_not_of(" \t"));
                value.erase(value.find_last_not_of(" \t\r") + 1);
                
                response.headers[key] = value;
            }
        }
        
        // 读取主体
        std::ostringstream body_stream;
        while (std::getline(stream, line)) {
            body_stream << line << "\n";
        }
        response.body = body_stream.str();
    }
};

// 网络编程示例
class NetworkDemo {
public:
    static void tcpEchoDemo() {
        std::cout << "=== TCP回声服务器演示 ===" << std::endl;
        
        // 启动服务器在后台线程
        TCPServer server(8080);
        
        std::thread server_thread([&server]() {
            if (server.start()) {
                server.run();
            }
        });
        
        // 等待服务器启动
        std::this_thread::sleep_for(std::chrono::milliseconds(500));
        
        // 客户端连接和测试
        TCPClient client;
        if (client.connect("127.0.0.1", 8080)) {
            for (int i = 0; i < 3; ++i) {
                std::string message = "测试消息 " + std::to_string(i + 1);
                
                if (client.sendMessage(message)) {
                    std::string response = client.receiveMessage();
                    std::cout << "发送: " << message << std::endl;
                    std::cout << "接收: " << response << std::endl;
                }
                
                std::this_thread::sleep_for(std::chrono::milliseconds(100));
            }
        }
        
        server.stop();
        if (server_thread.joinable()) {
            server_thread.join();
        }
    }
    
    static void udpEchoDemo() {
        std::cout << "\n=== UDP回声服务器演示 ===" << std::endl;
        
        // 启动UDP服务器
        UDPServer server(8081);
        
        std::thread server_thread([&server]() {
            if (server.start()) {
                server.run();
            }
        });
        
        std::this_thread::sleep_for(std::chrono::milliseconds(500));
        
        // UDP客户端测试
        UDPClient client;
        if (client.connect("127.0.0.1", 8081)) {
            for (int i = 0; i < 3; ++i) {
                std::string message = "UDP测试 " + std::to_string(i + 1);
                
                if (client.sendMessage(message)) {
                    std::string response = client.receiveMessage();
                    std::cout << "发送: " << message << std::endl;
                    std::cout << "接收: " << response << std::endl;
                }
                
                std::this_thread::sleep_for(std::chrono::milliseconds(100));
            }
        }
        
        server.stop();
        if (server_thread.joinable()) {
            server_thread.join();
        }
    }
    
    static void httpClientDemo() {
        std::cout << "\n=== HTTP客户端演示 ===" << std::endl;
        
        // 注意:这需要实际的HTTP服务器
        auto response = HTTPClient::get("httpbin.org", 80, "/get");
        
        std::cout << "HTTP状态码: " << response.status_code << std::endl;
        std::cout << "响应头部:" << std::endl;
        for (const auto& header : response.headers) {
            std::cout << "  " << header.first << ": " << header.second << std::endl;
        }
        std::cout << "响应主体:\n" << response.body << std::endl;
    }
};

int main() {
    NetworkDemo::tcpEchoDemo();
    NetworkDemo::udpEchoDemo();
    
    // HTTP演示需要网络连接
    // NetworkDemo::httpClientDemo();
    
    return 0;
}

总结

C++网络编程虽然没有标准库直接支持,但通过Socket API可以实现强大的网络功能:

核心概念

  • Socket: 网络通信端点
  • TCP: 可靠的面向连接协议
  • UDP: 快速的无连接协议
  • 客户端/服务器: 网络应用架构模式

编程模型

协议特点适用场景
TCP可靠、有序、面向连接Web服务、文件传输
UDP快速、无连接、不可靠游戏、实时通信
HTTP应用层协议Web应用、API

最佳实践

  • 错误处理: 充分处理网络错误
  • 非阻塞IO: 避免程序阻塞
  • 多线程: 并发处理多个连接
  • 资源管理: 及时关闭socket和清理资源
  • 跨平台: 处理不同系统的差异

高级技术

  • 异步IO: 使用select/poll/epoll
  • 线程池: 复用线程处理连接
  • 连接池: 复用网络连接
  • SSL/TLS: 安全通信加密

第三方库

  • Boost.Asio: 异步网络编程库
  • libcurl: HTTP/HTTPS客户端库
  • Poco: 网络和应用框架
  • Qt Network: Qt框架网络模块

注意事项

  • 字节序: 网络字节序转换
  • 缓冲区: 合理设置缓冲区大小
  • 超时: 设置适当的超时时间
  • 安全: 防范网络攻击
  • 性能: 优化网络IO性能

网络编程是构建分布式应用和服务的基础,掌握这些技术对现代C++开发至关重要。

本站内容仅供学习和研究使用。