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++开发至关重要。