C++ Network Programming
Overview
Network programming is an important part of modern applications. Although the C++ standard library currently does not have built-in network support, network programming can be done through Socket API, third-party libraries like Boost.Asio, etc. This chapter introduces Socket basics, TCP/UDP programming, asynchronous network programming, etc.
🔌 Socket Basics
TCP Client and Server
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 failed: " << 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 : "Unknown error";
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;
}
// Create socket
server_socket_ = socket(AF_INET, SOCK_STREAM, 0);
if (server_socket_ < 0) {
std::cerr << "Failed to create socket: " << SocketUtils::getLastErrorString() << std::endl;
return false;
}
// Set address reuse
int opt = 1;
setsockopt(server_socket_, SOL_SOCKET, SO_REUSEADDR, (char*)&opt, sizeof(opt));
// Bind address
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 << "Bind failed: " << SocketUtils::getLastErrorString() << std::endl;
close(server_socket_);
return false;
}
// Listen
if (listen(server_socket_, 5) < 0) {
std::cerr << "Listen failed: " << SocketUtils::getLastErrorString() << std::endl;
close(server_socket_);
return false;
}
running_ = true;
std::cout << "Server started on port " << 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 << "Accept failed: " << SocketUtils::getLastErrorString() << std::endl;
}
continue;
}
// Get client address
char client_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, INET_ADDRSTRLEN);
std::cout << "Client connected: " << client_ip << ":" << ntohs(client_addr.sin_port) << std::endl;
// Handle client in new thread
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 << "Received message: " << message << std::endl;
// Echo server: send same message back
std::string response = "Echo: " + message;
send(client_socket, response.c_str(), response.length(), 0);
}
close(client_socket);
std::cout << "Client disconnected" << 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;
}
// Create socket
client_socket_ = socket(AF_INET, SOCK_STREAM, 0);
if (client_socket_ < 0) {
std::cerr << "Failed to create socket: " << SocketUtils::getLastErrorString() << std::endl;
return false;
}
// Resolve host address
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) {
// Try to resolve by hostname
hostent* he = gethostbyname(host.c_str());
if (!he) {
std::cerr << "Hostname resolution failed: " << host << std::endl;
close(client_socket_);
return false;
}
memcpy(&server_addr.sin_addr, he->h_addr_list[0], he->h_length);
}
// Connect to server
if (::connect(client_socket_, (sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
std::cerr << "Connection failed: " << SocketUtils::getLastErrorString() << std::endl;
close(client_socket_);
return false;
}
std::cout << "Connected to " << 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 Programming
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;
}
// Create UDP socket
socket_ = socket(AF_INET, SOCK_DGRAM, 0);
if (socket_ < 0) {
std::cerr << "Failed to create UDP socket: " << SocketUtils::getLastErrorString() << std::endl;
return false;
}
// Bind address
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 bind failed: " << SocketUtils::getLastErrorString() << std::endl;
close(socket_);
return false;
}
running_ = true;
std::cout << "UDP server started on port " << 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 receive failed: " << 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 << "Received UDP message from " << client_ip << ":" << ntohs(client_addr.sin_port)
<< " - " << buffer << std::endl;
// Send reply
std::string response = "UDP Echo: " + 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;
}
// Create UDP socket
socket_ = socket(AF_INET, SOCK_DGRAM, 0);
if (socket_ < 0) {
std::cerr << "Failed to create UDP socket: " << SocketUtils::getLastErrorString() << std::endl;
return false;
}
// Set server address
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 hostname resolution failed: " << host << std::endl;
close(socket_);
return false;
}
memcpy(&server_addr_.sin_addr, he->h_addr_list[0], he->h_length);
}
std::cout << "UDP client ready, target: " << 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 Client Example
Simple HTTP Request
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;
}
// Construct HTTP GET request
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 << "Sending HTTP request:\n" << request_str << std::endl;
if (!client.sendMessage(request_str)) {
response.status_code = -2;
return response;
}
// Receive response
std::string full_response;
std::string chunk;
while (!(chunk = client.receiveMessage()).empty()) {
full_response += chunk;
}
// Parse HTTP response
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;
// Parse status line
if (std::getline(stream, line)) {
std::istringstream status_stream(line);
std::string version;
status_stream >> version >> response.status_code;
}
// Parse headers
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);
// Trim leading/trailing whitespace
value.erase(0, value.find_first_not_of(" \t"));
value.erase(value.find_last_not_of(" \t\r") + 1);
response.headers[key] = value;
}
}
// Read body
std::ostringstream body_stream;
while (std::getline(stream, line)) {
body_stream << line << "\n";
}
response.body = body_stream.str();
}
};
// Network programming example
class NetworkDemo {
public:
static void tcpEchoDemo() {
std::cout << "=== TCP Echo Server Demo ===" << std::endl;
// Start server in background thread
TCPServer server(8080);
std::thread server_thread([&server]() {
if (server.start()) {
server.run();
}
});
// Wait for server to start
std::this_thread::sleep_for(std::chrono::milliseconds(500));
// Client connection and testing
TCPClient client;
if (client.connect("127.0.0.1", 8080)) {
for (int i = 0; i < 3; ++i) {
std::string message = "Test message " + std::to_string(i + 1);
if (client.sendMessage(message)) {
std::string response = client.receiveMessage();
std::cout << "Sent: " << message << std::endl;
std::cout << "Received: " << 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 Echo Server Demo ===" << std::endl;
// Start UDP server
UDPServer server(8081);
std::thread server_thread([&server]() {
if (server.start()) {
server.run();
}
});
std::this_thread::sleep_for(std::chrono::milliseconds(500));
// UDP client testing
UDPClient client;
if (client.connect("127.0.0.1", 8081)) {
for (int i = 0; i < 3; ++i) {
std::string message = "UDP Test " + std::to_string(i + 1);
if (client.sendMessage(message)) {
std::string response = client.receiveMessage();
std::cout << "Sent: " << message << std::endl;
std::cout << "Received: " << 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 Client Demo ===" << std::endl;
// Note: This requires an actual HTTP server
auto response = HTTPClient::get("httpbin.org", 80, "/get");
std::cout << "HTTP Status Code: " << response.status_code << std::endl;
std::cout << "Response Headers:" << std::endl;
for (const auto& header : response.headers) {
std::cout << " " << header.first << ": " << header.second << std::endl;
}
std::cout << "Response Body:\n" << response.body << std::endl;
}
};
int main() {
NetworkDemo::tcpEchoDemo();
NetworkDemo::udpEchoDemo();
// HTTP demo requires network connection
// NetworkDemo::httpClientDemo();
return 0;
}Summary
Although C++ doesn't have direct standard library support for network programming, powerful network functionality can be implemented through the Socket API:
Core Concepts
- Socket: Network communication endpoint
- TCP: Reliable connection-oriented protocol
- UDP: Fast connectionless protocol
- Client/Server: Network application architecture pattern
Programming Models
| Protocol | Features | Use Cases |
|---|---|---|
| TCP | Reliable, ordered, connection-oriented | Web services, file transfer |
| UDP | Fast, connectionless, unreliable | Gaming, real-time communication |
| HTTP | Application layer protocol | Web applications, APIs |
Best Practices
- Error handling: Fully handle network errors
- Non-blocking I/O: Avoid program blocking
- Multithreading: Concurrently handle multiple connections
- Resource management: Promptly close sockets and clean up resources
- Cross-platform: Handle differences between different systems
Advanced Techniques
- Asynchronous I/O: Use select/poll/epoll
- Thread pools: Reuse threads to handle connections
- Connection pools: Reuse network connections
- SSL/TLS: Secure communication encryption
Third-party Libraries
- Boost.Asio: Asynchronous network programming library
- libcurl: HTTP/HTTPS client library
- Poco: Network and application framework
- Qt Network: Qt framework network module
Important Notes
- Byte order: Network byte order conversion
- Buffers: Reasonably set buffer sizes
- Timeouts: Set appropriate timeout values
- Security: Protect against network attacks
- Performance: Optimize network I/O performance
Network programming is the foundation for building distributed applications and services, and mastering these technologies is crucial for modern C++ development.