Linux网络——自定义协议
目录
一.什么是协议
二.协议与报文
三.自定义协议
1.封装套接字
2.构建请求与响应
3.序列化和反序列化
4.报头添加和去除
5.报文读取
四.服务器端程序
五.客户端程序
一.什么是协议
协议在生活中泛指:双方或多方为了完成某项任务或达成某种目的而制定的共同遵守的规定、标准或约定。
在计算机网络中:就是一种约定,约定了通信的双方,怎么发数据,怎么读数据,双方使用早就已经约定好的方式来进行数据的通信,这种早已经约定好的方式,就是一种协议。
协议主要作用是定义了在两个或多个通信实体之间交换的报文的格式和顺序,以及报文发送或接受一条报文或其他事件所采取的动作。
面对不同的场景,通信的方式自然也是不同的,在双方的面对不同的场景做出的约定自然也是不同的。所以在计算机网络中,协议的种类非常多,且是以层状的结构展现。
二.协议与报文
报文是网络中交换与传输的数据单元,包含了将要发送的完整的数据信息。因此可以看出,协议主要规定了如何进行通信和数据传输的规则,而报文则是这些规则下实际传输的数据内容,协议也决定了报文的格式和顺序等特性。
简单来说,协议是一种类型,报文就是这种类型下的对象。
下图每一个传输的都是一个报文,报文格式不同,因为所处的协议不同。
报文整体格式:
报头:包含了该报文的元数据,例如源地址、目标地址、长度等信息。
有效载荷:这是实际的数据内容,可能是一个文件、一个数据库记录,或者其他任何类型的数据。
三.自定义协议
今天我们自己定义的协议的隶属于,传输层之上的应用层协议。我们将完成对协议的请求报文,响应报文的构建。以及如何,设计添加报头和去除报头,对报文的序列化和反序列化。
今天我们实现的服务器功能是计算器的功能。
报文格式:
1.封装套接字
关于套接字上一篇已经有详细的说明,这里不多介绍。
Sock.hpp
#pragma once
#include <iostream>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "Log.hpp"
#define TCP SOCK_STREAM
#define UDP SOCK_DGRAM
const static int backlog = 32;enum
{SOCK_ERR = 10,BING_ERR,LISTEN_ERR,CONNECT_ERR
};class Udp
{
public:Udp(int SOCK){_listensock = socket(AF_INET, SOCK, 0);if (_listensock == -1){Logmessage(Fatal, "socket err ,error code %d,%s", errno, strerror(errno));exit(SOCK_ERR);}}Udp(uint16_t port, int SOCK): _port(port){_listensock = socket(AF_INET, SOCK, 0);if (_listensock == -1){Logmessage(Fatal, "socket err ,error code %d,%s", errno, strerror(errno));exit(10);}}void Bind(){struct sockaddr_in host;host.sin_family = AF_INET;host.sin_port = htons(_port);host.sin_addr.s_addr = INADDR_ANY; // #define INADDR_ANY 0x00000000socklen_t hostlen = sizeof(host);int n = bind(_listensock, (struct sockaddr *)&host, hostlen);if (n == -1){Logmessage(Fatal, "bind err ,error code %d,%s", errno, strerror(errno));exit(BING_ERR);}}int FD(){return _listensock;}~Udp(){close(_listensock);}protected:int _listensock;uint16_t _port;
};class Tcp : public Udp
{
public:Tcp(uint16_t port): Udp(port, TCP){}Tcp(): Udp(TCP){}void Listen(){int n = listen(_listensock, backlog);if (n == -1){Logmessage(Fatal, "listen err ,error code %d,%s", errno, strerror(errno));exit(LISTEN_ERR);}}int Accept(string *clientip, uint16_t *clientport){struct sockaddr_in client;socklen_t clientlen;int sock = accept(_listensock, (struct sockaddr *)&client, &clientlen);if (sock < 0){Logmessage(Warning, "bind err ,error code %d,%s", errno, strerror(errno));}else{*clientip = inet_ntoa(client.sin_addr);*clientport = ntohs(client.sin_port);}return sock;}void Connect(string ip, uint16_t port){struct sockaddr_in server;memset(&server, 0, sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(port);server.sin_addr.s_addr = inet_addr(ip.c_str());socklen_t hostlen = sizeof(server);int n = connect(_listensock, (struct sockaddr *)&server, hostlen);if (n == -1){Logmessage(Fatal, "Connect err ,error code %d,%s", errno, strerror(errno));exit(CONNECT_ERR);}}~Tcp(){}
};
2.构建请求与响应
请求:有待计算的数据,和运算符,以及对应将请求序列化,反序列化的函数。
class Request
{
public:Request(){}Request(int x, int y, char op): _x(x), _y(y), _op(op){}// 序列化std::string serialize(){}// 反序列化"123+321"void deserialize(const std::string &str){}public:int _x;int _y;char _op;
};
响应:有请求的计算结果,和退出码(标识计算结果的正确性),以及对应的序列化,和反序列化函数。
class Responce
{
public:Responce(int result, int code): _result(result), _code(code){}Responce(){}// 序列化std::string serialize(){}// 反序列化void deserialize(const std::string &str){}public:int _result;int _code;
};
3.序列化和反序列化
序列化:将某种结构化的数据,变为方便传输的序列,可以是字符串,也可以是二进制字节流。
反序列化:将序列化的数据,变为结构化的数据。
序列化和反序列化这个工作我们可以自己做也可以使用第三放工具——json。
json:头文件<jsoncpp/json/json.h>
请求序列化和反序列化:
//序列化
std::string serialize(){
#ifdef MYSELFstring strresult = to_string(_result);string strcode = to_string(_code);return strresult + SEP + strcode;
#else// 使用json序列化Json::Value root;root["result"] = _result;root["code"] = _code;Json::StyledWriter writer;return writer.write(root);
#endif}// 反序列化void deserialize(const std::string &str){
#ifdef MYSELFstring strresult;string strcode;bool isleft = 1;for (auto e : str){if (e >= '0' && e <= '9' && isleft){strresult += e;}else if (e <= '0' || e >= '9'){isleft = 0;}else if (e >= '0' && e <= '9' && !isleft){strcode += e;}}_result = atoi(strresult.c_str());_code = atoi(strcode.c_str());
#else// 使用json反序列化Json::Value root;Json::Reader reader; // Reader: 用来进行反序列化的reader.parse(str, root);_result = root["result"].asInt();_code = root["code"].asInt();
#endif}
响应序列化和反序列化:
// 序列化std::string serialize(){
#ifdef MYSELFstring strresult = to_string(_result);string strcode = to_string(_code);return strresult + SEP + strcode;#else// 使用json序列化Json::Value root;root["result"] = _result;root["code"] = _code;Json::StyledWriter writer;return writer.write(root);#endif}// 反序列化void deserialize(const std::string &str){
#ifdef MYSELFstring strresult;string strcode;bool isleft = 1;for (auto e : str){if (e >= '0' && e <= '9' && isleft){strresult += e;}else if (e <= '0' || e >= '9'){isleft = 0;}else if (e >= '0' && e <= '9' && !isleft){strcode += e;}}_result = atoi(strresult.c_str());_code = atoi(strcode.c_str());
#else// 使用json反序列化Json::Value root;Json::Reader reader; // Reader: 用来进行反序列化的reader.parse(str, root);_result = root["result"].asInt();_code = root["code"].asInt();
#endif}
4.报头添加和去除
在对报头部分我们添加有效载荷的长度,和固定的分隔符。以便将报头和有效载荷分离。
#define SEP "\r\n"
#define SEPLEN strlen(SEP)// str:报文;len:有效载荷的长度
std::string Rehead(std::string str, int len)
{return str.substr(str.length() - 2 - len, len);
}// 报文=报头+有效载荷——————"有效载荷长度"\r\n"有效载荷"\r\n
std::string Addhead(std::string str)
{std::string len = to_string(str.length());//"7\r\n123+123\r\n"return len + SEP + str + SEP;
}
5.报文读取
判断是否读取到一个完整的报文,只有读取到一个完整的报文,才将报文输出。
int ReadFormat(int fd, std::string &inputstr, std::string *target)
{// 从流中读取—————— "7"+\n\r+"123+321"+\n\rchar buff[1024];int n = recv(fd, buff, sizeof(buff), 0);if (n < 0){return n;}string readstr = buff;// cout << readstr << endl;// 解析判断读取的字符串是否完整// 尝试读取报头int pos = readstr.find(SEP, 0);if (pos == std::string::npos) // 没有找到分割"\n\r"return 0;// 找到报头分隔符,提取报头—————得到有效载荷的长度string headstr = readstr.substr(0, pos);int len = atoi(headstr.c_str());// 计算出整个报文应该有的长度——————报头+分割符+有效载荷int formatlen = headstr.length() + len + 2 * SEPLEN;if (readstr.length() < formatlen) // 读取的报文长度小于报文应该有的长度,没有读取完整return 0;// 读取到一个完整的报文了*target = readstr.substr(0, formatlen);inputstr.erase(0, formatlen);// cout << *target << endl;return len;
}
四.服务器端程序
服务器程序采用多线程处理请求的方式执行。
#pragma once
#include <iostream>
#include <string>
#include <pthread.h>
#include <functional>
#include "Sock.hpp"
#include "Protocol.hpp"using fun_t = std::function<Responce(const Request &)>;//处理函数
class server;
//线程函数参数
struct Args
{Args(server *ser, string ip, uint16_t port, int fd): _ip(ip), _port(port), _pserver(ser), _fd(fd){}int _fd;uint16_t _port;string _ip;server *_pserver;
};class server
{
public:server(fun_t func, int port): _func(func){tcp = new Tcp(port);tcp->Bind();tcp->Listen();cout << "服务器创建成功" << endl;}void start(){while (1){string clientip;uint16_t clientport;cout << "start accept" << endl;int sock = tcp->Accept(&clientip, &clientport);cout << "get a new connect" << endl;// 多线程处理请求pthread_t t;Args *args = new Args(this, clientip, clientport, sock);pthread_create(&t, nullptr, ThreadRun, args);}}~server(){delete tcp;}private:static void *ThreadRun(void *args){pthread_detach(pthread_self());Args *ts = static_cast<Args *>(args);ts->_pserver->serverIO(ts->_fd);delete ts;return nullptr;}void serverIO(int fd){}private:Tcp *tcp;fun_t _func;
};
服务器处理读取请求处理请求返回响应:
void serverIO(int fd){// 由于使用tcp面向数据流传输数据,所以我们并不能知道我们读取的是不是一个完整的报文。// 1.读取一个完整的请求报文string inputstr;string message;while (1){int len = ReadFormat(fd, inputstr, &message);if (len == 0) // 读取的不是完整报文,继续读取continue;if (len < 0) // 读取出错{break;}cout << "得到一个完整的报文:" << message << endl;// 2.去除报头——将报头和有效载荷分离message = Rehead(message, len);cout << "去除报头:" << message << endl;// 3.有效载荷反序列化Request request;request.deserialize(message);cout << "有效载荷反序列化后" << request._x << " " << request._op << " " << request._y << endl;// 4.处理业务逻辑Responce responce = _func(request);cout << "响应序列化前" << responce._result << ":" << responce._code << endl;// 5.有效载荷序列化message = responce.serialize();cout << "响应序列化后:" << message << endl;// 6.有效载荷添加报头message = Addhead(message);cout << "响应添加报头后:" << message << endl;// 7.发送响应send(fd, message.c_str(), message.length(), 0);}close(fd);}
服务器主调用逻辑:
// 请求处理函数,返回响应
Responce calculate(Request request)
{int result;int exitcode;switch (request._op){case '+':result = request._x + request._y;exitcode = 0;break;case '-':result = request._x - request._y;exitcode = 0;break;case '*':result = request._x * request._y;exitcode = 0;break;case '/':if (request._y == 0)exitcode = 1;else{result = request._x / request._y;exitcode = 0;}break;case '%':if (request._y == 0)exitcode = 2;else{result = request._x % request._y;exitcode = 0;}break;default:break;}return Responce(result, exitcode);
}int main()
{server Server(calculate, 8081);Server.start();return 0;
}
五.客户端程序
#include <iostream>
#include "Protocol.hpp"
#include "Sock.hpp"static void usage(std::string proc)
{std::cout << "Usage:\n\t" << proc << " serverip serverport\n"<< std::endl;
}int main(int argc, char *argv[])
{if (argc != 3){usage(argv[0]);exit(0);}std::string serverip = argv[1];uint16_t serverport = atoi(argv[2]);Tcp tcp;tcp.Connect(serverip, serverport);while (1){// 构建一个请求int x;int y;char op;cout << "Input operand 1: ";cin >> x;cout << "Input operand 2: ";cin >> y;cout << "Input operand op: ";cin >> op;// 1.构建请求Request request(x, y, op);// 2.有效载荷序列化string message = request.serialize();// 3.添加报头message = Addhead(message);// 4.发送给服务器send(tcp.FD(), message.c_str(), message.length(), 0);int formatlen = 0;string input;string target;// 1.构建响应Responce respon;while (1){// 2.读取响应int formatlen = ReadFormat(tcp.FD(), input, &target);if (formatlen == 0)continue;if (formatlen < 0)break;if (formatlen > 0){// 读取到一个完整的报文cout << "读取到一个完整的报文:" << target << endl;// 3.去报头string format = Rehead(target, formatlen);cout << "报文去报头后:" << format << endl;// 4.有效载荷反序列化respon.deserialize(format);cout << "反序列化:" << respon._result << ":" << respon._result << endl;break;}}cout << "Result :" << respon._result << ",Exit code:" << respon._code << endl;}return 0;
}
效果展示:
自己序列化:
使用json序列化:
本次自定义协议主要体现在:
- 对报文格式的整体设计。
- 构建合理的请求和响应。
- 如何将报头和有效载荷分离。
- 如何读取完整的报文。
- 如何将序列化和反序列化。
相关文章:

Linux网络——自定义协议
目录 一.什么是协议 二.协议与报文 三.自定义协议 1.封装套接字 2.构建请求与响应 3.序列化和反序列化 4.报头添加和去除 5.报文读取 四.服务器端程序 五.客户端程序 一.什么是协议 协议在生活中泛指:双方或多方为了完成某项任务或达成某种目的而制定的共…...

【OpenCV实现图像:用OpenCV图像处理技巧之巧用直方图】
文章目录 概要前置条件统计数据分析直方图均衡化原理小结 概要 图像处理是计算机视觉领域中的重要组成部分,而直方图在图像处理中扮演着关键的角色。如何巧妙地运用OpenCV库中的图像处理技巧,特别是直方图相关的方法,来提高图像质量、改善细…...

【Android】画面卡顿优化列表流畅度四之Glide几个常用参数设置
好像是一年前快两年了,笔者解析过glide的源码,也是因为觉得自己熟悉一些,也就没太关注过项目里glide的具体使用对当前业务的影响;主要是自负,还有就是真没有碰到过这样的数据加载情况。暴露了经验还是不太足够 有兴趣的…...
js控制手机蓝牙
要使用JavaScript控制手机蓝牙,您需要使用Web Bluetooth API。这是一种新的Web API,可以让Web应用程序访问和控制蓝牙设备。 以下是一些步骤,以便您开始使用Web Bluetooth API: 检查浏览器支持:首先,您需要…...
C++11 原始字符串字面量R“()“
原始字符串字面量(Raw String Literals) R"()"是C11引入的一项特性,它允许创建不需要转义字符的字符串字面量。字符串中包含特殊字符、换行符和其他转义字符时,不需要反斜杠转义它们。 原始(Raw):不用使用反…...
【Vue原理解析】之虚拟DOM
Vue.js是一款流行的JavaScript框架,它采用了虚拟DOM(Virtual DOM)的概念来提高性能和开发效率。虚拟DOM是Vue.js的核心之一,它通过在内存中构建一个轻量级的DOM树来代替直接操作真实的DOM,从而减少了对真实DOM的操作次…...

HCIE-灾备技术和安全服务
灾备技术 灾备包含两个概念:容灾、备份 备份是为了保证数据的完整性,数据不丢失。全量备份、增量备份,备份数据还原。 容灾是为了保证业务的连续性,尽可能不断业务。 快照:保存的不是底层块数据,保存的是逻…...
【图论实战】Boost学习 01:基本操作
文章目录 头文件图的构建图的可视化基本操作 头文件 #include <boost/graph/adjacency_list.hpp> #include <boost/graph/graphviz.hpp> #include <boost/graph/properties.hpp> #include <boost/property_map/property_map.hpp> #include <boost/…...

Rust 中的引用与借用
目录 1、引用与借用 1.1 可变引用 1.2 悬垂引用 1.3 引用的规则 2、slice 类型 2.1 字符串字面量其实就是一个slice 2.2 总结 1、引用与借用 在之前我们将String 类型的值返回给调用函数,这样会导致这个String会被移动到函数中,这样在原来的作用域…...

Azure 机器学习:在 Azure 机器学习中使用 Azure OpenAI 模型
目录 一、环境准备二、Azure 机器学习中的 OpenAI 模型是什么?三、在机器学习中访问 Azure OpenAI 模型连接到 Azure OpenAI部署 Azure OpenAI 模型 四、使用自己的训练数据微调 Azure OpenAI 模型使用工作室微调微调设置训练数据自定义微调参数部署微调的模型 使用…...

XML Web 服务 Eclipse实现中的sun-jaxws.xml文件
说明 在sun-jaxws.xml文件,可以配置endpoint、handler-chain等内容。在这个文件中配置的内容会覆盖在Java代码中使用注解属性配置的的内容。 这个文件根据自己的项目内容修改完成以后,作为web应用的一部分部署到web容器中(放到web应用的WEB…...

16.1 二次根式 教学设计及课堂检测设计
课堂检测如下:...
Android数据流的狂欢:Channel与Flow
在 Android 应用程序的开发中,处理异步数据流是一个常见的需求。为了更好地应对这些需求,Kotlin 协程引入了 Channel 和 Flow,它们提供了强大的工具来处理数据流,实现生产者-消费者模式,以及构建响应式应用程序。 本文…...
Java 单元测试最佳实践:如何充分利用测试自动化
单元测试是众所周知的做法,但还有很大的改进空间!在这篇文章中,我们讨论最有效的单元测试最佳实践,包括在此过程中最大化自动化工具的方法。我们还将讨论代码覆盖率、模拟依赖关系和整体测试策略。 什么是单元测试? 单…...

windows系统用于 SDN 的软件负载均衡器 (SLB)
适用于:Azure Stack HCI 版本 22H2 和 21H2;Windows Server 2022、Windows Server 2019、Windows Server 2016 软件负载均衡器包括哪些内容? 软件负载均衡器提供以下功能: 适用于北/南和东/西 TCP/UDP 流量的第 4 层 (L4) 负载均…...

漏洞复现--IP-guard flexpaper RCE
免责声明: 文章中涉及的漏洞均已修复,敏感信息均已做打码处理,文章仅做经验分享用途,切勿当真,未授权的攻击属于非法行为!文章中敏感信息均已做多层打马处理。传播、利用本文章所提供的信息而造成的任何直…...

Electron-vue出现GET http://localhost:9080/__webpack_hmr net::ERR_ABORTED解决方案
GET http://localhost:9080/__webpack_hmr net::ERR_ABORTED解决方案 使用版本解决方案解决总结 使用版本 以下是我解决此问题时使用的electron和vue等的一些版本信息 【附】经过测试 electron 的版本为 13.1.4 时也能解决 解决方案 将项目下的 .electron-vue/dev-runner.js…...

Linux---(六)自动化构建工具 make/Makefile
文章目录 一、make/Makefile二、快速查看(1)建立Makefile文件(2)编辑Makefile文件(3)解释(4)效果展示 三、背后的基本知识、原理(1)如何清理对应的临时文件呢…...
谷歌:编写干净的代码以减少认知负荷
您是否曾经阅读过代码却发现很难理解?您可能正在经历认知负荷! 认知负荷是指完成一项任务所需的脑力劳动量。阅读代码时,您必须记住变量值、条件逻辑、循环索引、数据结构状态和接口契约等信息。随着代码变得更加复杂,认知负荷也…...

微信小程序display常用属性和子元素排列方式介绍
wxss中display常用显示属性与css一致,介绍如下: 针对元素本身显示的属性: displayblock,元素显示换行displayinline,元素显示换行,但不可设置固定的宽度和高度,也不可设置上下方向的margin和p…...
RestClient
什么是RestClient RestClient 是 Elasticsearch 官方提供的 Java 低级 REST 客户端,它允许HTTP与Elasticsearch 集群通信,而无需处理 JSON 序列化/反序列化等底层细节。它是 Elasticsearch Java API 客户端的基础。 RestClient 主要特点 轻量级ÿ…...

遍历 Map 类型集合的方法汇总
1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...
【Go】3、Go语言进阶与依赖管理
前言 本系列文章参考自稀土掘金上的 【字节内部课】公开课,做自我学习总结整理。 Go语言并发编程 Go语言原生支持并发编程,它的核心机制是 Goroutine 协程、Channel 通道,并基于CSP(Communicating Sequential Processes࿰…...

AI病理诊断七剑下天山,医疗未来触手可及
一、病理诊断困局:刀尖上的医学艺术 1.1 金标准背后的隐痛 病理诊断被誉为"诊断的诊断",医生需通过显微镜观察组织切片,在细胞迷宫中捕捉癌变信号。某省病理质控报告显示,基层医院误诊率达12%-15%,专家会诊…...

Mysql中select查询语句的执行过程
目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析(Parser) 2.4、执行sql 1. 预处理(Preprocessor) 2. 查询优化器(Optimizer) 3. 执行器…...
return this;返回的是谁
一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请,不同级别的经理有不同的审批权限: // 抽象处理者:审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...
Webpack性能优化:构建速度与体积优化策略
一、构建速度优化 1、升级Webpack和Node.js 优化效果:Webpack 4比Webpack 3构建时间降低60%-98%。原因: V8引擎优化(for of替代forEach、Map/Set替代Object)。默认使用更快的md4哈希算法。AST直接从Loa…...
React从基础入门到高级实战:React 实战项目 - 项目五:微前端与模块化架构
React 实战项目:微前端与模块化架构 欢迎来到 React 开发教程专栏 的第 30 篇!在前 29 篇文章中,我们从 React 的基础概念逐步深入到高级技巧,涵盖了组件设计、状态管理、路由配置、性能优化和企业级应用等核心内容。这一次&…...
【免杀】C2免杀技术(十五)shellcode混淆uuid/ipv6/mac
针对 shellcode 混淆(Shellcode Obfuscation) 的实战手段还有很多,如下表所示: 类型举例目的编码 / 加密XOR、AES、RC4、Base64、Poly1305、UUID、IP/MAC改变字节特征,避开静态签名或 YARA结构伪装PE Stub、GIF/PNG 嵌入、RTF OLE、UUID、IP/MAC看起来像合法文件/数据,弱…...

CSS(2)
文章目录 Emmet语法快速生成HTML结构语法 Snipaste快速生成CSS样式语法快速格式化代码 快捷键(VScode)CSS 的复合选择器什么是复合选择器交集选择器后代选择器(重要)子选择器(重要)并集选择器(重要)**链接伪类选择器**focus伪类选…...