
应用层我们程序员写的⼀个个解决我们实际问题,满足我们日常需求的网络程序,都是在应用层.再谈协议协议是⼀种约定.socket的api的接,在读写数据时,都是按字符串的方式来发送接收的.如果我们要传输⼀些结构化的数据怎么办呢?其实协议就是双方约定好的结构化的数据网络版计算器例如我们需要实现一个服务器版的加法器。我们需要客户端把要计算的两个加数发过去然后由服务器进行计算最后再把结果返回给客户端.约定方案一:客户端发送一个形如 12 的字符串这个字符串中有两个操作数都是整形两个数字之间会有一个字符是运算符运算符只能是 ;数字和运算符之间没有空格…约定方案二:定义结构体来表示我们需要交互的信息发送数据时将这个结构体按照一个规则转换成字符串接收到数据的时候再按照相同的规则把字符串转化回结构体这个过程叫做 序列化 和 反序列化序列化和反序列化图是重点完整流程发送方上层应用结构化数据聊天消息是 3 份独立数据消息内容、时间、昵称。协议定制 约定好这个结构体里面有哪些字段、每个字段含义。序列化发送前多变一把多个字段拼成一串连续字节流网络只认字节流不认识结构体、类对象必须转成连续字节才能发出去。网络传输网络只负责搬运字节流完全看不懂字符串里面代表聊天消息只负责把字节从一端送到另一端接收方收到字节流反序列化一变多把收到的一整条字节流按照协议规则拆回成多个字段能不能不序列化什么的直接发送结构体二进制内存对齐不同编译器、平台结构体内存对齐填充不一样收发两端结构体内存布局不一致解析出错。跨语言不兼容C/C 结构体二进制无法直接被 Python、Java 等其他语言解析。可移植性差大小端、int 类型字节数在不同机器上可能不同但在OS 内部协议全部都是传递结构体对象OS内部就不存在上面的问题所以从今天开始如果我们要进行网络协议式的通信在应用层强烈建议使用序列化和反序列化方案。至于直接传递结构体的方案除非场景特殊否则不建议重新理解read、write、recv、send和tcp为什么支持全双工整体流程主机 A 发送聊天消息 → 主机 B 接收1.主机 A发送端应用层内存里是结构化聊天消息结构体{message, time, nickname}做序列化拼成字符串你好啊 20xx‑yy‑zz aa:bb:cc 新时代好青年放在用户态 buffer。2.系统调用write(sockfd, buffer, len)write 本质拷贝把用户空间 buffer 的数据拷贝到内核的 TCP 发送缓冲区。 注意write返回成功不等于数据已经发到对端网络仅仅代表数据已经拷贝进本机内核发送缓冲区。什么时候发、发多少由 TCP 内核协议栈自主决定。主机 B接收端网络的数据先拷贝进入主机 B 内核的TCP 接收缓冲区。应用层调用read(sockfd, buffer, len)。read 本质拷贝把内核接收缓冲区的数据拷贝到用户态程序的 buffer。用户拿到字符串 buffer执行反序列化还原成结构体交给上层业务处理3 个核心结论1.write/read到底做了什么write用户态 → 本机内核发送缓冲区拷贝read本机内核接收缓冲区→ 用户态拷贝。write成功数据不一定已经在网络上更没有到达对方主机只是交给本机内核管理。TCP 内核自己决定什么时候发包、发多大、重传。2.主机之间网络通信的本质把发送方发送缓冲区的数据经过网络拷贝到对端机器的接收缓冲区。计算机世界通信即拷贝全程就是多次数据拷贝。3.TCP 为什么是全双工一个 socket 在内核里同时拥有发送缓冲区 接收缓冲区两套独立缓冲区。 一边可以发数据另一边同时可以收数据收发互不干扰所以是全双工。生产者消费者模型发送缓冲区应用程序write是生产者往缓冲区写TCP 内核是消费者从缓冲区取数据往外发。接收缓冲区TCP 内核是生产者把收到的数据放进缓冲区应用程序read是消费者把数据读走。缓冲区满的时候write会阻塞缓冲区空的时候read会阻塞等待数据到来。JsoncppJsoncpp是⼀个用于处理JSON数据的C库。它提供了将JSON数据序列化为字符串以及从字符串 反序列化为C数据结构的功能。Jsoncpp是开源的广泛用于各种需要处理JSON数的C项目中。安装ubuntusudo apt-get install libjsoncpp-devCentos: sudo yum install jsoncpp-devel序列化使用 Json::Value 的 toStyledString #include jsoncpp/json/json.h int main() { Json::Value root; root[name] joe; root[sex] 男; std::string s root.toStyledString(); std::cout s std::endl; return 0; }运行结果输出sex : \u7537不是 bugjson 标准规定中文默认转成 unicode 转义序列。\u7537就是汉字男的 unicode 编码json 解析的时候会自动还原回中文网络传输没问题使用Json::StreamWriter #include memory #include jsoncpp/json/json.h int main() { Json::Value root; root[name] joe; root[sex] 男; Json::StreamWriterBuilder wbuilder; // StreamWriter的⼯⼚ std::unique_ptrJson::StreamWriterwriter(wbuilder.newStreamWriter()); std::stringstream ss; writer-write(root, ss); std::cout ss.str() std::endl; return 0; }运行结果使用Json::FastWriter#include jsoncpp/json/json.h int main() { Json::Value root; root[name] joe; root[sex] 男; Json::FastWriter writer; std::string s writer.write(root); std::cout s std::endl; return 0; }运行结果反序列化使用 Json::Reader优点提供详细的错误信息和位置方便调试。#include jsoncpp/json/json.h using namespace std; int main() { // Json字符串 string json_string {\name\:\张三\, \age\:30, \city\:\北京\}; // 字符串内部想写一个普通双引号必须写成 \ // 解析Json字符串 Json::Reader reader; Json::Value root; // 从字符串中读取Json数据 bool ok reader.parse(json_string, root); if (!ok) { // 失败 cout Failed to parse JSON: reader.getFormattedErrorMessages() endl; return 1; } // 成功访问JSON数据 string name root[name].asString(); int age root[age].asInt(); string city root[city].asString(); // 输出结果 cout Name: name endl; cout Age: age endl; cout City: city endl; return 0; }运行结果代码实现NetCal.hpp:#pragma once #include Protocol.hpp #include iostream class Cal { public: Cal() {} 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-除0错误 } else { resp.SetResult(req.X() / req.Y()); } break; } case %: { if (req.Y() 0) { resp.SetCode(2); // 2-模0错误 } else { resp.SetResult(req.X() % req.Y()); } break; } default: resp.SetCode(3); // 非法操作 break; } return resp; } private: };Socket.hpp:#pragma once #include Common.hpp #include Log.hpp #include InetAddr.hpp namespace SocketModule { using namespace LogMoudle; const static int gbacklog 16; 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_ptrSocket 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(); } }; class TcpSocket : public Socket { public: TcpSocket() {} TcpSocket(int fd) : _sockfd(fd) { } void SocketOrDie() { _sockfd socket(AF_INET, SOCK_STREAM, 0); if (_sockfd 0) { LOG(LogLevel::FATAL) socket err; exit(SOCKET_ERR); } LOG(LogLevel::INFO) socket sucess; } void BindOrDie(uint16_t port) { InetAddr local(port); int n bind(_sockfd, local.NetAddrptr(), local.NetAddrLen()); if (n 0) { LOG(LogLevel::FATAL) bind err; exit(BIND_ERR); } LOG(LogLevel::INFO) bind sucess; } void ListenOrDie(int backlog) { int n listen(_sockfd, backlog); if (n 0) { LOG(LogLevel::FATAL) listen err; exit(LISTEN_ERR); } LOG(LogLevel::INFO) listen sucess; } std::shared_ptrSocket Accept(InetAddr *client) // 传参用shared_ptr { struct sockaddr_in peer; socklen_t len sizeof(peer); int fd accept(_sockfd, CONV(peer), len); // 这里为什么不写_sockfdaccept(....)用_sockfd来接受 // 理清楚逻辑 auto sock _listensockptr-Accept(client);进到Accept后还没有创建sock对象即如果修改_socket是 // 修改的listen对象的呀改了的话Close关闭的就不是listensockfd了而是创建了对象后在构造函数的时候_sockfdfd // 修改的就是sock对象 if (fd 0) { LOG(LogLevel::WARNING) accept warning ...; return nullptr; // TODO } client-SetAddr(peer); return make_sharedTcpSocket(fd); } void Close() { if (_sockfd 0) { close(_sockfd); } } int Recv(std::string *out) { char buffer[1024]; ssize_t n recv(_sockfd, buffer, sizeof(buffer) - 1, 0); if (n 0) { buffer[n] 0; *out buffer; // 为什么要写不写因为在protocol.hpp的GetRequest // 如果读取的不是一个完整报文会继续continue所以累加如果写成那就读一次 // 赋值一次就不会完整 } // 这里不对n的值0,0判断在protocol.hpp的GetRequest判断 return n; } int Send(const std::string message) { return send(_sockfd, message.c_str(), message.size(), 0); } int Connect(const std::string server_ip, uint16_t port) { InetAddr addr(server_ip, port); return connect(_sockfd, addr.NetAddrptr(), addr.NetAddrLen()); } private: int _sockfd; // 既可以是listensockfd也可以说accrpt返回的sockfd }; }Protocol.hpp:#pragma once #include iostream #include string #include memory #include Socket.hpp #include jsoncpp/json/json.h using namespace SocketModule; using namespace LogMoudle; using namespace std; class Request { public: Request() {} Request(int x, int y, char oper) : _x(x), _y(y), _oper(oper) { } string Serialize() { Json::Value root; root[x] _x; root[y] _y; root[oper] _oper; Json::FastWriter writer; string s writer.write(root); return s; } //{x: 10, y : 20, oper : } bool Deserialize(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(); } return ok; } int X() { return _x; } int Y() { return _y; } char Oper() { return _oper; } ~Request() {} private: int _x; int _y; char _oper; }; class Response { public: Response() {} Response(int result, int code) : _result(result), _code(code) { } string Serialize() { Json::Value root; root[result] _result; root[code] _code; Json::FastWriter writer; string s writer.write(root); return s; } 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; } void SetResult(int res) { _result res; } void SetCode(int code) { _code code; } void ShowResult() { cout 计算结果是 _result [ _code ] endl; } ~Response() {} private: int _result; // 运算结果无法区分清楚应答是计算结果还是异常值 int _code; // 0:sucess, 1,2,3,4-不同的运算异常的情况 }; // 协议(基于TCP的)需要解决两个问题 // 1. request和response必须得有序列化和反序列化功能 // 2. 你必须保证读取的时候读到完整的请求(TCP, UDP不用考虑) const string sep \r\n; using func_t functionResponse(Request req); class Protocol { public: Protocol() {} Protocol(func_t func) : _func(func) { } string Encode(const string jsonstr) { string len to_string(jsonstr.size()); return len sep jsonstr sep; //// 50\r\n{x: 10, y : 20, oper : }\r\n } bool Decode(string buffer, string *package) { ssize_t pos buffer.find(sep); if (pos string ::npos) { return false; } string package_len_str buffer.substr(0, pos); int package_len_int stoi(package_len_str); int target_len package_len_str.size() package_len_int 2 * sep.size(); // 一个完整报文的长度 if (buffer.size() target_len) { return false; } // 到这里至少有一个完整报文 *package buffer.substr(pos sep.size(), package_len_int); buffer.erase(0, target_len); return true; } void GetRequest(shared_ptrSocket sock, InetAddr client) { string buffer_queue; while (true) { int n sock-Recv(buffer_queue); if (n 0) { // 1.解析报文提取完整的json请求如果不完整就让服务器继续读取 string json_package; // bool retDecode(buffer_queue,json_package); // if(!ret) // { // continue; // } // 到这里一定读取到完整的报文了 while (Decode(buffer_queue, json_package)) // 如果buffer_queue里面读到了多个完整的报文 // 就要while循环每次取一个到json_package里面处理后再去继续buffer_queue里面读处理完 { // 2. 请求json串反序列化 Request req; bool ok req.Deserialize(json_package); if (!ok) { continue; } // 3.计算业务处理 Response resp _func(req); // 4.序列化 string json_str resp.Serialize(); // 5.添加自定义长度 string send_str Encode(json_str); // 6.直接发送 sock-Send(send_str); } break; } else if (n 0) { LOG(LogLevel::INFO) client client.StringAddr() Quit; break; } else { LOG(LogLevel::WARNING) client: client.StringAddr() ,recv error; break; } } } string BuildRequestString(int x, int y, char oper) { Request req(x, y, oper); string res req.Serialize(); return Encode(res); } void GetRespose(shared_ptrSocket client, Response resp) { string buffer_queue; while (true) { int n client-Recv(buffer_queue); if (n 0) { string json_package; while (Decode(buffer_queue, json_package)) { coutnbendl; // 反序列化 resp.Deserialize(json_package); } break; } else if (n 0) { std::cout server quit std::endl; break; } else { std::cout recv error std::endl; break; } } } ~Protocol() { } private: func_t _func; // 因为我们用的是多进程 // 用多进程 fork 模型fork 会拷贝对象内存成员变量状态会乱 // Request _req; // Response _resp; };TcpClient.cc:#includeProtocol.hpp #includeCommon.hpp #includestring void GetDataFromStdin(int *x,int *y,char *oper) { coutPlease Enter xendl; cin*x; coutPlease Enter yendl; cin*y; coutPlease Enter operendl; cin*oper; } int main(int argc,char *argv[]) { if(argc!3) { coutUsage:argv[0]server_ipserver_portendl; } uint16_t server_portstoi(argv[2]); string server_ipargv[1]; shared_ptrSocketclientmake_sharedTcpSocket(); client-BuildTcpClientSocketMethod(); if(client-Connect(server_ip,server_port)!0) { // 失败 std::cerr connect error std::endl; exit(CONNECT_ERR); } unique_ptrProtocolprotocolmake_uniqueProtocol(); while (true) { // 1. 从标准输入当中获取数据 int x, y; char oper; GetDataFromStdin(x, y, oper); //2.请求得到一个可以直接发送的报文 string req_str protocol-BuildRequestString(x, y, oper); //3.发送 client-Send(req_str); //4.接受服务端返回的结果 Response resp; protocol-GetRespose(client,resp); //5.显示结果 resp.ShowResult(); } client-Close(); return 0; }TcpServer.hpp:#pragma once #include Socket.hpp #include iostream #include memory #include sys/wait.h #include functional using namespace SocketModule; using ioservice_t functionvoid(shared_ptrSocket sock, InetAddr client); class TcpServer { public: TcpServer(uint16_t port, ioservice_t fun) : _port(port), _listensockptr(make_uniqueTcpSocket()), _isrunning(false), _service(fun) { _listensockptr-BuildTcpSocketMethod(port); } void Start() { _isrunning true; while (_isrunning) { InetAddr client; auto sock _listensockptr-Accept(client); if (sock nullptr) { continue; } LOG(LogLevel::DEBUG) accept sucess; pid_t id fork(); if (id 0) { LOG(LogLevel::FATAL) fork error; exit(FORK_ERR); } else if (id 0) { // 子进程 _listensockptr-Close(); if (fork() 0) { exit(OK); } // 孙子进程 _service(sock, client); exit(OK); } else { sock-Close(); pid_t rid waitpid(id, nullptr, 0); (void)rid; } } _isrunning false; } private: uint16_t _port; unique_ptrSocket _listensockptr; bool _isrunning; ioservice_t _service; };main.cc:#include Protocol.hpp #include TcpServer.hpp #include NetCal.hpp using namespace std; void Usage(std::string proc) { std::cerr Usage: proc port std::endl; } // ./tcpserver 8080 int main(int argc, char *argv[]) { Enable_Console_Log_Strategy(); if (argc ! 2) { Usage(argv[0]); exit(USAGE_ERR); } unique_ptrCalcalmake_uniqueCal(); unique_ptrProtocolprotocolmake_uniqueProtocol([cal](Request req){ return cal-Execute(req); }); unique_ptrTcpServertsvrmake_uniqueTcpServer(stoi(argv[1]),[protocol] (shared_ptrSocketsock,InetAddrclient){ protocol-GetRequest(sock,client); }); tsvr-Start(); }makefile:.PHONY:all all:tcpserver tcpclient tcpserver:main.cc g -o $ $^ -stdc17 -ljsoncpp tcpclient:TcpClient.cc g -o $ $^ -stdc17 -ljsoncpp .PHONY:clean clean: rm -f tcpserver tcpclient