C++网络编程:TCP服务器与客户端的实现

一、学习目标与重点
本章将深入探讨C++网络编程的核心知识,帮助你掌握TCP服务器与客户端的实现。通过学习,你将能够:
- 理解网络编程的基本概念,掌握TCP/IP协议的核心要点
- 学会使用套接字编程,实现简单的TCP服务器与客户端
- 理解网络编程中的错误处理,提高程序的健壮性
- 学会使用Boost.Asio库,简化网络编程的复杂流程
- 培养网络编程思维,设计高效且稳定的网络应用
二、网络编程基础
2.1 TCP/IP协议简介
TCP/IP(传输控制协议/互联网协议)是互联网的核心协议,它提供了可靠的、面向连接的通信服务。TCP协议确保数据的可靠传输,通过三次握手建立连接,四次挥手关闭连接。
2.2 套接字编程基础
套接字(Socket)是网络编程的基础,它提供了进程间通信的接口。在C++中,可以使用<sys/socket.h>头文件提供的套接字API进行网络编程。
三、TCP服务器的实现
3.1 基本服务器结构
cpp
#include <iostream>
#include <string>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
const int PORT = 8080;
const int BUFFER_SIZE = 1024;
int main() {
std::cout << "=== TCP服务器示例 ===" << std::endl;
// 1. 创建套接字
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
std::cerr << "套接字创建失败" << std::endl;
return 1;
}
// 2. 设置套接字选项,允许地址复用
int opt = 1;
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
std::cerr << "设置套接字选项失败" << std::endl;
close(server_fd);
return 1;
}
// 3. 绑定地址和端口
sockaddr_in address;
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr*)&address, sizeof(address)) < 0) {
std::cerr << "绑定地址和端口失败" << std::endl;
close(server_fd);
return 1;
}
// 4. 监听连接
if (listen(server_fd, 3) < 0) {
std::cerr << "监听连接失败" << std::endl;
close(server_fd);
return 1;
}
std::cout << "服务器正在监听端口 " << PORT << "..." << std::endl;
// 5. 接受连接
int addrlen = sizeof(address);
int new_socket;
while (true) {
std::cout << "等待客户端连接..." << std::endl;
new_socket = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen);
if (new_socket < 0) {
std::cerr << "接受连接失败" << std::endl;
continue;
}
char client_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &address.sin_addr, client_ip, INET_ADDRSTRLEN);
std::cout << "客户端 " << client_ip << " 已连接" << std::endl;
// 6. 处理连接
char buffer[BUFFER_SIZE] = {0};
int valread = read(new_socket, buffer, BUFFER_SIZE);
if (valread < 0) {
std::cerr << "读取数据失败" << std::endl;
close(new_socket);
continue;
}
std::cout << "收到来自客户端的消息: " << buffer << std::endl;
// 7. 发送响应
std::string response = "服务器已收到消息: " + std::string(buffer);
send(new_socket, response.c_str(), response.length(), 0);
std::cout << "响应已发送" << std::endl;
// 8. 关闭连接
close(new_socket);
std::cout << "客户端连接已关闭" << std::endl;
}
// 9. 关闭服务器套接字
close(server_fd);
return 0;
}
3.2 多线程服务器
cpp
#include <iostream>
#include <string>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <thread>
#include <vector>
const int PORT = 8080;
const int BUFFER_SIZE = 1024;
void handleClient(int client_socket, const std::string& client_ip) {
char buffer[BUFFER_SIZE] = {0};
while (true) {
int valread = read(client_socket, buffer, BUFFER_SIZE);
if (valread < 0) {
std::cerr << "客户端 " << client_ip << " 读取数据失败" << std::endl;
break;
} else if (valread == 0) {
std::cout << "客户端 " << client_ip << " 已断开连接" << std::endl;
break;
}
std::cout << "收到来自客户端 " << client_ip << " 的消息: " << buffer << std::endl;
std::string response = "服务器已收到消息: " + std::string(buffer);
send(client_socket, response.c_str(), response.length(), 0);
std::cout << "响应已发送给客户端 " << client_ip << std::endl;
memset(buffer, 0, BUFFER_SIZE);
}
close(client_socket);
}
int main() {
std::cout << "=== 多线程TCP服务器示例 ===" << std::endl;
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
std::cerr << "套接字创建失败" << std::endl;
return 1;
}
int opt = 1;
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
std::cerr << "设置套接字选项失败" << std::endl;
close(server_fd);
return 1;
}
sockaddr_in address;
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr*)&address, sizeof(address)) < 0) {
std::cerr << "绑定地址和端口失败" << std::endl;
close(server_fd);
return 1;
}
if (listen(server_fd, 3) < 0) {
std::cerr << "监听连接失败" << std::endl;
close(server_fd);
return 1;
}
std::cout << "服务器正在监听端口 " << PORT << "..." << std::endl;
int addrlen = sizeof(address);
int new_socket;
std::vector<std::thread> client_threads;
while (true) {
std::cout << "等待客户端连接..." << std::endl;
new_socket = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen);
if (new_socket < 0) {
std::cerr << "接受连接失败" << std::endl;
continue;
}
char client_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &address.sin_addr, client_ip, INET_ADDRSTRLEN);
std::cout << "客户端 " << client_ip << " 已连接" << std::endl;
client_threads.emplace_back(handleClient, new_socket, client_ip);
}
for (auto& thread : client_threads) {
if (thread.joinable()) {
thread.join();
}
}
close(server_fd);
return 0;
}
四、TCP客户端的实现
4.1 基本客户端结构
cpp
#include <iostream>
#include <string>
#include <cstring>
#include <sys/socket.h>
#include <netinet/in.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <thread>
const int PORT = 8080;
const int BUFFER_SIZE = 1024;
const std::string SERVER_IP = "127.0.0.1";
void receiveMessages(int sock) {
char buffer[BUFFER_SIZE] = {0};
while (true) {
int valread = read(sock, buffer, BUFFER_SIZE);
if (valread < 0) {
std::cerr << "读取服务器响应失败" << std::endl;
break;
} else if (valread == 0) {
std::cout << "服务器已断开连接" << std::endl;
break;
}
std::cout << "服务器响应: " << buffer << std::endl;
memset(buffer, 0, BUFFER_SIZE);
}
}
int main() {
std::cout << "=== TCP客户端示例 ===" << std::endl;
// 1. 创建套接字
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
std::cerr << "套接字创建失败" << std::endl;
return 1;
}
// 2. 配置服务器地址
sockaddr_in serv_addr;
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
if (inet_pton(AF_INET, SERVER_IP.c_str(), &serv_addr.sin_addr) <= 0) {
std::cerr << "无效的服务器地址" << std::endl;
close(sock);
return 1;
}
// 3. 连接服务器
if (connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0) {
std::cerr << "连接服务器失败" << std::endl;
close(sock);
return 1;
}
std::cout << "已连接到服务器 " << SERVER_IP << ":" << PORT << std::endl;
// 4. 接收服务器消息的线程
std::thread recv_thread(receiveMessages, sock);
recv_thread.detach();
// 5. 发送消息
std::string message;
while (true) {
std::cout << "请输入消息(输入\"quit\"退出): ";
std::getline(std::cin, message);
if (message == "quit") {
break;
}
send(sock, message.c_str(), message.length(), 0);
}
// 6. 关闭套接字
close(sock);
return 0;
}
五、Boost.Asio库的使用
5.1 Boost.Asio基础
Boost.Asio是一个跨平台的网络编程库,它简化了网络编程的复杂流程,提供了异步、同步、单线程和多线程的编程模型。
5.2 使用Boost.Asio实现TCP服务器
cpp
#include <iostream>
#include <string>
#include <boost/asio.hpp>
using boost::asio::ip::tcp;
using namespace std;
const int PORT = 8080;
void handleClient(tcp::socket socket) {
try {
char buffer[1024];
// 读取客户端消息
size_t len = socket.read_some(boost::asio::buffer(buffer));
string message(buffer, len);
cout << "收到客户端消息: " << message << endl;
// 发送响应
string response = "服务器已收到消息: " + message;
boost::asio::write(socket, boost::asio::buffer(response));
cout << "响应已发送" << endl;
} catch (const std::exception& e) {
cerr << "处理客户端连接时出错: " << e.what() << endl;
}
}
int main() {
cout << "=== Boost.Asio TCP服务器示例 ===" << endl;
try {
boost::asio::io_service io_service;
// 创建acceptor
tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), PORT));
cout << "服务器正在监听端口 " << PORT << "..." << endl;
while (true) {
// 接受连接
tcp::socket socket(io_service);
acceptor.accept(socket);
// 处理连接
thread t(handleClient, move(socket));
t.detach();
}
} catch (const std::exception& e) {
cerr << "服务器出错: " << e.what() << endl;
return 1;
}
return 0;
}
5.3 使用Boost.Asio实现TCP客户端
cpp
#include <iostream>
#include <string>
#include <boost/asio.hpp>
using boost::asio::ip::tcp;
using namespace std;
const int PORT = 8080;
const string SERVER_IP = "127.0.0.1";
void receiveMessages(tcp::socket& socket) {
try {
char buffer[1024];
while (true) {
size_t len = socket.read_some(boost::asio::buffer(buffer));
string response(buffer, len);
cout << "服务器响应: " << response << endl;
}
} catch (const std::exception& e) {
cerr << "读取服务器响应时出错: " << e.what() << endl;
}
}
int main() {
cout << "=== Boost.Asio TCP客户端示例 ===" << endl;
try {
boost::asio::io_service io_service;
// 解析服务器地址
tcp::resolver resolver(io_service);
tcp::resolver::query query(tcp::v4(), SERVER_IP, to_string(PORT));
tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
// 连接服务器
tcp::socket socket(io_service);
boost::asio::connect(socket, endpoint_iterator);
cout << "已连接到服务器 " << SERVER_IP << ":" << PORT << endl;
// 接收服务器消息的线程
thread t(receiveMessages, ref(socket));
t.detach();
// 发送消息
string message;
while (true) {
cout << "请输入消息(输入\"quit\"退出): ";
getline(cin, message);
if (message == "quit") {
break;
}
boost::asio::write(socket, boost::asio::buffer(message));
}
} catch (const std::exception& e) {
cerr << "客户端出错: " << e.what() << endl;
return 1;
}
return 0;
}
六、综合案例:实现一个简单的聊天服务器
6.1 项目结构
ChatServer/
├── include/
│ └── ChatServer.h
├── src/
│ ├── ChatServer.cpp
│ └── main.cpp
└── build/
6.2 核心代码
cpp
// include/ChatServer.h
#ifndef CHATSERVER_H
#define CHATSERVER_H
#include <iostream>
#include <string>
#include <vector>
#include <thread>
#include <mutex>
#include <boost/asio.hpp>
using boost::asio::ip::tcp;
using namespace std;
class ChatServer {
private:
boost::asio::io_service io_service;
tcp::acceptor acceptor;
vector<shared_ptr<tcp::socket>> clients;
mutex clients_mutex;
void acceptConnection();
void handleClient(shared_ptr<tcp::socket> socket);
void broadcastMessage(const string& message, const tcp::endpoint& sender_endpoint);
public:
ChatServer(int port);
void run();
};
#endif // CHATSERVER_H
// src/ChatServer.cpp
#include "ChatServer.h"
ChatServer::ChatServer(int port) : acceptor(io_service, tcp::endpoint(tcp::v4(), port)) {
}
void ChatServer::run() {
cout << "聊天服务器正在运行..." << endl;
acceptConnection();
io_service.run();
}
void ChatServer::acceptConnection() {
shared_ptr<tcp::socket> socket = make_shared<tcp::socket>(io_service);
acceptor.async_accept(*socket, [this, socket](const boost::system::error_code& ec) {
if (!ec) {
cout << "客户端 " << socket->remote_endpoint() << " 已连接" << endl;
{
lock_guard<mutex> lock(clients_mutex);
clients.push_back(socket);
}
handleClient(socket);
} else {
cerr << "接受连接失败: " << ec.message() << endl;
}
acceptConnection();
});
}
void ChatServer::handleClient(shared_ptr<tcp::socket> socket) {
auto endpoint = socket->remote_endpoint();
char buffer[1024];
socket->async_read_some(boost::asio::buffer(buffer), [this, socket, endpoint](const boost::system::error_code& ec, size_t bytes_transferred) {
if (!ec) {
string message(buffer, bytes_transferred);
cout << "收到来自 " << endpoint << " 的消息: " << message << endl;
broadcastMessage(message, endpoint);
handleClient(socket);
} else {
cout << "客户端 " << endpoint << " 已断开连接" << endl;
{
lock_guard<mutex> lock(clients_mutex);
clients.erase(remove(clients.begin(), clients.end(), socket), clients.end());
}
}
});
}
void ChatServer::broadcastMessage(const string& message, const tcp::endpoint& sender_endpoint) {
string full_message = sender_endpoint.address().to_string() + ":" + to_string(sender_endpoint.port()) + " 说: " + message;
{
lock_guard<mutex> lock(clients_mutex);
for (auto socket : clients) {
if (socket->remote_endpoint() != sender_endpoint) {
boost::asio::async_write(*socket, boost::asio::buffer(full_message), [](const boost::system::error_code& ec, size_t /*bytes_transferred*/) {
if (ec) {
cerr << "发送消息失败: " << ec.message() << endl;
}
});
}
}
}
}
// src/main.cpp
#include <iostream>
#include "ChatServer.h"
int main() {
std::cout << "=== 简单的聊天服务器 ===" << std::endl;
try {
const int PORT = 8080;
ChatServer server(PORT);
server.run();
} catch (const std::exception& e) {
std::cerr << "服务器出错: " << e.what() << std::endl;
return 1;
}
return 0;
}
6.3 项目构建与运行
bash
# 创建构建目录
mkdir -p build && cd build
# 配置CMake
cmake -DCMAKE_BUILD_TYPE=Release ..
# 编译项目
cmake --build . --config Release
# 运行服务器
./ChatServer
# 在另一个终端运行客户端
./ChatClient
七、总结与练习
7.1 本章总结
本章介绍了C++网络编程的核心知识,包括:
- 网络编程的基本概念与TCP/IP协议
- 套接字编程基础
- TCP服务器的实现
- 多线程服务器的实现
- TCP客户端的实现
- Boost.Asio库的使用
- 综合案例:实现一个简单的聊天服务器
7.2 练习题
- 写一个程序,使用套接字编程实现一个简单的HTTP服务器。
- 编写一个函数,使用Boost.Asio库实现一个简单的FTP客户端。
- 写一个程序,使用多线程实现一个高并发的网络服务器。
- 实现一个类,使用Boost.Asio库实现一个简单的邮件服务器。
- 写一个程序,使用网络编程实现一个简单的实时数据传输系统。
7.3 进阶挑战
- 研究如何使用C++的无锁数据结构优化网络服务器的性能。
- 学习如何使用C++的协程(C++20及以后)与网络编程结合。
- 研究如何使用C++的并发编程优化一个大型网络应用。
- 学习如何使用C++的加密库(如OpenSSL)实现安全的网络通信。
- 研究如何使用C++的网络编程实现一个高可用性的分布式系统。