C++ 网络编程基础
C++ 网络编程是构建高性能网络应用的基础,涉及套接字编程、协议实现和并发处理等核心概念。
套接字基础
TCP 套接字示例:
cpp#include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <unistd.h> #include <cstring> class TCPServer { private: int serverSocket; int port; bool running; public: TCPServer(int p) : port(p), running(false) { serverSocket = socket(AF_INET, SOCK_STREAM, 0); if (serverSocket < 0) { throw std::runtime_error("Failed to create socket"); } // 设置地址重用 int opt = 1; setsockopt(serverSocket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); } void start() { struct sockaddr_in serverAddr; memset(&serverAddr, 0, sizeof(serverAddr)); serverAddr.sin_family = AF_INET; serverAddr.sin_addr.s_addr = INADDR_ANY; serverAddr.sin_port = htons(port); if (bind(serverSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) { throw std::runtime_error("Failed to bind socket"); } if (listen(serverSocket, 5) < 0) { throw std::runtime_error("Failed to listen on socket"); } running = true; std::cout << "Server listening on port " << port << std::endl; while (running) { struct sockaddr_in clientAddr; socklen_t clientLen = sizeof(clientAddr); int clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddr, &clientLen); if (clientSocket < 0) { if (running) { std::cerr << "Failed to accept connection" << std::endl; } continue; } char clientIP[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &clientAddr.sin_addr, clientIP, INET_ADDRSTRLEN); std::cout << "Client connected: " << clientIP << std::endl; handleClient(clientSocket); } } void stop() { running = false; close(serverSocket); } private: void handleClient(int clientSocket) { char buffer[1024]; while (true) { ssize_t bytesRead = recv(clientSocket, buffer, sizeof(buffer) - 1, 0); if (bytesRead <= 0) { break; } buffer[bytesRead] = '\0'; std::cout << "Received: " << buffer << std::endl; // 发送响应 const char* response = "Message received"; send(clientSocket, response, strlen(response), 0); } close(clientSocket); } }; // 使用 int main() { try { TCPServer server(8080); server.start(); } catch (const std::exception& e) { std::cerr << "Error: " << e.what() << std::endl; } return 0; }
UDP 套接字示例:
cppclass UDPServer { private: int serverSocket; int port; public: UDPServer(int p) : port(p) { serverSocket = socket(AF_INET, SOCK_DGRAM, 0); if (serverSocket < 0) { throw std::runtime_error("Failed to create UDP socket"); } struct sockaddr_in serverAddr; memset(&serverAddr, 0, sizeof(serverAddr)); serverAddr.sin_family = AF_INET; serverAddr.sin_addr.s_addr = INADDR_ANY; serverAddr.sin_port = htons(port); if (bind(serverSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) < 0) { throw std::runtime_error("Failed to bind UDP socket"); } } void receive() { char buffer[1024]; struct sockaddr_in clientAddr; socklen_t clientLen = sizeof(clientAddr); while (true) { ssize_t bytesRead = recvfrom(serverSocket, buffer, sizeof(buffer) - 1, 0, (struct sockaddr*)&clientAddr, &clientLen); if (bytesRead < 0) { std::cerr << "Failed to receive data" << std::endl; continue; } buffer[bytesRead] = '\0'; char clientIP[INET_ADDRSTRLEN]; inet_ntop(AF_INET, &clientAddr.sin_addr, clientIP, INET_ADDRSTRLEN); std::cout << "Received from " << clientIP << ": " << buffer << std::endl; // 发送响应 const char* response = "UDP Response"; sendto(serverSocket, response, strlen(response), 0, (struct sockaddr*)&clientAddr, clientLen); } } ~UDPServer() { close(serverSocket); } };
非阻塞 I/O
设置非阻塞套接字:
cpp#include <fcntl.h> void setNonBlocking(int socket) { int flags = fcntl(socket, F_GETFL, 0); if (flags < 0) { throw std::runtime_error("Failed to get socket flags"); } if (fcntl(socket, F_SETFL, flags | O_NONBLOCK) < 0) { throw std::runtime_error("Failed to set non-blocking mode"); } } // 使用 int sock = socket(AF_INET, SOCK_STREAM, 0); setNonBlocking(sock);
使用 select 进行多路复用:
cpp#include <sys/select.h> class SelectServer { private: int serverSocket; int maxFd; fd_set readFds; public: SelectServer(int port) { serverSocket = socket(AF_INET, SOCK_STREAM, 0); setNonBlocking(serverSocket); struct sockaddr_in serverAddr; memset(&serverAddr, 0, sizeof(serverAddr)); serverAddr.sin_family = AF_INET; serverAddr.sin_addr.s_addr = INADDR_ANY; serverAddr.sin_port = htons(port); bind(serverSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)); listen(serverSocket, 5); maxFd = serverSocket; FD_ZERO(&readFds); FD_SET(serverSocket, &readFds); } void run() { while (true) { fd_set tempFds = readFds; int activity = select(maxFd + 1, &tempFds, nullptr, nullptr, nullptr); if (activity < 0) { std::cerr << "Select error" << std::endl; continue; } // 检查新连接 if (FD_ISSET(serverSocket, &tempFds)) { handleNewConnection(); } // 检查现有连接 for (int fd = 0; fd <= maxFd; ++fd) { if (fd != serverSocket && FD_ISSET(fd, &tempFds)) { handleClientData(fd); } } } } private: void handleNewConnection() { struct sockaddr_in clientAddr; socklen_t clientLen = sizeof(clientAddr); int clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddr, &clientLen); if (clientSocket < 0) { return; } setNonBlocking(clientSocket); FD_SET(clientSocket, &readFds); if (clientSocket > maxFd) { maxFd = clientSocket; } } void handleClientData(int fd) { char buffer[1024]; ssize_t bytesRead = recv(fd, buffer, sizeof(buffer) - 1, 0); if (bytesRead <= 0) { close(fd); FD_CLR(fd, &readFds); return; } buffer[bytesRead] = '\0'; std::cout << "Received: " << buffer << std::endl; } };
epoll 高性能 I/O
epoll 服务器示例:
cpp#include <sys/epoll.h> class EpollServer { private: int serverSocket; int epollFd; static constexpr int MAX_EVENTS = 64; public: EpollServer(int port) { serverSocket = socket(AF_INET, SOCK_STREAM, 0); setNonBlocking(serverSocket); struct sockaddr_in serverAddr; memset(&serverAddr, 0, sizeof(serverAddr)); serverAddr.sin_family = AF_INET; serverAddr.sin_addr.s_addr = INADDR_ANY; serverAddr.sin_port = htons(port); bind(serverSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)); listen(serverSocket, 5); epollFd = epoll_create1(0); if (epollFd < 0) { throw std::runtime_error("Failed to create epoll instance"); } struct epoll_event event; event.events = EPOLLIN | EPOLLET; // 边缘触发 event.data.fd = serverSocket; if (epoll_ctl(epollFd, EPOLL_CTL_ADD, serverSocket, &event) < 0) { throw std::runtime_error("Failed to add socket to epoll"); } } void run() { struct epoll_event events[MAX_EVENTS]; while (true) { int numEvents = epoll_wait(epollFd, events, MAX_EVENTS, -1); if (numEvents < 0) { std::cerr << "Epoll wait error" << std::endl; continue; } for (int i = 0; i < numEvents; ++i) { if (events[i].data.fd == serverSocket) { handleNewConnection(); } else { handleClientData(events[i].data.fd); } } } } private: void handleNewConnection() { struct sockaddr_in clientAddr; socklen_t clientLen = sizeof(clientAddr); int clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddr, &clientLen); if (clientSocket < 0) { return; } setNonBlocking(clientSocket); struct epoll_event event; event.events = EPOLLIN | EPOLLET; event.data.fd = clientSocket; epoll_ctl(epollFd, EPOLL_CTL_ADD, clientSocket, &event); } void handleClientData(int fd) { char buffer[1024]; ssize_t bytesRead = recv(fd, buffer, sizeof(buffer) - 1, 0); if (bytesRead <= 0) { epoll_ctl(epollFd, EPOLL_CTL_DEL, fd, nullptr); close(fd); return; } buffer[bytesRead] = '\0'; std::cout << "Received: " << buffer << std::endl; const char* response = "HTTP/1.1 200 OK\r\nContent-Length: 13\r\n\r\nHello, World!"; send(fd, response, strlen(response), 0); } ~EpollServer() { close(serverSocket); close(epollFd); } };
HTTP 服务器实现
简单的 HTTP 服务器:
cppclass HTTPServer { private: int serverSocket; public: HTTPServer(int port) { serverSocket = socket(AF_INET, SOCK_STREAM, 0); int opt = 1; setsockopt(serverSocket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); struct sockaddr_in serverAddr; memset(&serverAddr, 0, sizeof(serverAddr)); serverAddr.sin_family = AF_INET; serverAddr.sin_addr.s_addr = INADDR_ANY; serverAddr.sin_port = htons(port); bind(serverSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)); listen(serverSocket, 5); } void run() { while (true) { struct sockaddr_in clientAddr; socklen_t clientLen = sizeof(clientAddr); int clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddr, &clientLen); if (clientSocket < 0) { continue; } handleRequest(clientSocket); close(clientSocket); } } private: void handleRequest(int clientSocket) { char buffer[4096]; ssize_t bytesRead = recv(clientSocket, buffer, sizeof(buffer) - 1, 0); if (bytesRead > 0) { buffer[bytesRead] = '\0'; std::cout << "Request:\n" << buffer << std::endl; // 解析 HTTP 请求 std::string request(buffer); std::string response = generateResponse(request); send(clientSocket, response.c_str(), response.length(), 0); } } std::string generateResponse(const std::string& request) { std::string response = "HTTP/1.1 200 OK\r\n"; response += "Content-Type: text/html\r\n"; response += "Connection: close\r\n"; std::string body = "<html><body><h1>Hello, World!</h1></body></html>"; response += "Content-Length: " + std::to_string(body.length()) + "\r\n"; response += "\r\n"; response += body; return response; } };
最佳实践
1. 使用 RAII 管理套接字
cppclass Socket { private: int fd; public: Socket(int domain, int type, int protocol) { fd = socket(domain, type, protocol); if (fd < 0) { throw std::runtime_error("Failed to create socket"); } } ~Socket() { if (fd >= 0) { close(fd); } } int getFd() const { return fd; } // 禁止拷贝 Socket(const Socket&) = delete; Socket& operator=(const Socket&) = delete; // 允许移动 Socket(Socket&& other) noexcept : fd(other.fd) { other.fd = -1; } };
2. 错误处理
cppvoid checkError(int result, const std::string& message) { if (result < 0) { throw std::runtime_error(message + ": " + strerror(errno)); } } // 使用 int sock = socket(AF_INET, SOCK_STREAM, 0); checkError(sock, "Failed to create socket");
3. 超时设置
cppvoid setSocketTimeout(int socket, int seconds) { struct timeval timeout; timeout.tv_sec = seconds; timeout.tv_usec = 0; setsockopt(socket, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); setsockopt(socket, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)); }
4. 使用线程池处理连接
cppclass ThreadPoolServer { private: TCPServer server; ThreadPool pool; public: ThreadPoolServer(int port, size_t threadCount) : server(port), pool(threadCount) {} void run() { server.setOnClientHandler([this](int clientSocket) { pool.enqueue([clientSocket]() { handleClient(clientSocket); }); }); server.start(); } };
注意事项
- 始终检查系统调用的返回值
- 处理部分读写的情况
- 注意字节序转换(htons, ntohs 等)
- 考虑使用更高级的网络库(如 Boost.Asio)
- 在多线程环境中注意线程安全
- 处理信号中断(EINTR)
- 考虑使用 TLS/SSL 加密通信
- 注意资源泄漏,确保套接字正确关闭