第39章 网络编程

网络编程基础

网络编程是指编写能够在网络上进行通信的程序。C++提供了多种方式来实现网络编程,从底层的套接字API到高级的网络库。

网络编程的基本概念

  1. 协议:通信双方共同遵守的规则,如TCP、UDP、HTTP等
  2. IP地址:网络设备的唯一标识
  3. 端口:应用程序在设备上的唯一标识
  4. 套接字:网络通信的端点
  5. 客户端/服务器:网络通信的两种角色

网络协议栈

网络协议通常按照层次结构组织,最常见的是OSI七层模型和TCP/IP四层模型:

OSI七层模型TCP/IP四层模型常见协议
应用层应用层HTTP、FTP、SMTP、DNS
表示层应用层SSL/TLS、数据压缩
会话层应用层RPC、NetBIOS
传输层传输层TCP、UDP
网络层网络层IP、ICMP、ARP
数据链路层网络接口层Ethernet、Wi-Fi
物理层网络接口层双绞线、光纤

TCP/IP协议基础

IP地址

  • IPv4:32位地址,格式为点分十进制(如192.168.1.1)
  • IPv6:128位地址,格式为冒分十六进制(如2001:0db8:85a3:0000:0000:8a2e:0370:7334)

端口

  • 范围:0-65535
  • 知名端口:0-1023(如HTTP 80、HTTPS 443)
  • 动态端口:1024-65535

TCP协议

  • 面向连接:需要先建立连接
  • 可靠传输:保证数据按序到达,无丢失
  • 流量控制:根据接收方能力调整发送速率
  • 拥塞控制:根据网络状况调整发送速率

UDP协议

  • 无连接:不需要建立连接
  • 不可靠传输:不保证数据到达顺序和完整性
  • 速度快:开销小,适用于实时应用

套接字编程

基本套接字类型

  1. 流式套接字(SOCK_STREAM):基于TCP协议,提供可靠的面向连接的通信
  2. 数据报套接字(SOCK_DGRAM):基于UDP协议,提供无连接的不可靠通信
  3. 原始套接字(SOCK_RAW):直接访问网络层协议,用于网络测试和诊断

套接字编程步骤

TCP服务器

  1. 创建套接字
  2. 绑定地址和端口
  3. 监听连接
  4. 接受连接
  5. 收发数据
  6. 关闭连接

TCP客户端

  1. 创建套接字
  2. 连接服务器
  3. 收发数据
  4. 关闭连接

基本套接字API

Windows套接字(Winsock)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")

int main() {
// 初始化Winsock
WSADATA wsaData;
int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != 0) {
printf("WSAStartup failed: %d\n", iResult);
return 1;
}

// 后续代码...

// 清理
WSACleanup();
return 0;
}

跨平台套接字

使用条件编译实现跨平台套接字:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib")
typedef int socklen_t;
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <netdb.h>
#endif

// 初始化函数
void initSocketLibrary() {
#ifdef _WIN32
WSADATA wsaData;
WSAStartup(MAKEWORD(2, 2), &wsaData);
#endif
}

// 清理函数
void cleanupSocketLibrary() {
#ifdef _WIN32
WSACleanup();
#endif
}

// 关闭套接字
void closeSocket(int sockfd) {
#ifdef _WIN32
closesocket(sockfd);
#else
close(sockfd);
#endif
}

TCP服务器示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
#include <iostream>
#include <string>

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

int main() {
#ifdef _WIN32
// 初始化Winsock
WSADATA wsaData;
int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != 0) {
std::cerr << "WSAStartup failed: " << iResult << std::endl;
return 1;
}
#endif

// 创建套接字
int serverSocket = socket(AF_INET, SOCK_STREAM, 0);
if (serverSocket == -1) {
std::cerr << "Failed to create socket" << std::endl;
#ifdef _WIN32
WSACleanup();
#endif
return 1;
}

// 设置服务器地址
struct sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(8080); // 端口8080
serverAddr.sin_addr.s_addr = INADDR_ANY; // 监听所有接口

// 绑定套接字
if (bind(serverSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == -1) {
std::cerr << "Failed to bind socket" << std::endl;
closeSocket(serverSocket);
#ifdef _WIN32
WSACleanup();
#endif
return 1;
}

// 监听连接
if (listen(serverSocket, 5) == -1) {
std::cerr << "Failed to listen" << std::endl;
closeSocket(serverSocket);
#ifdef _WIN32
WSACleanup();
#endif
return 1;
}

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

// 接受连接
struct sockaddr_in clientAddr;
socklen_t clientAddrLen = sizeof(clientAddr);
int clientSocket = accept(serverSocket, (struct sockaddr*)&clientAddr, &clientAddrLen);
if (clientSocket == -1) {
std::cerr << "Failed to accept connection" << std::endl;
closeSocket(serverSocket);
#ifdef _WIN32
WSACleanup();
#endif
return 1;
}

// 获取客户端地址
char clientIP[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &(clientAddr.sin_addr), clientIP, INET_ADDRSTRLEN);
std::cout << "Connection accepted from " << clientIP << ":" << ntohs(clientAddr.sin_port) << std::endl;

// 收发数据
char buffer[1024] = {0};
int bytesRead = recv(clientSocket, buffer, sizeof(buffer), 0);
if (bytesRead > 0) {
std::cout << "Received: " << buffer << std::endl;

// 发送响应
std::string response = "Hello from server!";
send(clientSocket, response.c_str(), response.size(), 0);
std::cout << "Response sent" << std::endl;
}

// 关闭套接字
closeSocket(clientSocket);
closeSocket(serverSocket);

#ifdef _WIN32
// 清理Winsock
WSACleanup();
#endif

return 0;
}

TCP客户端示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
#include <iostream>
#include <string>

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

int main() {
#ifdef _WIN32
// 初始化Winsock
WSADATA wsaData;
int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != 0) {
std::cerr << "WSAStartup failed: " << iResult << std::endl;
return 1;
}
#endif

// 创建套接字
int clientSocket = socket(AF_INET, SOCK_STREAM, 0);
if (clientSocket == -1) {
std::cerr << "Failed to create socket" << std::endl;
#ifdef _WIN32
WSACleanup();
#endif
return 1;
}

// 设置服务器地址
struct sockaddr_in serverAddr;
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(8080); // 服务器端口

// 将IP地址转换为网络字节序
if (inet_pton(AF_INET, "127.0.0.1", &(serverAddr.sin_addr)) <= 0) {
std::cerr << "Invalid address" << std::endl;
closeSocket(clientSocket);
#ifdef _WIN32
WSACleanup();
#endif
return 1;
}

// 连接服务器
if (connect(clientSocket, (struct sockaddr*)&serverAddr, sizeof(serverAddr)) == -1) {
std::cerr << "Failed to connect to server" << std::endl;
closeSocket(clientSocket);
#ifdef _WIN32
WSACleanup();
#endif
return 1;
}

std::cout << "Connected to server" << std::endl;

// 发送数据
std::string message = "Hello from client!";
send(clientSocket, message.c_str(), message.size(), 0);
std::cout << "Message sent: " << message << std::endl;

// 接收响应
char buffer[1024] = {0};
int bytesRead = recv(clientSocket, buffer, sizeof(buffer), 0);
if (bytesRead > 0) {
std::cout << "Received response: " << buffer << std::endl;
}

// 关闭套接字
closeSocket(clientSocket);

#ifdef _WIN32
// 清理Winsock
WSACleanup();
#endif

return 0;
}

网络库

Boost.Asio

Boost.Asio是一个跨平台的网络和底层I/O库,提供了异步I/O操作的能力。

基本概念

  • I/O服务:提供I/O操作的核心对象
  • 套接字:网络通信的端点
  • 缓冲区:数据存储的区域
  • 处理器:异步操作完成后的回调函数

TCP服务器示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include <iostream>
#include <boost/asio.hpp>

using boost::asio::ip::tcp;

class session {
public:
session(tcp::socket socket) : socket_(std::move(socket)) {}

void start() {
do_read();
}

private:
void do_read() {
auto self(shared_from_this());
socket_.async_read_some(boost::asio::buffer(data_, max_length),
[this, self](boost::system::error_code ec, std::size_t length) {
if (!ec) {
std::cout << "Received: " << std::string(data_, length) << std::endl;
do_write(length);
}
});
}

void do_write(std::size_t length) {
auto self(shared_from_this());
boost::asio::async_write(socket_, boost::asio::buffer(data_, length),
[this, self](boost::system::error_code ec, std::size_t /*length*/) {
if (!ec) {
do_read();
}
});
}

tcp::socket socket_;
enum { max_length = 1024 };
char data_[max_length];
};

class server {
public:
server(boost::asio::io_context& io_context, short port) :
acceptor_(io_context, tcp::endpoint(tcp::v4(), port)),
socket_(io_context) {
do_accept();
}

private:
void do_accept() {
acceptor_.async_accept(socket_,
[this](boost::system::error_code ec) {
if (!ec) {
std::make_shared<session>(std::move(socket_))->start();
}
do_accept();
});
}

tcp::acceptor acceptor_;
tcp::socket socket_;
};

int main() {
try {
boost::asio::io_context io_context;
server s(io_context, 8080);
std::cout << "Server listening on port 8080..." << std::endl;
io_context.run();
} catch (std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl;
}

return 0;
}

TCP客户端示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <iostream>
#include <boost/asio.hpp>

using boost::asio::ip::tcp;

int main() {
try {
boost::asio::io_context io_context;

tcp::resolver resolver(io_context);
tcp::resolver::results_type endpoints = resolver.resolve("localhost", "8080");

tcp::socket socket(io_context);
boost::asio::connect(socket, endpoints);

std::cout << "Connected to server" << std::endl;

// 发送数据
std::string message = "Hello from Boost.Asio client!";
boost::asio::write(socket, boost::asio::buffer(message));
std::cout << "Message sent: " << message << std::endl;

// 接收响应
char reply[1024];
size_t reply_length = boost::asio::read(socket, boost::asio::buffer(reply, message.size()));
std::cout << "Reply received: " << std::string(reply, reply_length) << std::endl;
} catch (std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl;
}

return 0;
}

Asio C++20 (std::asio)

C++20引入了标准库中的Asio,基于Boost.Asio的设计。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>
#include <string>
#include <asio.hpp>

using asio::ip::tcp;

int main() {
try {
asio::io_context io_context;

tcp::resolver resolver(io_context);
tcp::resolver::results_type endpoints = resolver.resolve("localhost", "8080");

tcp::socket socket(io_context);
asio::connect(socket, endpoints);

std::cout << "Connected to server" << std::endl;

// 发送数据
std::string message = "Hello from std::asio client!";
asio::write(socket, asio::buffer(message));
std::cout << "Message sent: " << message << std::endl;

// 接收响应
char reply[1024];
size_t reply_length = asio::read(socket, asio::buffer(reply, message.size()));
std::cout << "Reply received: " << std::string(reply, reply_length) << std::endl;
} catch (std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl;
}

return 0;
}

HTTP客户端/服务器

HTTP基础

  • 请求方法:GET、POST、PUT、DELETE等
  • 状态码:200 OK、404 Not Found、500 Internal Server Error等
  • 头部:Content-Type、Content-Length、Authorization等
  • :请求或响应的数据

简单HTTP服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <iostream>
#include <string>
#include <boost/asio.hpp>

using boost::asio::ip::tcp;

int main() {
try {
boost::asio::io_context io_context;
tcp::acceptor acceptor(io_context, tcp::endpoint(tcp::v4(), 8080));

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

while (true) {
tcp::socket socket(io_context);
acceptor.accept(socket);

// 读取请求
char buffer[1024] = {0};
boost::system::error_code error;
size_t bytes_read = socket.read_some(boost::asio::buffer(buffer), error);

if (!error) {
std::cout << "Received request:\n" << buffer << std::endl;

// 发送响应
std::string response = "HTTP/1.1 200 OK\r\n";
response += "Content-Type: text/html\r\n";
response += "Content-Length: 20\r\n";
response += "\r\n";
response += "Hello, HTTP client!";

boost::asio::write(socket, boost::asio::buffer(response));
}
}
} catch (std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl;
}

return 0;
}

简单HTTP客户端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <iostream>
#include <string>
#include <boost/asio.hpp>

using boost::asio::ip::tcp;

int main() {
try {
boost::asio::io_context io_context;

// 解析服务器地址
tcp::resolver resolver(io_context);
tcp::resolver::results_type endpoints = resolver.resolve("example.com", "80");

// 连接服务器
tcp::socket socket(io_context);
boost::asio::connect(socket, endpoints);

// 发送HTTP请求
std::string request = "GET / HTTP/1.1\r\n";
request += "Host: example.com\r\n";
request += "Connection: close\r\n";
request += "\r\n";

boost::asio::write(socket, boost::asio::buffer(request));

// 读取响应
char buffer[4096] = {0};
boost::system::error_code error;

while (socket.read_some(boost::asio::buffer(buffer), error)) {
std::cout << buffer;
memset(buffer, 0, sizeof(buffer));
}

if (error != boost::asio::error::eof) {
throw boost::system::system_error(error);
}
} catch (std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl;
}

return 0;
}

网络编程最佳实践

1. 错误处理

  • 检查所有返回值:所有套接字函数都可能失败
  • 使用异常:对于严重错误,使用异常处理
  • 记录错误:详细记录错误信息,便于调试

2. 资源管理

  • 使用智能指针:管理动态分配的资源
  • RAII:使用RAII模式管理套接字等资源
  • 正确关闭连接:确保所有连接都被正确关闭

3. 安全性

  • 输入验证:验证所有用户输入
  • 防止缓冲区溢出:使用安全的缓冲区操作
  • 使用HTTPS:对于敏感数据,使用加密传输
  • 防止DoS攻击:限制连接数和请求频率

4. 性能优化

  • 使用非阻塞I/O:提高并发处理能力
  • 使用线程池:充分利用多核处理器
  • 缓冲区管理:合理管理缓冲区大小
  • 连接复用:使用持久连接减少连接建立开销

5. 跨平台兼容性

  • 使用条件编译:处理不同平台的差异
  • 使用跨平台库:如Boost.Asio或std::asio
  • 测试:在多个平台上测试代码

网络编程常见问题

1. 连接失败

  • 检查网络连接:确保网络正常
  • 检查防火墙:确保端口未被防火墙阻止
  • 检查服务器状态:确保服务器正在运行
  • 检查地址和端口:确保地址和端口正确

2. 数据传输问题

  • 检查缓冲区大小:确保缓冲区足够大
  • 检查数据格式:确保数据格式正确
  • 检查编码:确保字符编码一致
  • 检查字节序:确保网络字节序转换正确

3. 性能问题

  • 检查算法:使用高效的算法
  • 检查数据结构:使用合适的数据结构
  • 检查I/O模式:使用非阻塞I/O
  • 检查线程模型:使用合适的线程模型

4. 安全性问题

  • 检查输入:验证所有输入
  • 检查权限:确保权限控制正确
  • 检查加密:使用合适的加密方法
  • 检查日志:确保敏感信息不被记录

网络编程工具

1. 网络分析工具

  • Wireshark:网络数据包分析器
  • tcpdump:命令行网络数据包分析器
  • Netcat:网络工具,可用于测试和调试

2. 性能测试工具

  • Apache Bench:HTTP性能测试工具
  • JMeter:负载测试工具
  • wrk:HTTP基准测试工具

3. 网络库

  • Boost.Asio:跨平台网络和I/O库
  • libcurl:客户端URL传输库
  • POCO:C++跨平台网络库
  • cpprestsdk:Microsoft的C++ REST SDK

示例:聊天室服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
#include <iostream>
#include <string>
#include <vector>
#include <memory>
#include <boost/asio.hpp>

using boost::asio::ip::tcp;

class chat_session : public std::enable_shared_from_this<chat_session> {
public:
chat_session(tcp::socket socket, std::vector<std::shared_ptr<chat_session>>& sessions)
: socket_(std::move(socket)), sessions_(sessions) {
}

void start() {
// 添加到会话列表
sessions_.push_back(shared_from_this());
do_read();
}

private:
void do_read() {
auto self(shared_from_this());
socket_.async_read_some(boost::asio::buffer(data_, max_length),
[this, self](boost::system::error_code ec, std::size_t length) {
if (!ec) {
// 广播消息给所有客户端
std::string message(data_, length);
broadcast(message);
do_read();
} else {
// 从会话列表中移除
remove_from_sessions();
}
});
}

void do_write(const std::string& message) {
auto self(shared_from_this());
boost::asio::async_write(socket_, boost::asio::buffer(message),
[this, self](boost::system::error_code ec, std::size_t /*length*/) {
if (ec) {
remove_from_sessions();
}
});
}

void broadcast(const std::string& message) {
for (auto session : sessions_) {
session->do_write(message);
}
}

void remove_from_sessions() {
auto it = std::find(sessions_.begin(), sessions_.end(), shared_from_this());
if (it != sessions_.end()) {
sessions_.erase(it);
}
}

tcp::socket socket_;
std::vector<std::shared_ptr<chat_session>>& sessions_;
enum { max_length = 1024 };
char data_[max_length];
};

class chat_server {
public:
chat_server(boost::asio::io_context& io_context, short port)
: acceptor_(io_context, tcp::endpoint(tcp::v4(), port)),
socket_(io_context) {
do_accept();
}

private:
void do_accept() {
acceptor_.async_accept(socket_,
[this](boost::system::error_code ec) {
if (!ec) {
std::make_shared<chat_session>(std::move(socket_), sessions_)->start();
}
do_accept();
});
}

tcp::acceptor acceptor_;
tcp::socket socket_;
std::vector<std::shared_ptr<chat_session>> sessions_;
};

int main() {
try {
boost::asio::io_context io_context;
chat_server server(io_context, 8080);
std::cout << "Chat server listening on port 8080..." << std::endl;
io_context.run();
} catch (std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl;
}

return 0;
}

总结

本章介绍了C++网络编程的基本概念和技术,包括:

  1. 网络编程基础:网络协议栈、TCP/IP协议基础
  2. 套接字编程:基本套接字API、TCP服务器/客户端示例
  3. 网络库:Boost.Asio和std::asio的使用
  4. HTTP客户端/服务器:HTTP基础、简单实现
  5. 网络编程最佳实践:错误处理、资源管理、安全性、性能优化
  6. 网络编程常见问题:连接失败、数据传输问题、性能问题、安全性问题
  7. 网络编程工具:网络分析工具、性能测试工具、网络库
  8. 示例:聊天室服务器

通过本章的学习,读者应该能够掌握C++网络编程的基本原理和技术,能够编写简单的网络应用程序,如TCP服务器/客户端、HTTP服务器/客户端等。在实际开发中,应根据具体需求选择合适的网络库和技术,遵循最佳实践,确保代码的可靠性、安全性和性能。