应⽤层
我们程序员写的⼀个个解决我们实际问题,满⾜我们⽇常需求的⽹络程序,都是在应⽤层
再谈"协议"
在 Linux 网络(乃至所有计算机网络)里,协议说白了就是通信双方提前商量好的「规则手册」------ 比如数据怎么发、发什么格式、出了问题怎么处理,都得按约定来,不然就像两个人说话鸡同鸭讲,根本没法沟通。
4层TCP/IP模型:
| 层级 | 核心作用 | 常见协议 | 生活类比 |
|---|---|---|---|
| 应用层 | 给应用程序提供特定服务,定义数据的格式和含义 | HTTP、SSH、DNS、自定义协议(比如你的计算器) | 信的内容(用中文写的 "你好") |
| 传输层 | 负责端到端的连接和数据可靠性 | TCP(可靠、有连接)、UDP(快速、无连接) | 怎么把信安全送到(挂号信 vs 平信) |
| 网络层 | 寻址和路由,找到目标设备 | IP(IPv4/IPv6)、ICMP(ping) | 信封上的地址(国家 - 城市 - 街道) |
| 网络接口层 | 处理物理硬件(网卡、网线) | ARP、以太网协议 | 送信的交通工具(货车、飞机) |
我们之前写的 TCP/UDP 程序,只解决了「数据怎么传」的问题,但「传什么格式的数据、数据代表什么意思」,需要我们自己定规则 ------ 这就是我们的自定义应用层协议。
网络版计算器
例如,我们需要实现⼀个服务器版的加法器.我们需要客⼾端把要计算的两个加数发过去,然后由服务器 进⾏计算,最后再把结果返回给客⼾端.
- 客户端发送计算式,如1+1,发送给服务器
- 服务器接受到计算式,在后台进行计算,然后发送给客户端
问题来了:客户端能直接把 "1+1" 这个字符串发给服务器吗?
答案是:不可以直接裸发。
举个例子:假设我们要发送 1+1+1 这个式子给服务器。由于互联网传输的不确定性,数据可能会被拆分传输:第一次只传了 1+1,第二次才传 +1。服务器收到 1+1 后,不知道后面还有数据,就会直接当成完整式子计算,得到错误的结果。
解决方案:序列化与反序列化
因此,我们需要对数据进行一层封装,就像给快递套上 "包装盒":
这样,服务器就永远只会处理完整的计算式,不会再被 "半包 / 粘包" 问题干扰了。
序列化和反序列化
在网络传输中,序列化与反序列化就像是给数据做「标准化打包和拆包」:
- 序列化:客户端发送数据前,把程序里的结构化数据(比如结构体、自定义对象),按约定格式转换成一串带边界标记的字节流,方便网络传输。
- 反序列化:服务端收到数据后,再按照约定的规则,把这串字节流还原成程序能直接用的结构化数据。
用结构体理解序列化 / 反序列化

1. 先约定双方通用的通信协议
想要顺畅通信,两端必须提前约定好消息的结构(对应上图的struct/ 类class),这条聊天消息固定包含 3 个字段:
message:聊天正文内容,示例值:你好啊time:消息发送时间,约定固定格式为20xx-yy-zz aabbccnickname:发送者昵称,示例值:新时代好青年两端都清楚每个字段的含义、排版规则,这就是通信的协议根基。
2. 发送端:序列化(多变一)
程序里message、time、nickname是相互独立的三段数据,没法直接高效走网络传输。序列化会把这三段内容按约定顺序拼接,整合为一整条连续长字符串:你好啊 20xx-yy-zz aabbcc 新时代好青年,实现信息由多变一。处理完成后发送方只需要把这条连续字节流交给网络,不用关心底层传输的分包细节。
3. 网络传输环节
整合好的长字节流在网络中传输,传输层只保障字节有序送达,不会区分原本的字段边界,所以必须依赖两端提前定好的协议,后续才能拆分还原内容。
4. 接收端:反序列化(一变多)
接收端拿到整条传输过来的长字符串后,按照提前约定的字段规则,把连续数据拆分还原成三段独立信息:
- 提取
message:你好啊 - 提取
time:20xx-yy-zz aabbcc - 提取
nickname:新时代好青年实现信息由一变多,上层聊天软件就可以正常展示、处理这条消息了,这个拆分还原的过程就是反序列化。
网络计算器实现
Socket封装
为了方便,我们专门把socket相关函数用C++封装成类Socket虚基类 :保留socket基本接口,如bind,listen,recv等,设计InitTcpServer和InitTcpClient接口,后续派生类可调用这两个接口自由创建服务器端和客户端
TcpSocket派生类 :根据虚基类实现具体的TCP的接口
cpp
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include "com.hpp"
#include <memory>
class Socket
{
public:
Socket()
{}
~Socket()
{}
virtual void create_socket() = 0;
virtual void Bind(uint16_t) = 0;
virtual void Listen(int) = 0;
virtual std::shared_ptr<Socket> Accept(InetAddr&) = 0;
virtual bool Connect(const std::string&,const uint16_t&) = 0;
virtual int Recv(std::string&) = 0;
virtual void Send(const std::string&) = 0;
virtual int get_sockfd() = 0;
void InitTcpServer(uint16_t port = DEFAULT_PORT,int backlog = DEFAULT_BACKLOG)
{
create_socket();
Bind(port);
Listen(backlog);
}
void InitTcpClient(std::string ip = DEFAULT_IP,uint16_t port = DEFAULT_PORT)
{
create_socket();
Connect(ip,port);
}
};
class TcpSocket : public Socket
{
private:
using func_t = std::function<void()>;
public:
TcpSocket(int sockfd = DEFAULT_SOCKFD)
:_sockfd(sockfd)
{}
void create_socket() override
{
_sockfd = socket(AF_INET,SOCK_STREAM,0);
if(_sockfd < 0)
exit(ExitCode::SOCKET);
}
void Bind(uint16_t port)override
{
InetAddr addr(port);
int n = bind(_sockfd,CONV(addr.get_addr()),sizeof(addr.get_addr()));
if(n < 0)
exit(ExitCode::BIND);
}
void Listen(int backlog)override
{
int n = listen(_sockfd,backlog);
if(n < 0)
exit(ExitCode::LISTEN);
}
std::shared_ptr<Socket> Accept(InetAddr& addr)override
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int sockfd = accept(_sockfd,CONV(peer),&len);
if(sockfd < 0)
return nullptr;
InetAddr tmp(peer);
addr = tmp;
std::shared_ptr<Socket> p = std::make_shared<TcpSocket>(sockfd);
return p;
}
bool Connect(const std::string& ip,const uint16_t& port)override
{
InetAddr addr(ip,port);
int n = connect(_sockfd,CONV(addr.get_addr()),sizeof(addr.get_addr()));
if(n < 0)
return false;
return true;
}
int Recv(std::string& out)override
{
char buffer[MAXNUM];
ssize_t n = recv(_sockfd,buffer,sizeof(buffer) - 1,0);
if(n > 0)
{
buffer[n] = '\0';
out += buffer;
return n;
}
else if(n == 0)
return 0;
else
return -1;
}
void Send(const std::string& out)override
{
send(_sockfd,out.c_str(),sizeof(out),0);
}
int get_sockfd()override
{
return _sockfd;
}
private:
int _sockfd;
};
定制协议
我们定制的协议:
1.结构化字段,提供好的序列和反序列化方案;
2.解决因为字节流问题,导致读取报文不完整的问题。
因此,一个完整的协议至少需要这两个类:
- Request:发送端发送的请求数据
- Response:接收端根据请求,进行处理后返回给发送端的数据
具体流程:
发送端->request序列化,发送给接收端->接收端接受request,反序列化->对request处理,制作response->response序列化,返回给发送端->发送端接受response,反序列化得到结果
Request:
cpp
class Request
{
public:
Request(int x = 0,char oper = ' ',int y = 0)
:_x(x),_oper(oper),_y(y)
{}
void Serialize(std::string& out)//序列化:成员变量序列化成字符串
{
}
bool Dserialize(std::string& out)//反序列化:字符串反序列化,提取成类中的成员变量
}
int get_x(){return _x;}
char get_oper(){return _oper;}
int get_y(){return _y;}
std::string get_request_string()
{
return std::to_string(_x) + " " + _oper + " " + std::to_string(_y);
}
private:
int _x;
char _oper;
int _y;
};
Response:
cpp
class Rseponse
{
public:
Response(double ret = 0,int code = 0)
:_result(ret),_code(code)
{}
Response(Response& rep)
{}
Response(Response&& rep)
{}
Response& operator=(Response&& rep)
{}
Response& operator=(Response& rep)
{}
void Serialize(std::string& out)//序列化
{}
bool Dserialize(std::string& out)//反序列化
{}
double get_ret()
{
return _result;
}
int get_code()
{
return _code;
}
std::string get_result_string()
{
return std::to_string(_result) + "[" +std::to_string(_code) + "]";
}
private:
double _result;//计算结果
int _code;//计算状态码
};
TCP 是一个面向字节流 的协议,它本身不维护消息边界。当发送方连续发送多个小数据包,或接收方一次读取到多个包的数据时,就可能出现"粘包 "------即多个应用层消息粘在一起,无法区分哪里是结束、哪里是开始。解决粘包问题的核心是在应用层定义消息边界。
一、发送端:打包流程(两步走)
- 业务序列化 :先把业务请求(如计算式
1+1)转成结构化的正文数据,比如{1 + 1}。 - 加协议边界(定界) :给正文加上「长度头 + 分隔符」,封装成可识别的完整报文:
- 固定格式:
正文长度\r\n正文内容\r\n - 例子:正文
{1 + 1}共 7 个字节,最终报文为7\r\n{1 + 1}\r\n - 作用:用长度头 + 首尾分隔符,给 TCP 字节流打上 "消息边界"。
- 固定格式:
二、接收端:解包流程(三步判断)
- 找分隔符 :先找报文开头的
\r\n,如果没找到,说明数据不完整,直接返回继续接收。 - 算总长度 :从分隔符前拿到正文长度,计算完整报文的总长度:
总长度 = 长度头长度 + 2个分隔符长度 + 正文长度(例子中为1 + 2*2 + 7 = 12字节) - 校验并处理 :
- 收到的数据长度 < 总长度:包不完整,等待后续数据。
- 收到的数据长度 ≥ 总长度:切出完整报文处理,剩余字节留到下一次解析。
这就是打包和解包的过程
我们再构建Protocol类,这个类用来:
- 对数据的打包和解包
- 接发送数据
- 处理数据(具体业务,需要上层指定)
cpp
class Protocol
{
private:
const std::string proto_sep = " ";
const std::string line_sep = "\r\n";//分隔符
using func_t = std::function<Reponse(Request&)>;//业务处理函数,上层指定
public:
Protocol()
{}
Protocol(func_t func)
:_func(func)
{}
void Encode(std::string& message)//打包
{
message = std::to_string(message.size()) + line_sep + message + line_sep;
}
bool Decode(std::string& package,std::string& message)//解包
{
int pos = package.find(line_sep);
if(pos == std::string::npos)
return false;
std::string num = package.substr(0,pos);
int total = std::stoi(num) + num.size() + 2 * line_sep.size();
if(package.size() < total)
return false;
message = package.substr(pos + line_sep.size(),std::stoi(num));
package.erase(0,total);
return true;
}
bool get_request(std::shared_ptr<Socket>& sock,Request& req)//接受request,进行1.解包2.反序列化
{
std::string package;
while(1)
{
int n = sock->Recv(package);
if(n > 0)
{
std::string message;
if(!Decode(package,message))
continue;
req.Dserialize(message);
return true;
}
else if(n == 0)
exit(ExitCode::NORMAL);
else
return false;
}
}
bool get_reponse(std::shared_ptr<Socket>& sock,Reponse& rep)//接受response,进行1.解包2.反序列化
{
std::string package;
while(true)
{
int n = sock->Recv(package);
if(n > 0)
{
std::string message;
if(!Decode(package,message))
continue;
rep.Dserialize(message);
return true;
}
else if(n == 0)
return false;
else
return false;
}
}
Reponse handle(Request& req)//数据处理(接受端的任务)
{
return _func(req);
}
private:
func_t _func;
};
Jsoncpp
Jsoncpp是⼀个⽤于处理JSON数据的C++库。它提供了将JSON数据序列化 为字符串以及从字符串 反序列化为C++数据结构的功能。
Jsoncpp是开源的,⼴泛⽤于各种需要处理JSON数据的C++项⽬ 中。
特性
-
简单易⽤:Jsoncpp提供了直观的API,使得处理JSON数据变得简单。
-
⾼性能:Jsoncpp的性能经过优化,能够⾼效地处理⼤量JSON数据。
-
全⾯⽀持:⽀持JSON标准中的所有数据类型,包括对象、数组、字符串、数字、布尔值和null。
-
错误处理:在解析JSON数据时,Jsoncpp提供了详细的错误信息和位置,⽅便开发者调试。
安装
bash
ubuntu:sudo apt-get install libjsoncpp-dev
Centos:sudo yum installjsoncpp-devel
检查头文件是否安装
bash
ls /usr/include/jsoncpp/json/
能看到 json.h、value.h 等文件,说明头文件已经装好了。
序列化
使用Json::Value 和Json::FastWriter快速上手
cpp
#include <jsoncpp/json/json.h>
#include <iostream>
#include <jsoncpp/json/value.h>
int main()
{
Json::Value json;
json["x"] = 1;
json["oper"] = '+';
json["y"] = 2;
Json::FastWriter w;
std::string out = w.write(json);
std::cout<<out;
return 0;
}

反序列化
使用Json::Value 和Json::Reader快速上手
cpp
#include <jsoncpp/json/json.h>
#include <iostream>
#include <jsoncpp/json/reader.h>
#include <jsoncpp/json/value.h>
int main()
{
Json::Value json;
json["x"] = 1;
json["oper"] = '+';
json["y"] = 2;
Json::FastWriter w;
std::string out = w.write(json);
std::cout<<out;
Json::Reader r;
bool res = r.parse(out,json);
if(res)
{
int x = json["x"].asInt();
char oper = json["oper"].asInt();
int y = json["y"].asInt();
std::cout<<x<<" "<<oper<<" "<<y<<std::endl;
}
return 0;
}

Request和Response实现
cpp
class Request
{
public:
Request(int x = 0,char oper = ' ',int y = 0)
:_x(x),_oper(oper),_y(y)
{}
void Serialize(std::string& out)
{
Json::Value json;
json["x"] = _x;
json["oper"] = _oper;
json["y"] = _y;
Json::FastWriter w;
out = w.write(json);
}
bool Dserialize(std::string& out)
{
Json::Value json;
Json::Reader r;
bool res = r.parse(out,json);
if(res)
{
_x = json["x"].asInt();
_oper = json["oper"].asInt();
_y = json["y"].asInt();
}
return res;
}
int get_x(){return _x;}
char get_oper(){return _oper;}
int get_y(){return _y;}
std::string get_request_string()
{
return std::to_string(_x) + " " + _oper + " " + std::to_string(_y);
}
private:
int _x;
char _oper;
int _y;
};
class Reponse
{
public:
Reponse(double ret = 0,int code = 0)
:_result(ret),_code(code)
{}
Reponse(Reponse& rep)
{
_result = rep.get_ret();
_code = rep.get_code();
}
Reponse(Reponse&& rep)
{
_result = rep.get_ret();
_code = rep.get_code();
}
Reponse& operator=(Reponse&& rep)
{
_result = rep.get_ret();
_code = rep.get_code();
return *this;
}
Reponse& operator=(Reponse& rep)
{
_result = rep.get_ret();
_code = rep.get_code();
return *this;
}
void Serialize(std::string& out)
{
Json::Value json;
json["result"] = _result;
json["code"] = _code;
Json::FastWriter w;
out = w.write(json);
}
bool Dserialize(std::string& out)
{
Json::Value json;
Json::Reader r;
bool res = r.parse(out,json);
if(res)
{
_result = json["result"].asDouble();
_code = json["code"].asInt();
}
return res;
}
double get_ret()
{
return _result;
}
int get_code()
{
return _code;
}
std::string get_result_string()
{
return std::to_string(_result) + "[" +std::to_string(_code) + "]";
}
private:
double _result;
int _code;
};
jsoncpp总结
• toStyledString 、StreamWriter 和 FastWriter 提供了不同的序列化选项,你可以根 据具体需求选择使⽤。
• Json::Reader 和 parseFromStream 函数是Jsoncpp中主要的反序列化⼯具,它们提供了强 ⼤的错误处理机制。
• 在进⾏序列化和反序列化时,请确保处理所有可能的错误情况,并验证输⼊和输出的有效性。
计算器设计
自定义协议能够让我们成功接收到"{1 + 1}"这个字符串,并对这个字符串反序列化成了Request,但这个Request怎么处理?
我们需要上层自定义。而在这里,我们要对这个Request中的数据进行普通的加减乘除的运算,因此我们需要设计计算器
这个计算器需要:
1.接受Request参数
2.对Request中的数据进行处理
3.对处理后数据做成Response
cpp
class cal
{
public:
Reponse func(Request &req)
{
auto x = req.get_x();
auto y = req.get_y();
char oper = req.get_oper();
switch (oper)
{
case '+':
return Reponse(x + y, 0);
case '-':
return Reponse(x - y, 0);
case '*':
return Reponse(x * y, 0);
case '/':
if (y == 0)
return Reponse(0, 1);
else
return Reponse(x / y, 0);
case '%':
return Reponse(x % y, 0);
default:
return Reponse(0, 2);
}
}
};
服务器设计
我们作为开发者,期望设计一个TcpServer,这个TcpServer内部包含TcpSocket和业务处理函数;TcpSocket由我们自己提供,而业务处理函数需要上层进行提供
cpp
#pragma once
#include "socket.hpp"
class TcpServer : public nocopy
{
private:
using func_t = std::function<void(std::shared_ptr<Socket>&,InetAddr&)>;//自定义处理函数
public:
TcpServer(uint16_t port,func_t func)
:_port(port),_ioserver(func),_listensock(std::make_shared<TcpSocket>())
{}
void init(int backlog = DEFAULT_BACKLOG)
{
_listensock->InitTcpServer(_port,backlog);
_listensockfd = _listensock->get_sockfd();
}
void run()
{
_isrunning = true;
while(_isrunning)
{
InetAddr addr;
std::shared_ptr<Socket> sock = _listensock->Accept(addr);
if(sock == nullptr)
continue;
pid_t pid = fork();
if(pid > 0)
{
close(sock->get_sockfd());
waitpid(pid,nullptr,0);
}
else if(pid == 0)
{
if(fork() > 0)
exit(ExitCode::NORMAL);
close(_listensock->get_sockfd());
_ioserver(sock,addr);//把数据全盘交给用户的业务处理函数
}
else
exit(ExitCode::FORK);
}
}
private:
uint16_t _port;
int _listensockfd;
bool _isrunning = false;
std::shared_ptr<Socket> _listensock;
func_t _ioserver;
};
上层使用
- 底层:提供TcpServer,用来接受数据。但这个数据接受后,我该如何处理?看上层的选择
- 上层:提供自定义协议,在TcpServer接受到数据后进行个性化处理
cpp
#include "NetCal.hpp"
#include "protocol.hpp"
#include "TcpServer.hpp"
#include "com.hpp"
int main(int argv, char *argc[])
{
if (argv != 2)
exit(ExitCode::FORMAT);
std::shared_ptr<cal> netcal = std::make_shared<cal>();
std::shared_ptr<Protocol> prot = std::make_shared<Protocol>([&netcal](Request &req) -> Reponse
{ return netcal->func(req); });
TcpServer server(atoi(argc[1]), [&prot](std::shared_ptr<Socket> &server, InetAddr &addr)
{//上层自定义处理函数
while(1)
{
Request req;
prot->get_request(server,req);
std::cout<<"request : "<<req.get_request_string()<<std::endl;
Reponse rep = prot->handle(req);
std::cout<<"reponse : "<<rep.get_result_string()<<std::endl;
std::string message;
rep.Serialize(message);
prot->Encode(message);
server->Send(message);
}
}
);
server.init();
server.run();
}