本文将基于一个完整的项目代码,系统讲解如何使用 C++ 实现一个 TCP 客户端与服务器,并重点说明实现过程中所涉及的核心知识点
一、项目整体结构
本项目采用典型的 客户端 - 服务器模型:
-
客户端:负责连接服务器、发送请求、接收响应
-
服务器:负责监听连接、处理请求、返回结果
核心模块包括:
InetAddr ------ 网络地址封装
TcpServer ------ TCP服务器框架
Command ------ 业务处理逻辑
Client ------ 客户端程序
二、TCP 编程基础知识
在实现之前,需要掌握以下基础知识:
1. Socket 编程模型
TCP 通信基于 socket,基本流程如下:
服务端流程
socket → bind → listen → accept → read/write
客户端流程
socket → connect → read/write
2. 网络字节序
网络传输统一使用 大端字节序,因此需要:
-
htons:主机序 → 网络序(端口) -
ntohs:网络序 → 主机序 -
inet_pton:字符串 IP → 二进制 -
inet_ntop:二进制 → 字符串 IP
3. sockaddr 结构
TCP 使用:
struct sockaddr_in
但接口统一使用:
struct sockaddr*
因此需要进行类型转换。
三、InetAddr:网络地址封装
为了避免重复操作底层结构,本项目封装了 InetAddr 类。
1. 功能
-
封装 IP + Port
-
提供网络序与主机序转换
-
提供接口给 socket 使用
2. 构造方式
(1)从网络结构构造(服务端 accept)
InetAddr(const sockaddr_in &addr)
作用:
-
提取 IP
-
转换端口为主机序
(2)客户端构造
InetAddr(const std::string& ip, uint16_t port)
作用:
- 构造用于 connect 的地址结构
(3)服务端构造
InetAddr(uint16_t port)
特点:
-
IP 设置为
INADDR_ANY -
监听所有网卡
3. 核心接口
const struct sockaddr* NetAddrPtr();
const size_t NetAddrLen();
👉 用于 socket API 调用
四、客户端实现
客户端逻辑较为简单,核心代码如下:
1. 创建 socket
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
SOCK_STREAM表示 TCP
2. 连接服务器
connect(sockfd, cilent.NetAddrPtr(), cilent.NetAddrLen());
3. 数据通信
write(sockfd, line.c_str(), line.size());
read(sockfd, buffer, sizeof(buffer));
流程:
-
从标准输入读取数据
-
发送给服务器
-
接收服务器返回结果
-
输出到终端
4. 关闭连接
close(sockfd);
五、TcpServer:服务器实现
服务器是整个项目的核心。
1. 成员变量
uint16_t _port;
int _listenSockfd;
bool _isrunning;
func_t _func;
其中:
using func_t = std::function<std::string(const std::string&, InetAddr&)>;
👉 表示业务处理函数
2. 初始化(Init)
(1)创建 socket
_listenSockfd = socket(AF_INET, SOCK_STREAM, 0);
(2)绑定端口
InetAddr local(_port);
bind(_listenSockfd, local.NetAddrPtr(), local.NetAddrLen());
(3)监听
listen(_listenSockfd, backlog);
3. 获取连接(accept)
int sockfd = accept(_listenSockfd, CONV(peer), &len);
特点:
-
阻塞等待客户端连接
-
返回新的通信 socket
4. 请求处理(Service)
ssize_t n = read(sockfd, buffer, sizeof(buffer));
处理逻辑:
-
读取客户端数据
-
调用业务函数
_func -
将结果返回给客户端
std::string result = _func(buffer, cilent);
write(sockfd, result.c_str(), result.size());
5. 并发处理(多线程)
为了支持多个客户端同时访问,使用 pthread:
线程数据封装
class ThreadData
{
TcpServer* _tsvr;
int _sockfd;
InetAddr _addr;
};
线程入口函数
static void* Routine(void* args)
流程:
-
分离线程
pthread_detach -
执行
Service -
释放资源
创建线程
pthread_create(&tid, nullptr, Routine, td);
👉 每个客户端一个线程
六、业务解耦(回调机制)
服务器不直接处理业务逻辑,而是通过回调函数:
std::function<std::string(const std::string&, InetAddr&)>
在 main 中绑定:
std::bind(&Command::Execute, &cmd, std::placeholders::_1, std::placeholders::_2)
优点
-
服务器只负责通信
-
业务逻辑独立
-
可扩展性强
七、Command 模块(业务层)
该模块负责处理客户端请求:
std::string Execute(const std::string& req, InetAddr& client);
功能可以是:
-
字符串处理
-
命令执行
-
数据查询
八、程序运行流程总结
服务端
启动 → 初始化socket → 监听端口 → accept连接 → 创建线程 → 处理请求
客户端
启动 → 创建socket → connect服务器 → 输入数据 → 接收响应
九、涉及的核心知识总结
实现本项目需要掌握以下知识:
1. C++ 基础
-
类与对象
-
构造函数
-
STL(string、function)
-
智能指针(unique_ptr)
2. Linux 系统编程
-
socket API
-
文件描述符
-
read / write
-
close
3. 网络编程
-
TCP 协议
-
三次握手(建立连接)
-
四次挥手(断开连接)
-
网络字节序
4. 多线程
-
pthread
-
线程创建与分离
-
并发处理模型
5. 设计思想
-
封装(InetAddr)
-
解耦(回调函数)
-
模块化(TcpServer / Command)
通过这个项目,可以完整理解 TCP 编程从底层 API 到结构设计的全过程。相比只调用接口,这种方式更有助于深入理解网络通信机制,以及服务器程序的基本构建方式
这个实现不仅具备基本通信能力,同时具备良好的扩展性,是进一步学习高性能网络编程的重要基础,接下来我们将学习自定义协议,敬请期待啦