第39章 网络编程 网络编程基础 网络编程是指编写能够在网络上进行通信的程序。C++提供了多种方式来实现网络编程,从底层的套接字API到高级的网络库。
网络编程的基本概念 协议 :通信双方共同遵守的规则,如TCP、UDP、HTTP等IP地址 :网络设备的唯一标识端口 :应用程序在设备上的唯一标识套接字 :网络通信的端点客户端/服务器 :网络通信的两种角色网络协议栈 网络协议通常按照层次结构组织,最常见的是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-65535TCP协议 面向连接 :需要先建立连接可靠传输 :保证数据按序到达,无丢失流量控制 :根据接收方能力调整发送速率拥塞控制 :根据网络状况调整发送速率UDP协议 无连接 :不需要建立连接不可靠传输 :不保证数据到达顺序和完整性速度快 :开销小,适用于实时应用套接字编程 基本套接字类型 流式套接字(SOCK_STREAM) :基于TCP协议,提供可靠的面向连接的通信数据报套接字(SOCK_DGRAM) :基于UDP协议,提供无连接的不可靠通信原始套接字(SOCK_RAW) :直接访问网络层协议,用于网络测试和诊断套接字编程步骤 TCP服务器 创建套接字 绑定地址和端口 监听连接 接受连接 收发数据 关闭连接 TCP客户端 创建套接字 连接服务器 收发数据 关闭连接 基本套接字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 () { 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 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 ); 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 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 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 ); 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 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 ) { 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); 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 ) { 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++网络编程的基本概念和技术,包括:
网络编程基础 :网络协议栈、TCP/IP协议基础套接字编程 :基本套接字API、TCP服务器/客户端示例网络库 :Boost.Asio和std::asio的使用HTTP客户端/服务器 :HTTP基础、简单实现网络编程最佳实践 :错误处理、资源管理、安全性、性能优化网络编程常见问题 :连接失败、数据传输问题、性能问题、安全性问题网络编程工具 :网络分析工具、性能测试工具、网络库示例 :聊天室服务器通过本章的学习,读者应该能够掌握C++网络编程的基本原理和技术,能够编写简单的网络应用程序,如TCP服务器/客户端、HTTP服务器/客户端等。在实际开发中,应根据具体需求选择合适的网络库和技术,遵循最佳实践,确保代码的可靠性、安全性和性能。