一、项目概述
本项目将基于 C++ 标准库及跨平台网络编程接口(Winsock/BSD Socket),实现一个支持 TCP 协议的简易网络通信工具,包含服务端和客户端两部分。服务端支持多客户端连接(基于多线程),可接收客户端消息并广播给所有在线客户端;客户端可连接服务端,实现消息的发送与接收。
通过该项目,你将掌握:
跨平台网络编程的核心概念(Socket、TCP 连接、端口/IP 等);C++ 多线程编程(处理多客户端并发);网络数据的收发与简单解析;跨平台编译适配(Windows/Linux)。
技术栈
编程语言:C++11 及以上网络接口:Winsock2(Windows)/BSD Socket(Linux/macOS)多线程:std::thread(C++11 标准)编译工具:g++(Linux)/MSVC(Windows)
二、核心原理
1. TCP 网络通信模型
TCP 是面向连接、可靠的字节流协议,通信流程遵循“三次握手建立连接→数据传输→四次挥手断开连接”:
服务端:创建 Socket → 绑定端口(bind)→ 监听连接(listen)→ 接受连接(accept)→ 收发数据(recv/send);客户端:创建 Socket → 连接服务端(connect)→ 收发数据(recv/send)。
2. 多线程处理多客户端
单线程服务端只能处理一个客户端,因此本项目通过主线程监听连接,每接收到一个新客户端连接,就创建一个子线程专门处理该客户端的消息收发,实现并发通信。
3. 跨平台适配
Windows 下使用 Winsock2 库,需初始化 WSA 环境;Linux/macOS 下使用 BSD Socket,无需初始化,直接调用接口。通过条件编译()实现代码跨平台兼容。
#ifdef _WIN32
三、项目实现步骤
步骤 1:环境准备
Windows 环境
编译器:Visual Studio 2019/2022 或 MinGW;依赖:Winsock2 库(编译时需链接 )。
ws2_32.lib
Linux/macOS 环境
编译器:g++(需支持 C++11,命令加 );依赖:无(系统自带 BSD Socket)。
-std=c++11
步骤 2:封装跨平台 Socket 工具类
首先封装一个 类,统一跨平台的 Socket 操作,避免重复代码。
SocketUtil
#include <iostream>
#include <string>
#include <cstring>
#include <vector>
#include <thread>
#include <mutex>
#include <atomic>
// 跨平台适配
#ifdef _WIN32
#include <winsock2.h>
#include <ws2tcpip.h>
#pragma comment(lib, "ws2_32.lib") // 链接Winsock库
#define SOCKET_ERROR_CODE SOCKET_ERROR
#define INVALID_SOCKET_FD INVALID_SOCKET
typedef SOCKET SocketFD;
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#define SOCKET_ERROR_CODE -1
#define INVALID_SOCKET_FD -1
typedef int SocketFD;
#endif
// 全局互斥锁,保护客户端列表操作
std::mutex client_mutex;
// 在线客户端列表
std::vector<SocketFD> client_list;
// 服务端运行状态
std::atomic<bool> server_running(false);
// Socket工具类
class SocketUtil {
public:
// 初始化网络环境(仅Windows需要)
static bool init() {
#ifdef _WIN32
WSADATA wsaData;
int ret = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (ret != 0) {
std::cerr << "WSA初始化失败:" << ret << std::endl;
return false;
}
#endif
return true;
}
// 清理网络环境(仅Windows需要)
static void cleanup() {
#ifdef _WIN32
WSACleanup();
#endif
}
// 创建TCP Socket
static SocketFD create_tcp_socket() {
SocketFD fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (fd == INVALID_SOCKET_FD) {
std::cerr << "创建Socket失败:" << get_error() << std::endl;
}
return fd;
}
// 获取错误码
static int get_error() {
#ifdef _WIN32
return WSAGetLastError();
#else
return errno;
#endif
}
// 关闭Socket
static void close_socket(SocketFD fd) {
if (fd == INVALID_SOCKET_FD) return;
#ifdef _WIN32
closesocket(fd);
#else
close(fd);
#endif
}
// 设置Socket地址(IPv4)
static void set_sockaddr(sockaddr_in& addr, const std::string& ip, uint16_t port) {
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port); // 端口转网络字节序
if (ip.empty()) {
addr.sin_addr.s_addr = htonl(INADDR_ANY); // 绑定所有网卡
} else {
inet_pton(AF_INET, ip.c_str(), &addr.sin_addr); // IP转网络字节序
}
}
};
步骤 3:实现服务端核心逻辑
服务端包含以下核心功能:
启动服务,绑定端口并监听;循环接受客户端连接,为每个客户端创建处理线程;接收客户端消息,广播给所有在线客户端;处理客户端断开连接,清理资源。
// 服务端类
class TCPServer {
private:
SocketFD server_fd;
uint16_t port;
// 处理单个客户端的消息(子线程函数)
void handle_client(SocketFD client_fd) {
char buffer[1024] = {0};
std::cout << "客户端[" << client_fd << "] 已连接" << std::endl;
// 将客户端加入列表
{
std::lock_guard<std::mutex> lock(client_mutex);
client_list.push_back(client_fd);
}
// 循环接收客户端消息
while (server_running) {
int ret = recv(client_fd, buffer, sizeof(buffer) - 1, 0);
if (ret <= 0) {
// 客户端断开或出错
std::cerr << "客户端[" << client_fd << "] 断开连接:" << SocketUtil::get_error() << std::endl;
break;
}
// 打印接收到的消息
std::string msg(buffer, ret);
std::cout << "收到客户端[" << client_fd << "]消息:" << msg << std::endl;
// 广播消息给所有客户端
broadcast_message(client_fd, msg);
// 清空缓冲区
memset(buffer, 0, sizeof(buffer));
}
// 清理客户端资源
{
std::lock_guard<std::mutex> lock(client_mutex);
// 从列表中移除客户端
for (auto it = client_list.begin(); it != client_list.end(); ++it) {
if (*it == client_fd) {
client_list.erase(it);
break;
}
}
}
SocketUtil::close_socket(client_fd);
std::cout << "客户端[" << client_fd << "] 资源已清理" << std::endl;
}
// 广播消息给所有客户端(排除发送者)
void broadcast_message(SocketFD sender_fd, const std::string& msg) {
std::lock_guard<std::mutex> lock(client_mutex);
for (SocketFD fd : client_list) {
if (fd == sender_fd) continue; // 不回发给自己
send(fd, msg.c_str(), msg.size(), 0);
}
}
public:
TCPServer(uint16_t port) : port(port), server_fd(INVALID_SOCKET_FD) {}
// 启动服务端
bool start() {
// 创建服务端Socket
server_fd = SocketUtil::create_tcp_socket();
if (server_fd == INVALID_SOCKET_FD) return false;
// 设置服务端地址
sockaddr_in server_addr;
SocketUtil::set_sockaddr(server_addr, "", port);
// 绑定端口
if (bind(server_fd, (sockaddr*)&server_addr, sizeof(server_addr)) == SOCKET_ERROR_CODE) {
std::cerr << "绑定端口失败:" << SocketUtil::get_error() << std::endl;
SocketUtil::close_socket(server_fd);
return false;
}
// 监听连接(最大等待队列10)
if (listen(server_fd, 10) == SOCKET_ERROR_CODE) {
std::cerr << "监听失败:" << SocketUtil::get_error() << std::endl;
SocketUtil::close_socket(server_fd);
return false;
}
server_running = true;
std::cout << "服务端启动成功,监听端口:" << port << std::endl;
// 循环接受客户端连接
while (server_running) {
sockaddr_in client_addr;
socklen_t client_addr_len = sizeof(client_addr);
SocketFD client_fd = accept(server_fd, (sockaddr*)&client_addr, &client_addr_len);
if (client_fd == INVALID_SOCKET_FD) {
if (server_running) {
std::cerr << "接受连接失败:" << SocketUtil::get_error() << std::endl;
}
continue;
}
// 创建线程处理客户端
std::thread(&TCPServer::handle_client, this, client_fd).detach();
}
return true;
}
// 停止服务端
void stop() {
server_running = false;
SocketUtil::close_socket(server_fd);
std::cout << "服务端已停止" << std::endl;
// 关闭所有客户端连接
std::lock_guard<std::mutex> lock(client_mutex);
for (SocketFD fd : client_list) {
SocketUtil::close_socket(fd);
}
client_list.clear();
}
~TCPServer() {
stop();
}
};
步骤 4:实现客户端核心逻辑
客户端包含以下核心功能:
连接指定 IP 和端口的服务端;创建线程接收服务端消息;从控制台输入消息,发送给服务端;处理断开连接逻辑。
// 客户端类
class TCPClient {
private:
SocketFD client_fd;
std::atomic<bool> client_running;
// 接收服务端消息(子线程函数)
void recv_message() {
char buffer[1024] = {0};
while (client_running) {
int ret = recv(client_fd, buffer, sizeof(buffer) - 1, 0);
if (ret <= 0) {
std::cerr << "与服务端断开连接:" << SocketUtil::get_error() << std::endl;
client_running = false;
break;
}
std::string msg(buffer, ret);
std::cout << "
收到服务端广播:" << msg << std::endl;
std::cout << "请输入消息:";
std::cout.flush(); // 刷新输出缓冲区
memset(buffer, 0, sizeof(buffer));
}
}
public:
TCPClient() : client_fd(INVALID_SOCKET_FD), client_running(false) {}
// 连接服务端
bool connect_server(const std::string& ip, uint16_t port) {
// 创建客户端Socket
client_fd = SocketUtil::create_tcp_socket();
if (client_fd == INVALID_SOCKET_FD) return false;
// 设置服务端地址
sockaddr_in server_addr;
SocketUtil::set_sockaddr(server_addr, ip, port);
// 连接服务端
if (connect(client_fd, (sockaddr*)&server_addr, sizeof(server_addr)) == SOCKET_ERROR_CODE) {
std::cerr << "连接服务端失败:" << SocketUtil::get_error() << std::endl;
SocketUtil::close_socket(client_fd);
client_fd = INVALID_SOCKET_FD;
return false;
}
client_running = true;
std::cout << "成功连接服务端 [" << ip << ":" << port << "]" << std::endl;
// 创建线程接收消息
std::thread(&TCPClient::recv_message, this).detach();
// 循环发送消息
std::string msg;
while (client_running) {
std::cout << "请输入消息(输入exit退出):";
std::getline(std::cin, msg);
if (msg == "exit") {
stop();
break;
}
if (msg.empty()) continue;
// 发送消息
int ret = send(client_fd, msg.c_str(), msg.size(), 0);
if (ret == SOCKET_ERROR_CODE) {
std::cerr << "发送消息失败:" << SocketUtil::get_error() << std::endl;
stop();
break;
}
}
return true;
}
// 停止客户端
void stop() {
client_running = false;
SocketUtil::close_socket(client_fd);
std::cout << "客户端已断开连接" << std::endl;
}
~TCPClient() {
stop();
}
};
步骤 5:实现主函数(入口)
主函数通过命令行参数区分服务端/客户端模式,解析参数后启动对应逻辑。
// 打印使用帮助
void print_help(const std::string& prog_name) {
std::cout << "简易网络通信工具使用说明:" << std::endl;
std::cout << "服务端模式:" << prog_name << " -s <端口>" << std::endl;
std::cout << "客户端模式:" << prog_name << " -c <服务端IP> <端口>" << std::endl;
std::cout << "示例:" << std::endl;
std::cout << " 启动服务端:" << prog_name << " -s 8888" << std::endl;
std::cout << " 启动客户端:" << prog_name << " -c 127.0.0.1 8888" << std::endl;
}
int main(int argc, char* argv[]) {
// 初始化网络环境
if (!SocketUtil::init()) {
return -1;
}
// 解析命令行参数
if (argc < 3) {
print_help(argv[0]);
SocketUtil::cleanup();
return -1;
}
std::string mode = argv[1];
if (mode == "-s") {
// 服务端模式
uint16_t port = atoi(argv[2]);
if (port == 0 || port > 65535) {
std::cerr << "无效的端口号:" << argv[2] << std::endl;
SocketUtil::cleanup();
return -1;
}
TCPServer server(port);
if (!server.start()) {
SocketUtil::cleanup();
return -1;
}
} else if (mode == "-c") {
// 客户端模式
if (argc < 4) {
print_help(argv[0]);
SocketUtil::cleanup();
return -1;
}
std::string ip = argv[2];
uint16_t port = atoi(argv[3]);
if (port == 0 || port > 65535) {
std::cerr << "无效的端口号:" << argv[3] << std::endl;
SocketUtil::cleanup();
return -1;
}
TCPClient client;
if (!client.connect_server(ip, port)) {
SocketUtil::cleanup();
return -1;
}
} else {
print_help(argv[0]);
SocketUtil::cleanup();
return -1;
}
// 清理网络环境
SocketUtil::cleanup();
return 0;
}
四、编译与运行
1. Linux/macOS 编译
使用 g++ 编译,需指定 C++11 标准:
g++ -std=c++11 -o net_tool net_tool.cpp -pthread
2. Windows 编译(MinGW)
g++ -std=c++11 -o net_tool.exe net_tool.cpp -lws2_32
3. Windows 编译(Visual Studio)
创建空项目,添加代码文件;项目属性 → 链接器 → 输入 → 附加依赖项,添加 ;编译生成可执行文件。
ws2_32.lib
4. 运行测试
启动服务端
# Linux/macOS
./net_tool -s 8888
# Windows
net_tool.exe -s 8888
输出:
服务端启动成功,监听端口:8888
启动客户端(多个)
# Linux/macOS
./net_tool -c 127.0.0.1 8888
# Windows
net_tool.exe -c 127.0.0.1 8888
输出:
成功连接服务端 [127.0.0.1:8888]
测试消息收发
在任意客户端输入消息,服务端会接收并广播给所有客户端,例如:
客户端1输入:服务端输出:
Hello World!客户端2/3 输出:
收到客户端[5]消息:Hello World!
收到服务端广播:Hello World!
输入 可退出客户端,服务端会清理该客户端连接。
exit
五、功能扩展建议
本项目实现了基础的 TCP 通信功能,可进一步扩展:
消息格式优化:添加消息头(如发送者ID、时间戳、消息长度),支持结构化消息;断线重连:客户端检测到断开后,自动重试连接服务端;UDP 协议支持:补充 UDP 通信逻辑,实现无连接的消息传输;日志系统:将消息记录到文件,便于调试和审计;权限验证:客户端连接时需输入密码,服务端验证通过后才允许通信;图形界面:使用 Qt/GTK 封装界面,替代控制台输入输出;大文件传输:支持分片传输大文件,实现断点续传。
六、常见问题与解决
1. Windows 编译提示
ws2_32.lib 找不到
ws2_32.lib
确认项目属性中已添加 ;MinGW 编译时需加
ws2_32.lib 参数。
-lws2_32
2. 客户端连接服务端失败
检查服务端是否已启动,端口是否正确;检查防火墙是否放行对应端口;服务端绑定 (INADDR_ANY),客户端需使用服务端的实际 IP(非 127.0.0.1 需确保网络互通)。
0.0.0.0
3. 多客户端连接后消息广播异常
确认客户端列表操作已加互斥锁(避免线程安全问题);检查 Socket 关闭逻辑,确保断开的客户端从列表中移除。
4. 中文乱码
统一编码格式(如 UTF-8),Windows 控制台需设置编码:。
chcp 65001
七、总结
本项目从零实现了一个跨平台的简易网络通信工具,核心围绕 TCP Socket 编程和多线程并发处理展开。通过封装跨平台工具类,解决了 Windows/Linux 网络接口差异的问题;通过多线程和互斥锁,实现了服务端对多客户端的并发处理;通过广播机制,实现了客户端之间的消息互通。
该项目覆盖了 C++ 网络编程的核心知识点,代码结构清晰、易于扩展,是入门网络编程的优质实战案例。掌握本项目后,可进一步学习更复杂的网络框架(如 Boost.Asio、libevent),实现高性能的网络应用。


