09-网络编程基础

网络编程实现进程间跨主机通信。Socket是网络通信的基础API,TCP提供可靠连接,UDP提供快速无连接传输。

Socket编程

Socket是网络通信端点的抽象。通过 socket()、bind()、listen()、accept()、connect() 等系统调用实现网络通信。

基本Socket操作

Socket编程遵循创建-绑定-监听-接受/连接的流程。RAII封装确保资源正确释放,避免文件描述符泄漏。

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

class Socket {
private:
    int sockfd;
    
public:
    Socket(int domain, int type, int protocol) {
        sockfd = socket(domain, type, protocol);
        if (sockfd < 0) {
            throw std::runtime_error("Socket creation failed");
        }
    }
    
    ~Socket() {
        if (sockfd >= 0) {
            close(sockfd);
        }
    }
    
    int getFd() const { return sockfd; }
    
    void bind(const sockaddr* addr, socklen_t addrlen) {
        if (::bind(sockfd, addr, addrlen) < 0) {
            throw std::runtime_error("Bind failed");
        }
    }
    
    void listen(int backlog) {
        if (::listen(sockfd, backlog) < 0) {
            throw std::runtime_error("Listen failed");
        }
    }
    
    int accept(sockaddr* addr, socklen_t* addrlen) {
        int clientfd = ::accept(sockfd, addr, addrlen);
        if (clientfd < 0) {
            throw std::runtime_error("Accept failed");
        }
        return clientfd;
    }
    
    void connect(const sockaddr* addr, socklen_t addrlen) {
        if (::connect(sockfd, addr, addrlen) < 0) {
            throw std::runtime_error("Connect failed");
        }
    }
};

TCP服务器

TCP服务器使用 SOCK_STREAM 创建面向连接的Socket。listen() 设置连接队列,accept() 阻塞等待客户端连接。

#include <iostream>
#include <string>
#include <cstring>

class TCPServer {
private:
    Socket socket;
    int port;
    
public:
    TCPServer(int p) : socket(AF_INET, SOCK_STREAM, 0), port(p) {
        setupServer();
    }
    
    void setupServer() {
        // 设置地址重用
        int opt = 1;
        setsockopt(socket.getFd(), SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
        
        // 绑定地址
        sockaddr_in addr{};
        addr.sin_family = AF_INET;
        addr.sin_addr.s_addr = INADDR_ANY;
        addr.sin_port = htons(port);
        
        socket.bind(reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
        socket.listen(5);
        
        std::cout << "Server listening on port " << port << std::endl;
    }
    
    void run() {
        while (true) {
            sockaddr_in clientAddr{};
            socklen_t clientLen = sizeof(clientAddr);
            
            int clientfd = socket.accept(
                reinterpret_cast<sockaddr*>(&clientAddr), 
                &clientLen
            );
            
            std::cout << "Client connected: " 
                      << inet_ntoa(clientAddr.sin_addr) << std::endl;
            
            handleClient(clientfd);
            close(clientfd);
        }
    }
    
private:
    void handleClient(int clientfd) {
        char buffer[1024];
        while (true) {
            ssize_t bytesRead = recv(clientfd, buffer, sizeof(buffer) - 1, 0);
            if (bytesRead <= 0) {
                break;
            }
            
            buffer[bytesRead] = '\0';
            std::cout << "Received: " << buffer << std::endl;
            
            // 回显消息
            std::string response = "Echo: " + std::string(buffer);
            send(clientfd, response.c_str(), response.length(), 0);
        }
    }
};

TCP客户端

TCP客户端主动发起连接。connect() 连接到服务器,send()/recv() 发送/接收数据。网络字节序用 htons() 转换。

class TCPClient {
private:
    Socket socket;
    std::string host;
    int port;
    
public:
    TCPClient(const std::string& h, int p) 
        : socket(AF_INET, SOCK_STREAM, 0), host(h), port(p) {
        connectToServer();
    }
    
    void connectToServer() {
        sockaddr_in serverAddr{};
        serverAddr.sin_family = AF_INET;
        serverAddr.sin_port = htons(port);
        
        if (inet_pton(AF_INET, host.c_str(), &serverAddr.sin_addr) <= 0) {
            throw std::runtime_error("Invalid address");
        }
        
        socket.connect(
            reinterpret_cast<sockaddr*>(&serverAddr), 
            sizeof(serverAddr)
        );
        
        std::cout << "Connected to " << host << ":" << port << std::endl;
    }
    
    void sendMessage(const std::string& message) {
        send(socket.getFd(), message.c_str(), message.length(), 0);
    }
    
    std::string receiveMessage() {
        char buffer[1024];
        ssize_t bytesRead = recv(socket.getFd(), buffer, sizeof(buffer) - 1, 0);
        if (bytesRead > 0) {
            buffer[bytesRead] = '\0';
            return std::string(buffer);
        }
        return "";
    }
};

UDP编程

UDP服务器

class UDPServer {
private:
    Socket socket;
    int port;
    
public:
    UDPServer(int p) : socket(AF_INET, SOCK_DGRAM, 0), port(p) {
        setupServer();
    }
    
    void setupServer() {
        sockaddr_in addr{};
        addr.sin_family = AF_INET;
        addr.sin_addr.s_addr = INADDR_ANY;
        addr.sin_port = htons(port);
        
        socket.bind(reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
        std::cout << "UDP Server listening on port " << port << std::endl;
    }
    
    void run() {
        char buffer[1024];
        sockaddr_in clientAddr{};
        socklen_t clientLen = sizeof(clientAddr);
        
        while (true) {
            ssize_t bytesRead = recvfrom(
                socket.getFd(), buffer, sizeof(buffer) - 1, 0,
                reinterpret_cast<sockaddr*>(&clientAddr), &clientLen
            );
            
            if (bytesRead > 0) {
                buffer[bytesRead] = '\0';
                std::cout << "Received from " 
                          << inet_ntoa(clientAddr.sin_addr) 
                          << ": " << buffer << std::endl;
                
                // 回显消息
                std::string response = "Echo: " + std::string(buffer);
                sendto(
                    socket.getFd(), response.c_str(), response.length(), 0,
                    reinterpret_cast<sockaddr*>(&clientAddr), clientLen
                );
            }
        }
    }
};

UDP客户端

class UDPClient {
private:
    Socket socket;
    std::string host;
    int port;
    sockaddr_in serverAddr;
    
public:
    UDPClient(const std::string& h, int p) 
        : socket(AF_INET, SOCK_DGRAM, 0), host(h), port(p) {
        setupServerAddr();
    }
    
    void setupServerAddr() {
        serverAddr.sin_family = AF_INET;
        serverAddr.sin_port = htons(port);
        
        if (inet_pton(AF_INET, host.c_str(), &serverAddr.sin_addr) <= 0) {
            throw std::runtime_error("Invalid address");
        }
    }
    
    void sendMessage(const std::string& message) {
        sendto(
            socket.getFd(), message.c_str(), message.length(), 0,
            reinterpret_cast<sockaddr*>(&serverAddr), sizeof(serverAddr)
        );
    }
    
    std::string receiveMessage() {
        char buffer[1024];
        sockaddr_in fromAddr{};
        socklen_t fromLen = sizeof(fromAddr);
        
        ssize_t bytesRead = recvfrom(
            socket.getFd(), buffer, sizeof(buffer) - 1, 0,
            reinterpret_cast<sockaddr*>(&fromAddr), &fromLen
        );
        
        if (bytesRead > 0) {
            buffer[bytesRead] = '\0';
            return std::string(buffer);
        }
        return "";
    }
};

非阻塞IO

非阻塞Socket

#include <fcntl.h>
#include <sys/select.h>

class NonBlockingSocket {
private:
    int sockfd;
    
public:
    NonBlockingSocket(int domain, int type, int protocol) {
        sockfd = socket(domain, type, protocol);
        if (sockfd < 0) {
            throw std::runtime_error("Socket creation failed");
        }
        
        // 设置为非阻塞
        int flags = fcntl(sockfd, F_GETFL, 0);
        fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);
    }
    
    ~NonBlockingSocket() {
        if (sockfd >= 0) {
            close(sockfd);
        }
    }
    
    int getFd() const { return sockfd; }
    
    bool connect(const sockaddr* addr, socklen_t addrlen) {
        int result = ::connect(sockfd, addr, addrlen);
        if (result == 0) {
            return true;  // 立即连接成功
        }
        
        if (errno == EINPROGRESS) {
            return false;  // 连接进行中
        }
        
        throw std::runtime_error("Connect failed");
    }
    
    bool isConnected() {
        int error = 0;
        socklen_t len = sizeof(error);
        int result = getsockopt(sockfd, SOL_SOCKET, SO_ERROR, &error, &len);
        
        if (result == 0 && error == 0) {
            return true;
        }
        return false;
    }
};

select模型

#include <sys/select.h>

class SelectServer {
private:
    int serverFd;
    fd_set readfds;
    int maxFd;
    std::vector<int> clientFds;
    
public:
    SelectServer(int port) {
        setupServer(port);
        FD_ZERO(&readfds);
        FD_SET(serverFd, &readfds);
        maxFd = serverFd;
    }
    
    void run() {
        while (true) {
            fd_set tempfds = readfds;
            
            int activity = select(maxFd + 1, &tempfds, nullptr, nullptr, nullptr);
            
            if (activity < 0) {
                throw std::runtime_error("Select failed");
            }
            
            // 检查新连接
            if (FD_ISSET(serverFd, &tempfds)) {
                acceptNewConnection();
            }
            
            // 检查客户端数据
            for (auto it = clientFds.begin(); it != clientFds.end();) {
                if (FD_ISSET(*it, &tempfds)) {
                    if (handleClientData(*it)) {
                        ++it;
                    } else {
                        close(*it);
                        FD_CLR(*it, &readfds);
                        it = clientFds.erase(it);
                    }
                } else {
                    ++it;
                }
            }
        }
    }
    
private:
    void setupServer(int port) {
        serverFd = socket(AF_INET, SOCK_STREAM, 0);
        
        sockaddr_in addr{};
        addr.sin_family = AF_INET;
        addr.sin_addr.s_addr = INADDR_ANY;
        addr.sin_port = htons(port);
        
        bind(serverFd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr));
        listen(serverFd, 5);
    }
    
    void acceptNewConnection() {
        sockaddr_in clientAddr{};
        socklen_t clientLen = sizeof(clientAddr);
        
        int clientFd = accept(serverFd, 
                             reinterpret_cast<sockaddr*>(&clientAddr), 
                             &clientLen);
        
        if (clientFd >= 0) {
            clientFds.push_back(clientFd);
            FD_SET(clientFd, &readfds);
            maxFd = std::max(maxFd, clientFd);
            
            std::cout << "New client connected" << std::endl;
        }
    }
    
    bool handleClientData(int clientFd) {
        char buffer[1024];
        ssize_t bytesRead = recv(clientFd, buffer, sizeof(buffer) - 1, 0);
        
        if (bytesRead > 0) {
            buffer[bytesRead] = '\0';
            std::cout << "Received: " << buffer << std::endl;
            
            std::string response = "Echo: " + std::string(buffer);
            send(clientFd, response.c_str(), response.length(), 0);
            return true;
        }
        
        return false;  // 客户端断开连接
    }
};

HTTP客户端

简单HTTP客户端

#include <sstream>
#include <regex>

class HTTPClient {
private:
    std::string host;
    int port;
    
public:
    HTTPClient(const std::string& h, int p = 80) : host(h), port(p) {}
    
    std::string get(const std::string& path) {
        TCPClient client(host, port);
        
        // 构建HTTP请求
        std::stringstream request;
        request << "GET " << path << " HTTP/1.1\r\n";
        request << "Host: " << host << "\r\n";
        request << "Connection: close\r\n";
        request << "\r\n";
        
        client.sendMessage(request.str());
        
        // 接收响应
        std::string response = client.receiveMessage();
        return parseResponse(response);
    }
    
    std::string post(const std::string& path, const std::string& data) {
        TCPClient client(host, port);
        
        // 构建HTTP请求
        std::stringstream request;
        request << "POST " << path << " HTTP/1.1\r\n";
        request << "Host: " << host << "\r\n";
        request << "Content-Type: application/json\r\n";
        request << "Content-Length: " << data.length() << "\r\n";
        request << "Connection: close\r\n";
        request << "\r\n";
        request << data;
        
        client.sendMessage(request.str());
        
        // 接收响应
        std::string response = client.receiveMessage();
        return parseResponse(response);
    }
    
private:
    std::string parseResponse(const std::string& response) {
        // 简单的响应解析
        std::regex headerRegex(R"(HTTP/\d\.\d (\d+) (.+))");
        std::smatch match;
        
        if (std::regex_search(response, match, headerRegex)) {
            int statusCode = std::stoi(match[1]);
            std::string statusText = match[2];
            
            std::cout << "Status: " << statusCode << " " << statusText << std::endl;
        }
        
        // 查找响应体
        size_t bodyStart = response.find("\r\n\r\n");
        if (bodyStart != std::string::npos) {
            return response.substr(bodyStart + 4);
        }
        
        return response;
    }
};

网络工具类

地址转换

#include <arpa/inet.h>

class NetworkUtils {
public:
    static std::string ipToString(uint32_t ip) {
        struct in_addr addr;
        addr.s_addr = ip;
        return std::string(inet_ntoa(addr));
    }
    
    static uint32_t stringToIp(const std::string& ipStr) {
        return inet_addr(ipStr.c_str());
    }
    
    static uint16_t hostToNetwork(uint16_t host) {
        return htons(host);
    }
    
    static uint16_t networkToHost(uint16_t network) {
        return ntohs(network);
    }
    
    static uint32_t hostToNetwork(uint32_t host) {
        return htonl(host);
    }
    
    static uint32_t networkToHost(uint32_t network) {
        return ntohl(network);
    }
};

超时处理

#include <sys/time.h>

class TimeoutSocket {
private:
    int sockfd;
    
public:
    TimeoutSocket(int domain, int type, int protocol) {
        sockfd = socket(domain, type, protocol);
    }
    
    void setReceiveTimeout(int seconds) {
        struct timeval timeout;
        timeout.tv_sec = seconds;
        timeout.tv_usec = 0;
        
        setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, 
                  &timeout, sizeof(timeout));
    }
    
    void setSendTimeout(int seconds) {
        struct timeval timeout;
        timeout.tv_sec = seconds;
        timeout.tv_usec = 0;
        
        setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, 
                  &timeout, sizeof(timeout));
    }
    
    ssize_t recvWithTimeout(void* buffer, size_t length, int flags) {
        return recv(sockfd, buffer, length, flags);
    }
    
    ssize_t sendWithTimeout(const void* buffer, size_t length, int flags) {
        return send(sockfd, buffer, length, flags);
    }
};

IO多路复用

select vs epoll 对比

特性

select

epoll

最大连接数

1024(FD_SETSIZE)

无限制

时间复杂度

O(n)

O(1)

适用场景

少量连接

大量连接

跨平台

Linux专有

性能

连接数少时可接受

高性能服务器

select 示例

#include <sys/select.h>
#include <vector>

class SelectServer {
private:
    int serverFd;
    std::vector<int> clients;
    
public:
    void run() {
        fd_set readfds;
        int maxFd = serverFd;
        
        while (true) {
            FD_ZERO(&readfds);
            FD_SET(serverFd, &readfds);  // 监听服务器socket
            
            // 监听所有客户端
            for (int clientFd : clients) {
                FD_SET(clientFd, &readfds);
                maxFd = std::max(maxFd, clientFd);
            }
            
            // 等待事件(阻塞)
            int ret = select(maxFd + 1, &readfds, nullptr, nullptr, nullptr);
            if (ret < 0) {
                perror("select");
                break;
            }
            
            // 检查服务器socket(新连接)
            if (FD_ISSET(serverFd, &readfds)) {
                int clientFd = accept(serverFd, nullptr, nullptr);
                clients.push_back(clientFd);
            }
            
            // 检查客户端socket(数据到达)
            for (auto it = clients.begin(); it != clients.end();) {
                if (FD_ISSET(*it, &readfds)) {
                    char buffer[1024];
                    ssize_t n = recv(*it, buffer, sizeof(buffer), 0);
                    if (n <= 0) {
                        close(*it);
                        it = clients.erase(it);
                        continue;
                    }
                    // 处理数据
                }
                ++it;
            }
        }
    }
};

epoll 示例(Linux高性能)

#include <sys/epoll.h>

class EpollServer {
private:
    int serverFd;
    int epollFd;
    
public:
    EpollServer(int port) {
        // 创建server socket
        serverFd = socket(AF_INET, SOCK_STREAM, 0);
        // bind, listen...
        
        // 创建epoll
        epollFd = epoll_create1(0);
        
        // 添加服务器socket到epoll
        epoll_event ev;
        ev.events = EPOLLIN;  // 监听可读事件
        ev.data.fd = serverFd;
        epoll_ctl(epollFd, EPOLL_CTL_ADD, serverFd, &ev);
    }
    
    void run() {
        const int MAX_EVENTS = 10;
        epoll_event events[MAX_EVENTS];
        
        while (true) {
            // 等待事件
            int nfds = epoll_wait(epollFd, events, MAX_EVENTS, -1);
            
            for (int i = 0; i < nfds; ++i) {
                if (events[i].data.fd == serverFd) {
                    // 新连接
                    int clientFd = accept(serverFd, nullptr, nullptr);
                    
                    // 添加客户端到epoll
                    epoll_event ev;
                    ev.events = EPOLLIN | EPOLLET;  // 边缘触发
                    ev.data.fd = clientFd;
                    epoll_ctl(epollFd, EPOLL_CTL_ADD, clientFd, &ev);
                } else {
                    // 客户端数据
                    int clientFd = events[i].data.fd;
                    char buffer[1024];
                    ssize_t n = recv(clientFd, buffer, sizeof(buffer), 0);
                    
                    if (n <= 0) {
                        // 连接关闭或错误
                        epoll_ctl(epollFd, EPOLL_CTL_DEL, clientFd, nullptr);
                        close(clientFd);
                    } else {
                        // 处理数据
                        send(clientFd, buffer, n, 0);
                    }
                }
            }
        }
    }
    
    ~EpollServer() {
        close(epollFd);
        close(serverFd);
    }
};

epoll 触发模式

水平触发(Level Triggered,默认):

  • 只要fd可读/可写,epoll_wait就返回

  • 适合:简单场景,不易遗漏事件

  • 缺点:可能重复触发

边缘触发(Edge Triggered):

  • 只在状态变化时触发一次

  • 必须一次性读完所有数据

  • 适合:高性能服务器

  • 需配合非阻塞IO

// 边缘触发 + 非阻塞IO示例
void handleEdgeTriggered(int fd) {
    while (true) {
        char buffer[1024];
        ssize_t n = recv(fd, buffer, sizeof(buffer), MSG_DONTWAIT);
        
        if (n > 0) {
            // 处理数据
        } else if (n == 0) {
            // 连接关闭
            break;
        } else {
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                // 数据读完了
                break;
            } else {
                // 真正的错误
                perror("recv");
                break;
            }
        }
    }
}

选择建议:

  • 少量连接(<100):select(跨平台)

  • 大量连接:epoll(Linux)/ kqueue(BSD)/ IOCP(Windows)

  • 高性能要求:边缘触发 + 非阻塞IO

  • 简单应用:水平触发