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…...
浅谈 React Hooks
React Hooks 是 React 16.8 引入的一组 API,用于在函数组件中使用 state 和其他 React 特性(例如生命周期方法、context 等)。Hooks 通过简洁的函数接口,解决了状态与 UI 的高度解耦,通过函数式编程范式实现更灵活 Rea…...
变量 varablie 声明- Rust 变量 let mut 声明与 C/C++ 变量声明对比分析
一、变量声明设计:let 与 mut 的哲学解析 Rust 采用 let 声明变量并通过 mut 显式标记可变性,这种设计体现了语言的核心哲学。以下是深度解析: 1.1 设计理念剖析 安全优先原则:默认不可变强制开发者明确声明意图 let x 5; …...

【Oracle APEX开发小技巧12】
有如下需求: 有一个问题反馈页面,要实现在apex页面展示能直观看到反馈时间超过7天未处理的数据,方便管理员及时处理反馈。 我的方法:直接将逻辑写在SQL中,这样可以直接在页面展示 完整代码: SELECTSF.FE…...
在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能
下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能,包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...

基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容
基于 UniApp + WebSocket实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...
Python爬虫实战:研究feedparser库相关技术
1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...

Linux相关概念和易错知识点(42)(TCP的连接管理、可靠性、面临复杂网络的处理)
目录 1.TCP的连接管理机制(1)三次握手①握手过程②对握手过程的理解 (2)四次挥手(3)握手和挥手的触发(4)状态切换①挥手过程中状态的切换②握手过程中状态的切换 2.TCP的可靠性&…...

为什么需要建设工程项目管理?工程项目管理有哪些亮点功能?
在建筑行业,项目管理的重要性不言而喻。随着工程规模的扩大、技术复杂度的提升,传统的管理模式已经难以满足现代工程的需求。过去,许多企业依赖手工记录、口头沟通和分散的信息管理,导致效率低下、成本失控、风险频发。例如&#…...

蓝桥杯3498 01串的熵
问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798, 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...
laravel8+vue3.0+element-plus搭建方法
创建 laravel8 项目 composer create-project --prefer-dist laravel/laravel laravel8 8.* 安装 laravel/ui composer require laravel/ui 修改 package.json 文件 "devDependencies": {"vue/compiler-sfc": "^3.0.7","axios": …...