【在Linux世界中追寻伟大的One Piece】应用层自定义协议|序列化

目录

1 -> 应用层

2 -> 网络版计算器

3 -> 序列化与反序列化

4 -> 重新理解read、write、recv、send和tcp为什么支持全双工

5 -> 开始实现

5.1 -> 定制协议

5.2 -> 关于流式数据的处理


1 -> 应用层

应用层是OSI模型或TCP/IP模型中的最高层,它直接为用户的应用程序提供网络服务。应用层的主要功能包括:

  • 用户交互:提供用户与计算机网络交互的界面,允许用户访问网络资源、发送和接收数据、运行应用程序等。
  • 数据处理:允许用户在端系统上进行文档编辑、数据存储和处理等操作。
  • 网络通信:通过网络接口与计算机网络进行通信,发送和接收数据,与其他端系统或网络设备进行交互。
  • 支持多种网络应用模型:如客户/服务器模型(C/S模型)和对等网络模型(P2P模型),这些模型定义了应用程序之间通信和服务提供的方式。
  • 提供网络服务:如域名解析系统(DNS)、文件传输协议(FTP)、电子邮件传输协议(SMTP、POP3、IMAP)和超文本传输协议(HTTP)等。
  • 数据表示和转换:确保不同系统和应用程序之间的数据能够正确理解和处理,包括数据格式转换、字符编码、数据压缩和数据加密。
  • 会话管理:管理应用程序之间的会话,包括会话的建立、维护和终止。
  • 错误处理和恢复:处理通信过程中的错误,并提供相应的恢复机制。
  • 用户接口:提供图形用户界面(GUI)或命令行界面(CLI),使用户能够方便地使用网络服务。

2 -> 网络版计算器

例如,我们需要实现一个服务器版的加法器。我们需要客户端把要计算的两个加数发过去,然后由服务器进行计算,最后再把结果返回给客户端。

约定方案一:

  • 客户端发送一个形如"1+1"的字符串。
  • 这个字符串中有两个操作数,都是整形。
  • 两个数字之间会有一个字符是运算符,运算符只能是+。
  • 数字和运算符之间没有空格。

约定方案二:

  • 定义结构体来表示我们需要交互的信息。
  • 发送数据时将这个结构体按照一个规则转换成字符串,接收到数据的时候再按照相同的规则把字符串转化回结构体。
  • 这个过程叫做"序列化"和"反序列化"。

3 -> 序列化与反序列化

无论我们采用方案一,还是方案二,还是其他的方案,只要保证,一端发送时构造的数据,在另一端能够正确的进行解析,就是OK的。这种约定,就是应用层协议

但是,为了让我们深刻理解协议,我们打算自定义实现一下协议的过程。

  • 采用方案2,我们也要体现协议定制的细节。
  • 引入序列化和反序列化。
  • 要对socket进行字节流的读取处理。

4 -> 重新理解read、write、recv、send和tcp为什么支持全双工

  • 在任何一台主机上,TCP连接既有发送缓冲区,又有接受缓冲区,所以,在内核中,可以在发消息的同时,也可以收消息,即全双工
  • 这就是为什么一个tcp sockfd读写都是它的原因。
  • 实际数据什么时候发,发多少,出错了怎么办,由TCP控制,所以TCP叫做传输控制协议

5 -> 开始实现

代码结构

Calculate.hpp Makefile Socket.hpp TcpServer.hpp
Daemon.hpp Protocol.hpp TcpClientMain.cc TcpServerMain.cc
// 简单起见,可以直接采用自定义线程
// 建议不用用户输入,直接 client<<->>server 通信,这样可以省去编写没有干货的代码

Socket封装

socket.hpp

#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>

#define Convert(addrptr) ((struct sockaddr *)addrptr)

namespace Net_Work
{
	const static int defaultsockfd = -1;
	const int backlog = 5;
	enum
	{
		SocketError = 1,
		BindError,
		ListenError,
	};
	// 封装一个基类,Socket 接口类
	// 设计模式:模版方法类
	class Socket
	{
	public:
		virtual ~Socket() {}
		virtual void CreateSocketOrDie() = 0;
		virtual void BindSocketOrDie(uint16_t port) = 0;
		virtual void ListenSocketOrDie(int backlog) = 0;
		virtual Socket* AcceptConnection(std::string* peerip,
			uint16_t* peerport) = 0;
		virtual bool ConnectServer(std::string& serverip, uint16_t
			serverport) = 0;
		virtual int GetSockFd() = 0;
		virtual void SetSockFd(int sockfd) = 0;
		virtual void CloseSocket() = 0;
		virtual bool Recv(std::string* buffer, int size) = 0;
		virtual void Send(std::string& send_str) = 0;
		// TODO
	public:
		void BuildListenSocketMethod(uint16_t port, int backlog)
		{
			CreateSocketOrDie();
			BindSocketOrDie(port);
			ListenSocketOrDie(backlog);
		}

		bool BuildConnectSocketMethod(std::string& serverip,
			uint16_t serverport)
		{
			CreateSocketOrDie();
			return ConnectServer(serverip, serverport);
		}

		void BuildNormalSocketMethod(int sockfd)
		{
			SetSockFd(sockfd);
		}
	};

	class TcpSocket : public Socket
	{
	public:
		TcpSocket(int sockfd = defaultsockfd) : _sockfd(sockfd)
		{
		}

		~TcpSocket()
		{
		}

		void CreateSocketOrDie() override
		{
			_sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
			if (_sockfd < 0)
				exit(SocketError);
		}

		void BindSocketOrDie(uint16_t port) override
		{
			struct sockaddr_in local;
			memset(&local, 0, sizeof(local));

			local.sin_family = AF_INET;
			local.sin_addr.s_addr = INADDR_ANY;
			local.sin_port = htons(port);

			int n = ::bind(_sockfd, Convert(&local),
				sizeof(local));
			if (n < 0)
				exit(BindError);
		}

		void ListenSocketOrDie(int backlog) override
		{
			int n = ::listen(_sockfd, backlog);
			if (n < 0)
				exit(ListenError);
		}

		Socket* AcceptConnection(std::string * peerip, uint16_t
			* peerport) override
		{
			struct sockaddr_in peer;
			socklen_t len = sizeof(peer);
			int newsockfd = ::accept(_sockfd, Convert(&peer),
				&len);
			if (newsockfd < 0)
				return nullptr;

			*peerport = ntohs(peer.sin_port);
			*peerip = inet_ntoa(peer.sin_addr);
			Socket* s = new TcpSocket(newsockfd);

			return s;
		}

		bool ConnectServer(std::string& serverip, uint16_t
			serverport) override
		{
			struct sockaddr_in server;
			memset(&server, 0, sizeof(server));

			server.sin_family = AF_INET;
			server.sin_addr.s_addr = inet_addr(serverip.c_str());
			server.sin_port = htons(serverport);

			int n = ::connect(_sockfd, Convert(&server),
				sizeof(server));
			if (n == 0)
				return true;
			else
				return false;
		}

		int GetSockFd() override
		{
			return _sockfd;
		}

		void SetSockFd(int sockfd) override
		{
			_sockfd = sockfd;
		}

		void CloseSocket() override
		{
			if (_sockfd > defaultsockfd)
				::close(_sockfd);
		}

		bool Recv(std::string* buffer, int size) override
		{
			char inbuffer[size];
			ssize_t n = recv(_sockfd, inbuffer, size - 1, 0);
			if (n > 0)
			{
				inbuffer[n] = 0;
				*buffer += inbuffer; // 故意拼接的
				return true;
			}
			else if (n == 0) 
				return false;
			else 
				return false;
		}

		void Send(std::string& send_str) override
		{
			send(_sockfd, send_str.c_str(), send_str.size(), 0);
		}

	private:
		int _sockfd;
	};
}

5.1 -> 定制协议

基本结构

定制基本的结构化字段,这个就是协议。

class Request
{
private:
	// _data_x _oper _data_y
	// 报文的自描述字段
	// "len\r\nx op y\r\n" : \r\n 不属于报文的一部分,约定
	// 很多工作都是在做字符串处理!
	int _data_x; // 第一个参数
	int _data_y; // 第二个参数
	char _oper; // + - * / %
};

class Response
{
private:
	// "len\r\n_result _code\r\n"
	int _result; // 运算结果
	int _code; // 运算状态
};

protocol.hpp

#pragma once
#include <iostream>
#include <memory>
#include <jsoncpp/json/json.h>

namespace Protocol
{
	// 问题
	// 1. 结构化数据的序列和反序列化
	// 2. 还要解决用户区分报文边界 --- 数据包粘报问题
	// 讲法
	// 1. 自定义协议
	// 2. 成熟方案序列和反序列化
	// "protocol_code\r\nlen\r\nx op y\r\n" : \r\n 不属于报文的一部分,约定
	const std::string ProtSep = " ";
	const std::string LineBreakSep = "\r\n";

	// "len\r\nx op y\r\n" : \r\n 不属于报文的一部分,约定
	std::string Encode(const std::string& message)
	{
		std::string len = std::to_string(message.size());
		std::string package = len + LineBreakSep + message +
			LineBreakSep;

		return package;
	}
	// "len\nx op y\n" : \n 不属于报文的一部分,约定
	// 我无法保证 package 就是一个独立的完整的报文
	// "l
	// "len
	// "len\r\n
	// "len\r\nx
	// "len\r\nx op
	// "len\r\nx op y
	// "len\r\nx op y\r\n"
	// "len\r\nx op y\r\n""len
	// "len\r\nx op y\r\n""len\n
	// "len\r\nx op
	// "len\r\nx op y\r\n""len\nx op y\r\n"
	// "len\r\nresult code\r\n""len\nresult code\r\n"
	bool Decode(std::string &package, std::string *message)
	{
		// 除了解包,我还想判断报文的完整性, 能否正确处理具有"边界"的报文
		auto pos = package.find(LineBreakSep);
		if (pos == std::string::npos)
			return false;

		std::string lens = package.substr(0, pos);
		int messagelen = std::stoi(lens);
		int total = lens.size() + messagelen + 2 *
			LineBreakSep.size();
		if (package.size() < total)
			return false;

		// 至少 package 内部一定有一个完整的报文了!
		*message = package.substr(pos + LineBreakSep.size(),
			messagelen);
		package.erase(0, total);

		return true;
	}

	class Request
	{
	public:
		Request() : _data_x(0), _data_y(0), _oper(0)
		{
		}

		Request(int x, int y, char op) : _data_x(x), _data_y(y),
			_oper(op)
		{
		}

		void Debug()
		{
			std::cout << "_data_x: " << _data_x << std::endl;
			std::cout << "_data_y: " << _data_y << std::endl;
			std::cout << "_oper: " << _oper << std::endl;
		}

		void Inc()
		{
			_data_x++;
			_data_y++;
		}

		// 结构化数据->字符串
		bool Serialize(std::string* out)
		{
			Json::Value root;
			root["datax"] = _data_x;
			root["datay"] = _data_y;
			root["oper"] = _oper;
			Json::FastWriter writer;
			*out = writer.write(root);

			return true;
		}

		bool Deserialize(std::string & in) // "x op y" [)
		{
			Json::Value root;
			Json::Reader reader;
			bool res = reader.parse(in, root);
			if (res)
			{
				_data_x = root["datax"].asInt();
				_data_y = root["datay"].asInt();
				_oper = root["oper"].asInt();
			}

			return res;
		}

		int GetX() 
		{ 
			return _data_x; 
		}

		int GetY() 
		{ 
			return _data_y; 
		}

		char GetOper() 
		{ 
			return _oper; 
		}

	private:
		// _data_x _oper _data_y
		// 报文的自描述字段
		// "len\r\nx op y\r\n" : \r\n 不属于报文的一部分,约定
		// 很多工作都是在做字符串处理!
		int _data_x; // 第一个参数
		int _data_y; // 第二个参数
		char _oper; // + - * / %
	};

	class Response
	{
	public:
		Response() : _result(0), _code(0)
		{
		}

		Response(int result, int code) : _result(result),
			_code(code)
		{
		}

		bool Serialize(std::string * out)
		{
			Json::Value root;
			root["result"] = _result;
			root["code"] = _code;
			Json::FastWriter writer;
			*out = writer.write(root);

			return true;
		}

		bool Deserialize(std::string & in) // "_result _code" [)
		{
			Json::Value root;
			Json::Reader reader;
			bool res = reader.parse(in, root);
			if (res)
			{
				_result = root["result"].asInt();
				_code = root["code"].asInt();
			}

			return res;
		}

		void SetResult(int res) 
		{ 
			_result = res; 
		}

		void SetCode(int code) 
		{ 
			_code = code; 
		}

		int GetResult() 
		{ 
			return _result; 
		}

		int GetCode() 
		{ 
			return _code; 
		}

	private:
		// "len\r\n_result _code\r\n"
		int _result; // 运算结果
		int _code; // 运算状态
	};

	// 简单的工厂模式,建造类设计模式
	class Factory
	{
	public:
		std::shared_ptr<Request> BuildRequest()
		{
			std::shared_ptr<Request> req =
				std::make_shared<Request>();

			return req;
		}

		std::shared_ptr<Request> BuildRequest(int x, int y, char
			op)
		{
			std::shared_ptr<Request> req =
				std::make_shared<Request>(x, y, op);

			return req;
		}

		std::shared_ptr<Response> BuildResponse()
		{
			std::shared_ptr<Response> resp =
				std::make_shared<Response>();

			return resp;
		}

		std::shared_ptr<Response> BuildResponse(int result, int
			code)
		{
			std::shared_ptr<Response> req =
				std::make_shared<Response>(result, code);

			return req;
		}
	};
}

期望的报文格式

5.2 -> 关于流式数据的处理

  • 如何保证你每次读取就能读完请求缓冲区的所有内容?
  • 怎么保证读取完毕或者读取没有完毕的时候,读到的就是一个完整的请求呢?
  • 处理TCP缓冲区中的数据,一定要保证正确处理请求。
const std::string ProtSep = " ";
const std::string LineBreakSep = "\n";
// "len\nx op y\n" : \n 不属于报文的一部分,约定

std::string Encode(const std::string& message)
{
	std::string len = std::to_string(message.size());
	std::string package = len + LineBreakSep + message +
		LineBreakSep;

	return package;
}

// "len\nx op y\n" : \n 不属于报文的一部分,约定
// 我无法保证 package 就是一个独立的完整的报文
// "l
// "len
// "len\n
// "len\nx
// "len\nx op
// "len\nx op y
// "len\nx op y\n"
// "len\nx op y\n""len
// "len\nx op y\n""len\n
// "len\nx op
// "len\nx op y\n""len\nx op y\n"
// "len\nresult code\n""len\nresult code\n"
bool Decode(std::string& package, std::string* message)
{
	// 除了解包,我还想判断报文的完整性, 能否正确处理具有"边界"的报文
	auto pos = package.find(LineBreakSep);
	if (pos == std::string::npos)
		return false;

	std::string lens = package.substr(0, pos);
	int messagelen = std::stoi(lens);
	int total = lens.size() + messagelen + 2 *
		LineBreakSep.size();
	if (package.size() < total)
		return false;

	// 至少 package 内部一定有一个完整的报文了!
	*message = package.substr(pos + LineBreakSep.size(),
		messagelen);
	package.erase(0, total);

	return true;
}

所以,完整的处理过程应该是:


感谢各位大佬支持!!!

互三啦!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/894182.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【D3.js in Action 3 精译_035】4.1 D3 中的坐标轴的创建(下篇):坐标轴与轴标签的具体实现

当前内容所在位置&#xff08;可进入专栏查看其他译好的章节内容&#xff09; 第一部分 D3.js 基础知识 第一章 D3.js 简介&#xff08;已完结&#xff09; 1.1 何为 D3.js&#xff1f;1.2 D3 生态系统——入门须知1.3 数据可视化最佳实践&#xff08;上&#xff09;1.3 数据可…

【Vue.js设计与实现】第三篇第9章:渲染器-简单Diff算法-阅读笔记

文章目录 9.1 减少 DOM 操作的性能开销9.2 DOM 复用与 key 的作用9.3 找到需要移动的元素9.4 如何移动元素9.5 添加新元素9.6 移除不存在的元素 系列目录&#xff1a;【Vue.js设计与实现】阅读笔记目录 当新旧vnode 的子节点都是一组节点时&#xff0c;为了以最小的性能…

《深度学习》OpenCV库、Dlib库 人脸检测 案例解析

目录 一、Dlib库 1、什么是Dlib库 2、OpenCV优缺点 1&#xff09;优点 2&#xff09;缺点 3、Dlib库优缺点 1&#xff09;优点 2&#xff09;缺点 4、安装Dlib库 二、案例实现 1、对图片进行人脸识别 运行结果&#xff1a; 2、使用摄像头或对视频检测人脸 运行结…

安装和简单使用Milvus

安装和简单使用Milvus 1 介绍 Milvus是国产的高性能分布式向量数据库。 # Milvus官网 https://milvus.io/# 安装文档 https://milvus.io/docs/install-overview.md# Python的对应关系和接口文档 https://milvus.io/api-reference/pymilvus/v2.4.x/About.md2 安装Milvus 2.1…

flutter assets配置加载本地图片报错

首选列出我在照着网上说的设置assets怎么搞都报错&#xff0c;错误如下&#xff0c;搞的我想骂娘。 flutter: uses-material-design: true assets: - assets/images 后来找到了下面这个教程&#xff0c;才终于解决&#xff0c;就是要在后面加一个"/" 。 flutter这个…

北京大学与长安汽车联合发布TEOcc: 时域增强的多模态占据预测

北京大学与长安汽车联合发布TEOcc: 时域增强的多模态占据预测 Abstract 作为一种新颖的3D场景表示&#xff0c;语义占据&#xff08;semantic occupancy&#xff09;在自动驾驶领域引起了广泛关注。然而&#xff0c;现有的占据预测方法主要集中于设计更好的占据表示形式&…

scala 抽象类

理解抽象类 抽象的定义 定义一个抽象类 &#xff1a;abstract class A {} idea实例 抽象类重写 idea实例 练习 1.abstract2.错3.abstract class A{}4.对

保姆级Pinpoint(APM)实战教程

什么是Pinpoint Pinpoint是由韩国NAVER公司开发并开源的一款应用程序管理工具&#xff0c;主要针对大规模分布式系统进行性能监控和故障诊断。通过跟踪分布式应用程序之间的事务&#xff0c;帮助分析系统的整体结构以及其中的组件是如何相互连接的。 与其对标的还有Twitter的Zi…

软件模拟I2C和硬件直接驱动I2C读取TCA95系列I2C转IO芯片分析

问题描述&#xff1a; 软件读取I2C转IO信号跳变&#xff0c;低电平时能读到高电平信号&#xff0c;高电平时能读到低电平信号&#xff0c;正确信号和错误信号的比值约10:1。 原因分析&#xff1a; I2C芯片的驱动底层采用了软件模拟实现&#xff0c;没有防错机制&#xff0c;…

MongoDB 8.0已全面可用

全球广受欢迎的文档型数据库MongoDB目前最新最强的版本&#xff0c;在易用性、企业级安全性、 弹性、可用性等方面均有大幅提升&#xff0c;适用于各种应用程序。 MongoDB 8.0的优化使整体吞吐量提高了32%&#xff0c;时间序列数据聚合的处理速度提高了200%以上。MongoDB 8.0的…

SSD |(七)FTL详解(中)

文章目录 &#x1f4da;垃圾回收&#x1f407;垃圾回收原理&#x1f407;写放大&#x1f407;垃圾回收实现&#x1f407;垃圾回收时机 &#x1f4da;解除映射关系&#x1f4da;磨损均衡 &#x1f4da;垃圾回收 &#x1f407;垃圾回收原理 ✋设定一个迷你SSD空间&#xff1a; 假…

OpenAi推出ChatGPT客户端

10 月 18 日&#xff0c;继苹果 macOS 版之后&#xff0c;OpenAI 为微软 Windows 用户推出了 ChatGPT 应用桌面客户端。目前这款应用正在测试&#xff0c;ChatGPT Plus / Enterprise / Team / Edu 版本的付费用户可以在微软应用商店中下载使用。 这款应用实质上是网页版 ChatGP…

Part1_MCP4017T-502E/LT型数字变阻器使用方法

MCP4017T-502E/LT是Microchip&#xff08;微芯&#xff09;公司的一款SC70封装且具备7位单I2C™数字端口与易失性存储器数字电位器&#xff0c;通过数字接口来控制电位器的阻值大小&#xff0c;可用于需要精确调整电压分压比、信号增益控制等应用场景。相比传统的机械电位器&am…

【Java】多线程 Start() 与 run() (简洁实操)

Java系列文章目录 补充内容 Windows通过SSH连接Linux 第一章 Linux基本命令的学习与Linux历史 文章目录 Java系列文章目录一、前言二、学习内容&#xff1a;三、问题描述start() 方法run() 方法 四、解决方案&#xff1a;4.1 重复调用 .run()4.2 重复调用 start()4.3 正常调用…

初识Linux · 重定向和缓冲区

目录 前言&#xff1a; 预备知识 缓冲区 重定向 前言&#xff1a; 其实有了文件2的预备知识&#xff0c;我们已经初步了解了文件描述符fd是什么&#xff0c;底层是如何运作的了&#xff0c;那么本文&#xff0c;我们通过文件描述符对重定向和缓冲区有一个更深层次的理解&a…

鸿蒙开发案例:推箱子

推箱子游戏&#xff08;Sokoban&#xff09;的实现。游戏由多个单元格组成&#xff0c;每个单元格可以是透明的、墙或可移动的区域。游戏使用Cell类定义单元格的状态&#xff0c;如类型&#xff08;透明、墙、可移动区域&#xff09;、圆角大小及坐标偏移。而MyPosition类则用于…

三菱PLC如何实现数据排序的分析?

一、分析 将D100到D104中的据从小到大排序结果存在D100到D104中&#xff0c;如D100到D104中存入100&#xff0c;34&#xff0c;27&#xff0c;45&#xff0c;22这5个数据&#xff0c;编写一个子程序&#xff0c;只到通过调用这个子程序就可以实现这5个数据的排序。当然简单的方…

iOS IPA上传到App Store Connect的三种方案详解

引言 在iOS应用开发中&#xff0c;完成开发后的重要一步就是将IPA文件上传到App Store Connect以便进行测试或发布到App Store。无论是使用Xcode进行原生开发&#xff0c;还是通过uni-app、Flutter等跨平台工具生成的IPA文件&#xff0c;上传到App Store的流程都是类似的。苹果…

衡石分析平台系统分析人员手册-应用模版

应用模板​ 应用模板使分析成果能被快速复用&#xff0c;节省应用创作成本&#xff0c;提升应用创作效率。此外应用模板实现了应用在不同环境上快速迁移。 支持应用复制功能 用户可以从现有的分析成果关联到新的分析需求并快速完成修改。 支持应用导出为模板功能 实现多个用户…

数论的第二舞——卡特兰数

当然了&#xff0c;虽然主角是卡特兰数&#xff0c;但是我们该学的数论还是不能落下的&#xff0c;首先先来介绍一个开胃小菜线性筛 1.积性函数&#xff1a; 2.线性筛 线性筛的筛选素数的时间复杂度更低&#xff0c;可以达到O(n)的时间复杂度 将每一轮进行筛选的数 n 表示…