一、Socket.hpp:模板方法模式 + TCP 实现
1.1 文件存在的意义
Socket 操作有固定流程:创建 → 绑定 → 监听 → 接受连接。 但 TCP 和 UDP 的具体实现不同。模板方法模式把"流程"固化在基类,把"变化"留给子类。
1.2 抽象基类 Socket
class Socket {
// 纯虚函数:子类必须实现
virtual void SocketOrDie() = 0; // 创建套接字
virtual void BindOrDie(uint16_t port) = 0; // 绑定端口
virtual void ListenOrDie(int backlog) = 0; // 开始监听
virtual std::shared_ptr<Socket> Accept(InetAddr *client) = 0; // 接受连接
virtual void Close() = 0; // 关闭套接字
virtual int Recv(std::string *out) = 0; // 接收数据
virtual int Send(const std::string &message) = 0; // 发送数据
virtual int Connect(const std::string &server_ip, uint16_t port) = 0; // 连接服务器
// 模板方法:固化流程
void BuildTcpSocketMethod(uint16_t port, int backlog = gbacklog) {
SocketOrDie(); // 第1步
BindOrDie(port); // 第2步
ListenOrDie(backlog); // 第3步
}
void BuildTcpClientSocketMethod() {
SocketOrDie(); // 客户端只需要创建
}
};
模板方法的好处 :
-
服务器端不可能"先 Listen 再 Bind",流程错误在编译期就避免
-
新增 UDP 时,只需继承
Socket,实现 7 个纯虚函数,流程自动正确
1.3 TcpSocket 实现详解
class TcpSocket : public Socket {
int _sockfd; // 核心资源:文件描述符
public:
TcpSocket() : _sockfd(defaultfd) {} // 默认构造(监听套接字)
TcpSocket(int fd) : _sockfd(fd) {} // 从已有 fd 构造(连接套接字)
为什么需要两个构造函数?
-
服务器:
TcpSocket()→ 创建新的监听 fd -
Accept()后:TcpSocket(fd)→ 包装系统返回的连接 fdvoid SocketOrDie() override {
_sockfd = ::socket(AF_INET, SOCK_STREAM, 0); // :: 表示系统调用
if (_sockfd < 0) {
LOG(LogLevel::FATAL) << "socket error";
exit(SOCKET_ERR);
}
}
注意 ::socket:加 :: 明确调用全局命名空间的系统函数,避免与类成员函数混淆。
std::shared_ptr<Socket> Accept(InetAddr *client) override {
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int fd = ::accept(_sockfd, CONV(peer), &len);
if (fd < 0) {
LOG(LogLevel::WARNING) << "accept warning ...";
return nullptr;
}
client->SetAddr(peer); // 填充客户端地址信息
return std::make_shared<TcpSocket>(fd); // 返回新的连接套接字
}
-
多态:上层代码只认识
Socket接口,不知道具体是 TCP 还是 UDP -
自动内存管理:不用手动
deleteint Recv(std::string *out) override {
char buffer[1024];
ssize_t n = ::recv(_sockfd, buffer, sizeof(buffer) - 1, 0);
if (n > 0) {
buffer[n] = 0;
*out += buffer; // 注意是 += 不是 = !
}
return n;
}
+= 是核心设计!
-
TCP 是流式协议,一次
recv可能读不完一个完整请求 -
buffer_queue += buffer 把新读到的数据追加到累积缓冲区
-
后续
Protocol::Decode()从累积缓冲区中提取完整报文int Send(const std::string &message) override {
return send(_sockfd, message.c_str(), message.size(), 0);
}
为什么 Send 不用 +=?
-
发送方知道自己要发什么,直接发完整报文即可
-
Encode()已经把长度+JSON 拼好了,不需要累积#pragma once
#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstdlib>
#include "Common.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"namespace SocketModule
{
using namespace LogModule;
const static int gbacklog = 16;
// 模版方法模式
// 基类socket, 大部分方法,都是纯虚方法
class Socket
{
public:
virtual ~Socket() {}
virtual void SocketOrDie() = 0;
virtual void BindOrDie(uint16_t port) = 0;
virtual void ListenOrDie(int backlog) = 0;
virtual std::shared_ptr<Socket> Accept(InetAddr *client) = 0;
virtual void Close() = 0;
virtual int Recv(std::string *out) = 0;
virtual int Send(const std::string &message) = 0;
virtual int Connect(const std::string &server_ip, uint16_t port) = 0;public: void BuildTcpSocketMethod(uint16_t port, int backlog = gbacklog) { SocketOrDie(); BindOrDie(port); ListenOrDie(backlog); } void BuildTcpClientSocketMethod() { SocketOrDie(); } // void BuildUdpSocketMethod() // { // SocketOrDie(); // BindOrDie(); // } }; const static int defaultfd = -1; class TcpSocket : public Socket { public: TcpSocket() : _sockfd(defaultfd) { } TcpSocket(int fd) : _sockfd(fd) { } ~TcpSocket() {} void SocketOrDie() override { _sockfd = ::socket(AF_INET, SOCK_STREAM, 0); if (_sockfd < 0) { LOG(LogLevel::FATAL) << "socket error"; exit(SOCKET_ERR); } LOG(LogLevel::INFO) << "socket success"; } void BindOrDie(uint16_t port) override { InetAddr localaddr(port); int n = ::bind(_sockfd, localaddr.NetAddrPtr(), localaddr.NetAddrLen()); if (n < 0) { LOG(LogLevel::FATAL) << "bind error"; exit(BIND_ERR); } LOG(LogLevel::INFO) << "bind success"; } void ListenOrDie(int backlog) override { int n = ::listen(_sockfd, backlog); if (n < 0) { LOG(LogLevel::FATAL) << "listen error"; exit(LISTEN_ERR); } LOG(LogLevel::INFO) << "listen success"; } std::shared_ptr<Socket> Accept(InetAddr *client) override { struct sockaddr_in peer; socklen_t len = sizeof(peer); int fd = ::accept(_sockfd, CONV(peer), &len); if (fd < 0) { LOG(LogLevel::WARNING) << "accept warning ..."; return nullptr; // TODO } client->SetAddr(peer); return std::make_shared<TcpSocket>(fd); } // n == read的返回值 int Recv(std::string *out) override { // 流式读取,不关心读到的是什么 char buffer[1024]; ssize_t n = ::recv(_sockfd, buffer, sizeof(buffer) - 1, 0); if (n > 0) { buffer[n] = 0; *out += buffer; // 故意 } return n; } int Send(const std::string &message) override { return send(_sockfd, message.c_str(), message.size(), 0); } void Close() override //?? { if (_sockfd >= 0) ::close(_sockfd); } int Connect(const std::string &server_ip, uint16_t port) override { InetAddr server(server_ip, port); return ::connect(_sockfd, server.NetAddrPtr(), server.NetAddrLen()); } private: int _sockfd; // _sockfd , listensockfd, sockfd; }; // class UdpSocket : public Socket // { // };}
二、Protocol.hpp:项目的灵魂
2.1 文件存在的意义
这个文件解决了网络编程的两大核心问题:
-
结构化数据 ↔ 字符串:序列化/反序列化
-
字节流 ↔ 消息:自定义协议解决粘包
2.2 Request 类:客户端 → 服务器
class Request {
int _x;
int _y;
char _oper;
public:
Request(int x, int y, char oper) : _x(x), _y(y), _oper(oper) {}
std::string Serialize() {
Json::Value root;
root["x"] = _x;
root["y"] = _y;
root["oper"] = _oper; // char 自动转为 ASCII 码整数
Json::FastWriter writer;
return writer.write(root); // 返回如 {"x":10,"y":20,"oper":43}
}
bool Deserialize(std::string &in) {
Json::Value root;
Json::Reader reader;
bool ok = reader.parse(in, root);
if (ok) {
_x = root["x"].asInt();
_y = root["y"].asInt();
_oper = root["oper"].asInt(); // 转回 char
}
return ok;
}
};
为什么 _oper 存的是 ASCII 码?
-
JSON 没有
char类型,'+'的 ASCII 是43,序列化后变成{"oper":43} -
反序列化时用
asInt()取出43,使用时直接当char用(C++ 会自动转换)
2.3 Response 类:服务器 → 客户端
class Response {
int _result;
int _code; // 0=成功, 1=除零, 2=模零, 3=非法操作
public:
Response(int result, int code) : _result(result), _code(code) {}
std::string Serialize() {
Json::Value root;
root["result"] = _result;
root["code"] = _code;
Json::FastWriter writer;
return writer.write(root);
}
bool Deserialize(std::string &in) { ... }
void ShowResult() {
std::cout << "计算结果是: " << _result << "[" << _code << "]" << std::endl;
}
};
_code 的设计是点睛之笔!
-
如果没有
code,result=0时客户端不知道是真的结果为 0,还是出错了 -
code让业务错误 与计算结果彻底分离
2.4 Encode / Decode:自定义协议核心
const std::string sep = "\r\n";
std::string Encode(const std::string &jsonstr) {
std::string len = std::to_string(jsonstr.size());
return len + sep + jsonstr + sep;
}
协议格式可视化 :

为什么用 \r\n 而不用 \n?
bool Decode(std::string &buffer, std::string *package) {
// 步骤1:找第一个 \r\n,确定"长度字段"的边界
ssize_t pos = buffer.find(sep);
if (pos == std::string::npos) return false; // 数据不够,继续读
// 步骤2:提取长度值
std::string package_len_str = buffer.substr(0, pos);
int package_len_int = std::stoi(package_len_str);
// 步骤3:计算"一个完整报文"需要的总长度
// 总长度 = 长度字段自身长度 + \r\n(2字节) + JSON内容长度 + \r\n(2字节)
int target_len = package_len_str.size() + package_len_int + 2 * sep.size();
// 步骤4:判断 buffer 中是否有足够数据
if (buffer.size() < target_len) return false; // 数据不够,继续读
// 步骤5:提取完整 JSON 内容
*package = buffer.substr(pos + sep.size(), package_len_int);
// 步骤6:从 buffer 中移除已处理的报文(关键!)
buffer.erase(0, target_len);
return true;
}
buffer 为什么用引用 &?
Recv()是流式读取,可能一次读到多个报文,也可能只读到半个报文
buffer作为累积缓冲区,保留未处理完的残留数据下次
Recv()时,新数据追加到buffer尾部,继续尝试Decode
循环解析的妙处 :
while (Decode(buffer_queue, &json_package)) {
// 处理第一个完整请求
// 处理第二个完整请求(如果粘包了)
// ...
}
2.5 GetRequest:服务器的"主循环"
void GetRequest(std::shared_ptr<Socket> &sock, InetAddr &client) {
std::string buffer_queue; // 累积缓冲区
while (true) {
int n = sock->Recv(&buffer_queue); // 追加读取
if (n > 0) {
std::string json_package;
// 循环提取所有完整报文
while (Decode(buffer_queue, &json_package)) {
LOG(LogLevel::DEBUG) << client.StringAddr() << " 请求: " << json_package;
// 1. 反序列化
Request req;
bool ok = req.Deserialize(json_package);
if (!ok) continue;
// 2. 业务处理(通过回调函数 _func)
Response resp = _func(req);
// 3. 序列化响应
std::string json_str = resp.Serialize();
// 4. 编码(加长度报头)
std::string send_str = Encode(json_str);
// 5. 发送
sock->Send(send_str);
}
}
else if (n == 0) {
LOG(LogLevel::INFO) << "client:" << client.StringAddr() << "Quit!";
break; // 客户端断开
}
else {
LOG(LogLevel::WARNING) << "client:" << client.StringAddr() << ", recv error";
break; // 出错
}
}
}

2.6 GetResponse:客户端的接收
bool GetResponse(std::shared_ptr<Socket> &client, std::string &resp_buff, Response *resp) {
while (true) {
int n = client->Recv(&resp_buff);
if (n > 0) {
std::string json_package;
while (Decode(resp_buff, &json_package)) {
resp->Deserialize(json_package); // 填充 resp 对象
}
return true;
}
else if (n == 0) {
std::cout << "server quit " << std::endl;
return false;
}
else {
std::cout << "recv error" << std::endl;
return false;
}
}
}
与 GetRequest 的区别 :
-
GetRequest是服务器端 ,需要循环处理多个请求(长连接) -
GetResponse是客户端 ,一次请求对应一次响应,返回bool表示成功/失败
2.7 BuildRequestString:客户端的"打包工厂"
std::string BuildRequestString(int x, int y, char oper) {
Request req(x, y, oper); // 1. 构造请求对象
std::string json_req = req.Serialize(); // 2. 序列化为 JSON
return Encode(json_req); // 3. 加长度报头,返回可直接发送的字符串
}
存在价值:把"构造→序列化→编码"三步封装成一步,客户端代码更简洁。
#pragma once
#include "Socket.hpp"
#include <iostream>
#include <string>
#include <memory>
#include <jsoncpp/json/json.h>
#include <functional>
// 实现一个自定义的网络版本的计算器
using namespace SocketModule;
// 约定好各个字段的含义,本质就是约定好协议!
// client -> server
// 如何要做序列化和反序列化:
// 1. 我们自己写(怎么做) ---> 往往不具备很好的扩展性
// 2. 使用现成的方案(这个是我们要写的) ---> json -> jsoncpp
// content_len jsonstring
// 50\r\n协议号\r\n{"x": 10, "y" : 20, "oper" : '+'}\r\n
// 50
// {"x": 10, "y" : 20, "oper" : '+'}
class Request
{
public:
Request()
{
}
Request(int x, int y, char oper) : _x(x), _y(y), _oper(oper)
{
}
std::string Serialize()
{
// _x = 10 _y = 20, _oper = '+'
// "10" "20" '+' : 用空格作为分隔符
Json::Value root;
root["x"] = _x;
root["y"] = _y;
root["oper"] = _oper; // _oper是char,也是整数,阿斯克码值
Json::FastWriter writer;
std::string s = writer.write(root);
return s;
}
// {"x": 10, "y" : 20, "oper" : '+'}
bool Deserialize(std::string &in)
{
// "10" "20" '+' -> 以空格作为分隔符 -> 10 20 '+'
Json::Value root;
Json::Reader reader;
bool ok = reader.parse(in, root);
if (ok)
{
_x = root["x"].asInt();
_y = root["y"].asInt();
_oper = root["oper"].asInt(); //?
}
return ok;
}
~Request() {}
int X() { return _x; }
int Y() { return _y; }
char Oper() { return _oper; }
private:
int _x;
int _y;
char _oper; // + - * / % -> _x _oper _y -> 10 + 20
};
// server -> client
class Response
{
public:
Response() {}
Response(int result, int code) : _result(result), _code(code)
{
}
std::string Serialize()
{
Json::Value root;
root["result"] = _result;
root["code"] = _code;
Json::FastWriter writer;
return writer.write(root);
}
bool Deserialize(std::string &in)
{
Json::Value root;
Json::Reader reader;
bool ok = reader.parse(in, root);
if (ok)
{
_result = root["result"].asInt();
_code = root["code"].asInt();
}
return ok;
}
~Response() {}
void SetResult(int res)
{
_result = res;
}
void SetCode(int code)
{
_code = code;
}
void ShowResult()
{
std::cout << "计算结果是: " << _result << "[" << _code << "]" << std::endl;
}
private:
int _result; // 运算结果,无法区分清楚应答是计算结果,还是异常值
int _code; // 0:sucess, 1,2,3,4->不同的运算异常的情况, 约定!!!!
};
const std::string sep = "\r\n";
using func_t = std::function<Response(Request &req)>;
// 协议(基于TCP的)需要解决两个问题:
// 1. request和response必须得有序列化和反序列化功能
// 2. 你必须保证,读取的时候,读到完整的请求(TCP, UDP不用考虑)
class Protocol
{
public:
Protocol()
{
}
Protocol(func_t func) : _func(func)
{
}
std::string Encode(const std::string &jsonstr)
{
// 50\r\n{"x": 10, "y" : 20, "oper" : '+'}\r\n
std::string len = std::to_string(jsonstr.size());
return len + sep + jsonstr + sep; // 应用层封装报头
}
// 50\r\n{"x": 10, "y" : 20, "oper" : '+'}\r\n
// 5
// 50
// 50\r
// 50\r\n
// 50\r\n{"x": 10, "
// 50\r\n{"x": 10, "y" : 20, "oper" : '+'}\r\n
// 50\r\n{"x": 10, "y" : 20, "oper" : '+'}\r\n50\r\n{"x": 10, "y" : 20, "ope
//.....
// packge故意是&
// 1. 判断报文完整性
// 2. 如果包含至少一个完整请求,提取他, 并从移除它,方便处理下一个
bool Decode(std::string &buffer, std::string *package)
{
ssize_t pos = buffer.find(sep);
if (pos == std::string::npos)
return false; // 让调用方继续从内核中读取数据
std::string package_len_str = buffer.substr(0, pos);
int package_len_int = std::stoi(package_len_str);
// buffer 一定有长度,但是一定有完整的报文吗?
int target_len = package_len_str.size() + package_len_int + 2 * sep.size();
if (buffer.size() < target_len)
return false;
// buffer一定至少有一个完整的报文!
*package = buffer.substr(pos + sep.size(), package_len_int);
buffer.erase(0, target_len);
return true;
}
void GetRequest(std::shared_ptr<Socket> &sock, InetAddr &client)
{
// 读取
std::string buffer_queue;
while (true)
{
int n = sock->Recv(&buffer_queue);
if (n > 0)
{
std::cout << "-----------request_buffer--------------" << std::endl;
std::cout << buffer_queue << std::endl;
std::cout << "------------------------------------" << std::endl;
std::string json_package;
// 1. 解析报文,提取完整的json请求,如果不完整,就让服务器继续读取
while (Decode(buffer_queue, &json_package))
{
// 我敢100%保证,我一定拿到了一个完整的报文
// {"x": 10, "y" : 20, "oper" : '+'} -> 你能处理吗?
// 2. 请求json串,反序列化
// std::cout << "-----------request_json--------------" << std::endl;
// std::cout << json_package << std::endl;
// std::cout << "------------------------------------" << std::endl;
// std::cout << "-----------request_buffer--------------" << std::endl;
// std::cout << buffer_queue << std::endl;
// std::cout << "------------------------------------" << std::endl;
LOG(LogLevel::DEBUG) << client.StringAddr() << " 请求: " << json_package;
Request req;
bool ok = req.Deserialize(json_package);
if (!ok)
continue;
// 3. 我一定得到了一个内部属性已经被设置了的req了.
// 通过req->resp, 不就是要完成计算功能嘛!!业务
Response resp = _func(req);
// 4. 序列化
std::string json_str = resp.Serialize();
// 5. 添加自定义长度
std::string send_str = Encode(json_str); // 携带长度的应答报文了"len\r\n{result:XXX, code:XX}\r\n"
// 6. 直接发送
sock->Send(send_str);
}
}
else if (n == 0)
{
LOG(LogLevel::INFO) << "client:" << client.StringAddr() << "Quit!";
break;
}
else
{
LOG(LogLevel::WARNING) << "client:" << client.StringAddr() << ", recv error";
break;
}
}
}
bool GetResponse(std::shared_ptr<Socket> &client, std::string &resp_buff, Response *resp)
{
// 面向字节流,你怎么保证,你的client读到的 一个网络字符串,就一定是一个完整的请求呢??
while (true)
{
int n = client->Recv(&resp_buff);
if (n > 0)
{
// std::cout << "-----------resp_buffer--------------" << std::endl;
// std::cout << resp_buff << std::endl;
// std::cout << "------------------------------------" << std::endl;
// 成功
std::string json_package;
// 1. 解析报文,提取完整的json请求,如果不完整,就让服务器继续读取
// bool ret = Decode(resp_buff, &json_package);
// if (!ret)
// continue;
while (Decode(resp_buff, &json_package))
{
// std::cout << "----------response json---------------" << std::endl;
// std::cout << json_package << std::endl;
// std::cout << "--------------------------------------" << std::endl;
// std::cout << "-----------resp_buffer--------------" << std::endl;
// std::cout << resp_buff << std::endl;
// std::cout << "------------------------------------" << std::endl;
// 2. 走到这里,我能保证,我一定拿到了一个完整的应答json报文
// 2. 反序列化
resp->Deserialize(json_package);
}
return true;
}
else if (n == 0)
{
std::cout << "server quit " << std::endl;
return false;
}
else
{
std::cout << "recv error" << std::endl;
return false;
}
}
}
std::string BuildRequestString(int x, int y, char oper)
{
// 1. 构建一个完整的请求
Request req(x, y, oper);
// 2. 序列化
std::string json_req = req.Serialize();
// // 2.1 debug
// std::cout << "------------json_req string------------" << std::endl;
// std::cout << json_req << std::endl;
// std::cout << "---------------------------------------" << std::endl;
// 3. 添加长度报头
return Encode(json_req);
}
~Protocol()
{
}
private:
// 因为我们用的是多进程
// Request _req;
// Response _resp;
func_t _func;
};
三、Cal.hpp:纯粹的业务层
3.1 文件存在的意义
这是唯一不需要关心网络的文件!只负责:
-
拿到
Request(已经反序列化好的结构体) -
计算结果
-
返回
Response
3.2 Execute 函数详解
Response Execute(Request &req) {
Response resp(0, 0); // result=0, code=0(成功)
switch (req.Oper()) {
case '+': resp.SetResult(req.X() + req.Y()); break;
case '-': resp.SetResult(req.X() - req.Y()); break;
case '*': resp.SetResult(req.X() * req.Y()); break;
case '/':
if (req.Y() == 0) resp.SetCode(1); // 除零错误
else resp.SetResult(req.X() / req.Y());
break;
case '%':
if (req.Y() == 0) resp.SetCode(2); // 模零错误
else resp.SetResult(req.X() % req.Y());
break;
default: resp.SetCode(3); // 非法操作符
}
return resp;
}
设计亮点:
-
零耦合:不知道 JSON、不知道 Socket、不知道 TCP
-
错误码体系 :
code=1/2/3让客户端能给出精确错误提示 -
异常安全:除零不会崩溃,而是返回错误码
#pragma once
#include "Protocol.hpp"
#include <iostream>class Cal
{
public:
Response Execute(Request &req)
{
Response resp(0, 0); // code: 0表示成功
switch (req.Oper())
{
case '+':
resp.SetResult(req.X() + req.Y());
break;
case '-':
resp.SetResult(req.X() - req.Y());break; case '*': resp.SetResult(req.X() * req.Y()); break; case '/': { if (req.Y() == 0) { resp.SetCode(1); // 1除零错误 } else { resp.SetResult(req.X() / req.Y()); } } break; case '%': { if (req.Y() == 0) { resp.SetCode(2); // 2 mod 0 错误 } else { resp.SetResult(req.X() % req.Y()); } } break; default: resp.SetCode(3); // 非法操作 break; } return resp; }};