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序列化:

 

 本次自定义协议主要体现在:

  • 对报文格式的整体设计。
  • 构建合理的请求和响应。
  • 如何将报头和有效载荷分离。
  • 如何读取完整的报文。
  • 如何将序列化和反序列化。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Proteus仿真】【Arduino单片机】简易计算器设计

文章目录 一、功能简介二、软件设计三、实验现象联系作者 一、功能简介 本项目使用Proteus8仿真Arduino单片机控制器&#xff0c;使用PCF8574、LCD1602液晶、4*4矩阵键盘等。 主要功能&#xff1a; 系统运行后&#xff0c;操作矩阵按键可实现简单四则运算。 二、软件设计 /* …

链表题(3)

链表题 正文开始前给大家推荐个网站&#xff0c;前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站。 本篇内容继续给大家带来链表的一些练习题 链表分割 知识点&#xff1a; 编程基础 链表…

MUYUCMS v2.1:一款开源、轻量级的内容管理系统基于Thinkphp开发

MuYuCMS&#xff1a;一款基于Thinkphp开发的轻量级开源内容管理系统&#xff0c;为企业、个人站长提供快速建站解决方案。它具有以下的环境要求&#xff1a; 支持系统&#xff1a;Windows/Linux/Mac WEB服务器&#xff1a;Apache/Nginx/ISS PHP版本&#xff1a;php > 5.6 (…

Vue3问题:如何实现页面引导提示?

前端功能问题系列文章&#xff0c;点击上方合集↑ 序言 大家好&#xff0c;我是大澈&#xff01; 本文约1700字&#xff0c;整篇阅读大约需要3分钟。 本文主要内容分三部分&#xff0c;第一部分是需求分析&#xff0c;第二部分是实现步骤&#xff0c;第三部分是问题详解。 …

【数据结构】非递归实现二叉树的前 + 中 + 后 + 层序遍历(听说面试会考?)

&#x1f466;个人主页&#xff1a;Weraphael ✍&#x1f3fb;作者简介&#xff1a;目前学习C和算法 ✈️专栏&#xff1a;数据结构 &#x1f40b; 希望大家多多支持&#xff0c;咱一起进步&#xff01;&#x1f601; 如果文章对你有帮助的话 欢迎 评论&#x1f4ac; 点赞&…

第2关:还原键盘输入(list)

题目&#xff1a; 知识点&#xff1a; 列表list相较于数组&#xff1a; 优势&#xff1a;可在任意指定位置插入或者删除元素而不影响列表其他地方 。 劣势&#xff1a;无法直接进行下标索引&#xff0c;需要迭代器it逐个遍历。 代码&#xff1a; #include <iostream>…

Mysql学习笔记--基础

一&#xff0c;SQL最重要的增删改命令格式 1&#xff0c;insert into 表名&#xff08;不写这个括号里面的内容就默认所有字段都要添加&#xff09; values&#xff08;&#xff09; 插入单条数据 2&#xff0c;insert into 表名 (里面是列名) values&#xff08;根据列名依次…

Git之分支与版本->课程目标及知识点的应用场景,分支的场景应用,标签的场景应用

1.课程目标及知识点的应用场景 Git分支和标签的命名规范 分支 dev/test/pre/pro(即master) dev:开发环境--windows (自己的电脑) test:测试环境--windows/linux (公司专门的测试电脑 pre:灰度环境(非常大的公司非常重要的项目) pro:正式环境 灰度环境与正式环境的服务器配置…

Azure 机器学习 - 使用受保护工作区时的网络流量流

目录 环境准备入站和出站要求方案&#xff1a;从工作室访问工作区方案&#xff1a;从工作室使用 AutoML、设计器、数据集和数据存储方案&#xff1a;使用计算实例和计算群集方案&#xff1a;使用联机终结点入站通信出站通信 方案&#xff1a;使用 Azure Kubernetes 服务方案&am…

数据结构线性表——栈

前言&#xff1a;哈喽小伙伴们&#xff0c;今天我们将一起进入数据结构线性表的第四篇章——栈的讲解&#xff0c;栈还是比较简单的哦&#xff0c;跟紧博主的思路&#xff0c;不要掉队哦。 目录 一.什么是栈 二.如何实现栈 三.栈的实现 栈的初始化 四.栈的操作 1.数据入栈…

Kafka中遇到的错误:

1、原因&#xff1a;kafka是一个去中心化结果的&#xff0c;所以在启动Kafka的时候&#xff0c;每一个节点上都需要启动。 启动的命令&#xff1a;kafka-server-start.sh -daemon /usr/local/soft/kafka_2.11-1.0.0/config/server.properties

力扣刷题-二叉树-二叉树的层序遍历(相关题目总结)

思路 层序遍历一个二叉树。就是从左到右一层一层的去遍历二叉树。这种遍历的方式和我们之前讲过的都不太一样。 需要借用一个辅助数据结构即队列来实现&#xff0c;队列先进先出&#xff0c;符合一层一层遍历的逻辑&#xff0c;而用栈先进后出适合模拟深度优先遍历也就是递归的…

【ruoyi】微服务关闭登录验证码

登录本地的nacos服务&#xff0c;修改&#xff1a;配置管理-配置列表-ruoyi-gateway-dev.yml 将验证码的enabled设置成false&#xff0c;即可

5+干湿结合的佳作,可另外添加分析升级

今天给同学们分享一篇生信文章“PCTAIRE Protein Kinase 1 (PCTK1) Suppresses Proliferation, Stemness,and Chemoresistance in Colorectal Cancer through the BMPR1B-Smad1/5/8 Signaling Pathway”&#xff0c;这篇文章发表在Int J Mol Sci期刊上&#xff0c;影响因子为5.…

计算机网络:概述

0 学时安排及讨论题目 0.1讨论题目&#xff1a; CSMA/CD协议交换机基本原理ARP协议及其安全子网划分IP分片路由选择算法网络地址转换NATTCP连接建立和释放再论网络体系结构 0.2 本节主要内容 计算机网络在信息时代中的作用 互联网概述 互联网的组成 计算机网络在我国的发展 …

2024 款:最新前端技术趋势

Hello&#xff0c;大家好&#xff0c;我是 Sunday。 上一次的时候聊了 那么些已经落后的前端开发技术 。但是光知道什么技术落后了是不够的&#xff0c;咱们还得知道 前端最新的技术趋势是什么。所以&#xff0c;今天这篇文章&#xff0c;咱们就来聊一聊&#xff0c;2023 最新…

Android T 实现简易的 USB Mode Select 需求

Android T 实现 USB Mode Select 需求 一、实现效果 二、主要实现思路 在手机连接 USB 发生/取消通知的同时&#xff0c;控制弹窗 Dialog 的显示/消失。 三、主要代码实现 连接 USB 发送/取消的主要实现是在 UsbDeviceManager.java 类中。类路径如下&#xff1a; system/f…

SAP实现文本框多行输入(类cl_gui_textedit)

参考文章&#xff1a;https://blog.csdn.net/SAPmatinal/article/details/130882962 先看效果&#xff0c;在输入框先来一段《赤壁赋》 然后点击 ‘保存输出’按钮&#xff0c;就能把输入内容从表里读取并输出来 源代码&#xff1a; *&-------------------------------…

【云备份项目总结】客户端篇

项目总结 整体回顾util.hppdata.hppcloud.hpp 代码 客户端的代码与服务端的代码实现有很多相似之处&#xff0c;我们也只编写一个简单的客户端代码。 整体回顾 客户端要实现的功能是&#xff1a;对指定文件夹中的文件自动进行备份上传。但是并不是所有的文件每次都需要上传&am…

css style、css color 转 UIColor

你能看过来&#xff0c;就说明这个问题很好玩&#xff01;IT开发是一个兴趣&#xff0c;更是一个挑战&#xff01;兴趣使你工作有热情。挑战使让你工作充满刺激拉满的状态&#xff01;我们日复一日年复一年的去撸代码&#xff0c;那些普普通通的功能代码&#xff0c;已经厌倦了…