Linux学习之自定义协议

前言:

首先对于Tcp的socket的通信,我们已经大概了解了,但是其中其实是由一个小问题,我们目前是不得而知得,在我们客户端与服务端连接成功后,服务端读取来自客户端得消息,我们使用的是函数read,通过来读取accept之后的文件,从而获取信息,可是我们怎么去知道每次读取都是一个完成的报文呢?

在用户端与服务端通信的时候,用户首先写信息到发送缓冲区当中,经过网络推送到接收缓冲区,之后服务端从接收缓冲区中读取数据。在这个过程中,其实都是有Tcp自主控制的,这也就是他为什么叫传输控制协议,tcp会处理在发送过程中所有遇到的问题:发什么,什么时候发,出错了怎么办?这里我们用到的接口,如write,read,accept等都是实现用户到内核的数据拷贝。

如何保证,数据的发送是准确无误的,这就取决于tcp,而tcp怎么保证?这就需要协议的定制。

目录

前言:

协议的定制

套接字文件

protocol.hpp

Calcultor.hpp

客户端:

服务端:

主运行函数:


协议的定制

协议是一种约定,在进行socket通信时,读写都是用字符串,那么如果要传输的数据是结构化数据呢?

我们以接受发消息为例:

我们平常发的消息,不仅仅只有我们的消息的内容,其实还有时间,名字,和消息。

一般,用户发出消息,系统会将该消息写到一个固定的结构体里,在将该结构化数据转化为字符串,再通过网络发送出去,接受的时候,还是需要将字符串先转化为结构数据从,之后再访问其中成员获取消息给用户。

数据的结构化的过程我们可以简单的理解为协议的封装。

现在我们就通过编写一个网络计算机为例,将所有的知识结合起来:

套接字文件

首先是套接字文件,里面包含了客户端与服务端需要直接调用的方法,例如创建套接字,绑定,监听,连接,接受等。

//网络接口
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<unistd.h>
#include <string.h>
#include<strings.h>const int backlog=10;
const int defaultfd=-1;
enum 
{CREATEERROR=1,BINDERROR=2,LISTENERROR=3,ACCEPTERROR=4
};
class Sock
{public:Sock(const int socket=defaultfd):_sockfd(){}void Createsockfd(){//1.创建套接字文件描述符_sockfd=socket(AF_INET,SOCK_STREAM,0);if(_sockfd<0){std::cout<<"创建失败"<<",错误码:"<<errno<<",错误信息"<<strerror(errno)<<std::endl;exit(CREATEERROR);}std::cout<<"创建成功"<<std::endl;}void Bind(uint16_t port){//初始化sockaddr_instruct sockaddr_in local;bzero(&local, sizeof(local));local.sin_family=AF_INET;local.sin_port=htons(port);local.sin_addr.s_addr=INADDR_ANY;   //2.绑定端口号socklen_t len=sizeof(local);int n=bind(_sockfd,(const struct sockaddr*)&local,len);if(n<0){std::cout<<"绑定失败"<<",错误码:"<<errno<<",错误信息"<<strerror(errno)<<std::endl;exit(BINDERROR);}std::cout<<"端口号绑定成功"<<std::endl;}void Listen(){//3.将套接字设置为监听状态int l=listen(_sockfd,backlog);if(l<0){std::cout<<"监听失败"<<",错误码:"<<errno<<",错误信息"<<strerror(errno)<<std::endl;exit(LISTENERROR);}std::cout<<"设置监听成功"<<std::endl;}            void Connect(std::string &ip,uint16_t &port){//我去连接哪一个服务端//获取服务端信息struct sockaddr_in remote;bzero(&remote, sizeof(remote));remote.sin_family=AF_INET;remote.sin_port=htons(port);inet_pton(AF_INET, ip.c_str(),&(remote.sin_addr));//建立连接socklen_t len=sizeof(remote);int n=connect(_sockfd,(const struct sockaddr*)&remote,len);if(n<0){std::cout<<"连接失败"<<",错误码:"<<errno<<",错误信息"<<strerror(errno)<<std::endl;exit(ACCEPTERROR);}std::cout<<"连接成功"<<std::endl;}int Accept(std::string *clientip,uint16_t* clientport){struct sockaddr_in client;socklen_t clientlen=sizeof(client);std::cout<<"测试1。。。。。。"<<std::endl;int newfd=accept(_sockfd,(struct sockaddr*)&client,&clientlen);if(newfd<0){std::cout<<"接收失败"<<",错误码:"<<errno<<",错误信息"<<strerror(errno)<<std::endl;return -1;}std::cout<<"测试2。。。。。。"<<std::endl;//获取ip地址与端口号char _ptr[32];inet_ntop(AF_INET, &client.sin_addr,_ptr,sizeof(_ptr));*clientip=std::string(_ptr);*clientport=ntohs(client.sin_port);std::cout<<"接收成功"<<std::endl;return newfd;}int Fd(){return _sockfd;}~Sock(){}void Close(){//孙子进程提供服务close(_sockfd);}private:int _sockfd;
};

我们来看看协议封装的头文件:

protocol.hpp

主要是序列化与反序列化

#pragma once
#include <iostream>
#include <string>
using namespace std;
const std::string blankspace = " ";
const string protocol_str = "/n";
//这里我们计算的字符串 10 * 10 会被Encode为 5/n10 * 10/n
std::string Encode(std::string &content)//编码格式为 len/nx op y/n
{std::string package = std::to_string(content.size());package += protocol_str;package += content;package += protocol_str;return package;
}
//反之去掉/n 与长度 变为 x op y格式
bool Decode(std::string &package, std::string *content)
{std::size_t pos = package.find(protocol_str);if(pos == std::string::npos) return false;std::string len_str = package.substr(0, pos);std::size_t len = std::stoi(len_str);// package = len_str + content_str + 2std::size_t total_len = len_str.size() + len + 2;if(package.size() < total_len) return false;*content = package.substr(pos+1, len);// earse 移除报文 package.erase(0, total_len);package.erase(0, total_len);return true;
}// 定制协议,这里我们打算搞得是网络计算器
// 请求
class Request
{
public:Request(int data1, int data2,char c) : _x(data1), _y(data2),_op(c){}// 报文的读格式为 len/n a op bbool Serialize(string *out) // 序列化{// 转化为字符串// 构建报文的有效载荷std::string s = std::to_string(_x);s += blankspace;s += _op;s += blankspace;s += std::to_string(_y);*out = s;return true;//序列化  将已知的x,op,y进行第一层封装为 x op y 格式的字符串}bool Deserialize(const string &package) // 反序列化   已经定义好了是 len\nx op y{size_t pos=package.find(blankspace);if(pos==std::string::npos){return false;}std::string x=package.substr(0,pos);size_t pot=package.rfind(blankspace);if(pot==std::string::npos){return false;}std::string y=package.substr(pot+1);if(pos+2!=pot){return false;}_op=package[pos+1];_x=std::stoi(x.c_str());_y=std::stoi(y.c_str());//通过反序列化取得 字符 x,y,op 并转化相应类型}~Request(){}public:int _x;int _y;char _op; // + = * / %
};
// 应答
class Response
{
public:Response(int result, int code) : _result(result), _code(code){}~Response(){}// 序列化bool Serialize(string *out){//"len"\n"result code"// 构建有效的报文载荷std::string s = std::to_string(_result);s += blankspace;s += std::to_string(_code);*out = s;return true;}// 反序列化bool Deserialize(const std::string &in) // len\n result code {std::size_t pos = in.find(blankspace);if (pos == std::string::npos)return false;std::string part_left = in.substr(0, pos);std::string part_right = in.substr(pos+1);_result = std::stoi(part_left);_code = std::stoi(part_right);return true;}public:int _result;int _code; // 错误码,非0具体实际表明错误原因
};

之后根据我们初始化构造传入的参数,进行编码,传入到定制对象Request中,进行序列化,之后完成序列化,取得定制对象并传入 函数Calculator中进行计算,再根据返回的结果在进行Response对象的定制,序列化,再编码,作为报文字符串可以进行发了,服务端之后接受到到再解码,反序列化,即可。

计算转化头文件

Calcultor.hpp

主要是将传入的字符串,编码,序列化,之后调用计算,再解码,反序列化。实现计算过程。

#pragma once
#include<iostream>
#include"protocol.hpp"class ServerCal{public:ServerCal(){}Response CalculatorOperator(const Request &req){Response resp(0, 0);switch (req.op){case '+':resp.result = req.x + req.y;break;case '-':resp.result = req.x - req.y;break;case '*':resp.result = req.x * req.y;break;case '/':{if (req.y == 0)resp.code = Div_Zero;elseresp.result = req.x / req.y;}break;case '%':{if (req.y == 0)resp.code = Mod_Zero;elseresp.result = req.x % req.y;}break;default:resp.code = Other_Oper;break;}return resp;}std::string Calculator(std::string &package){//首先把内容转成报文格式std::string content;bool can=Decode(package,&content);//把传进来的字符解码 len/n x op yif(!can) return ;Request req;can=req.Deserialize(content);//反序列化 x op yif(!can)return ;content="";Response resp=CalculatorOperator(req);//进行计算req.x,req.y,req.opresp.Serialize(&content);//变为 result codecontent=Encode(content);//变为 len/n result codereturn content;}private:};

协议的定制就是约定,无论客户端还是服务端都要以这种方式来接受发报文,不符合该要求的是不会接收的。

可以看到序列与反序列化基本上都是字符串操作,每次这样写有点麻烦,实际上,再开发过程中,有现成的序列与反序列化的方法供我们使用,一般就是json,protobuf.

//json
bool Serialize(std::string *out){Json::Value root;root["x"] = x;root["y"] = y;root["op"] = op;// Json::FastWriter w;Json::StyledWriter w;*out = w.write(root);return true;}
bool Deserialize(const std::string &in) // "x op y"{Json::Value root;Json::Reader r;r.parse(in, root);x = root["x"].asInt();y = root["y"].asInt();op = root["op"].asInt();return true;}
bool Serialize(std::string *out){Json::Value root;root["result"] = result;root["code"] = code;// Json::FastWriter w;Json::StyledWriter w;*out = w.write(root);return true;}bool Deserialize(const std::string &in) // "result code"{Json::Value root;Json::Reader r;r.parse(in, root);result = root["result"].asInt();code = root["code"].asInt();return true;}

客户端:

#include<iostream>
#include<string>
#include<unistd.h>
#include<time.h>
#include<assert.h>
#include"Protocol.hpp"
#include"Socket.hpp"
void USage(char *s)
{std::cout<<"Usage:"<<s<<"<ip> <port>"<<std::endl;
}
int main(int argc,char*argv[])
{if(argc!=3){USage(argv[0]);exit(0);}uint16_t port=stoi(argv[2]);std::string ip =argv[1];Sock sockfd;sockfd.Createsockfd();sockfd.Connect(ip, port);int cnt=0;srand(time(NULL)^getpid());const std::string opers = "+-*/%=-=&^";while(cnt <= 10){std::cout << "===============第" << cnt << "次测试....., " << "===============" << std::endl;int x = rand() % 100 + 1;usleep(1234);int y = rand() % 100;usleep(4321);char oper = opers[rand()%opers.size()];Request req(x, y, oper);std::string package;req.Serialize(&package);package = Encode(package);write(sockfd.Fd(), package.c_str(), package.size());cout<<"发送字符串:"<<package<<endl;char buffer[128];std::string inbuffer_stream;ssize_t n = read(sockfd.Fd(), buffer, sizeof(buffer)); if(n > 0){buffer[n] = 0;inbuffer_stream += buffer; // "len"\n"result code"\nstd::cout << inbuffer_stream << std::endl;std::string content;bool r = Decode(inbuffer_stream, &content); // "result code"assert(r);Response resp;r = resp.Deserialize(content);assert(r);cout<<"接受字符串:"<<content<<endl;}std::cout << "=================================================" << std::endl;sleep(1);cnt++;}sockfd.Close();return 0;}

服务端:

#pragma once
#include<iostream>
#include<functional>
#include<strings.h>
#include <signal.h>
#include"Socket.hpp"
using namespace std;
const uint16_t defaultport=8080;
const string defaultip="127.0.0.1";
const int defaultsockfd=-1;
using func_t = std::function<std::string(std::string &package)>;
class Tcpserver
{public:Tcpserver(uint16_t port ,func_t callback) : _port(port), _callback(callback){}void InitServer(){_listensock.Createsockfd();_listensock.Bind(_port);_listensock.Listen();}void Start(){signal(SIGCHLD,SIG_IGN); signal(SIGPIPE,SIG_IGN); while(true){string clientip;uint16_t clientport;int sockfd=_listensock.Accept(&clientip,&clientport);//回调初始化地址与端口号if(sockfd<0){continue;}   //孙子进程提供服务,可并发访问。if(fork()==0){//关闭文件描述符不影响文件缓冲区,防止文件描述符不够用_listensock.Close(); //servicestd::string buffoutput;while(true){char buff[1024];ssize_t n=read(sockfd, buff, sizeof(buff));if(n==0){std::cout<<"读取消息失败"<<"错误码"<<errno<<"错误原因"<<strerror(errno)<<std::endl;break;}else if(n>0){std::cout<<"读取消息成功:"<<buff<<",进行报文解读.........."<<std::endl;buff[n]=0;buffoutput+=std::string(buff);std::cout<<buffoutput<<endl;//可能有一堆请求while(true){std::string info=_callback(buffoutput);//回调给函数DoCalculator//为空就继续读if(info.empty()) {break;}std::cout<< "debug, response:"<< info.c_str()<<std::endl;std::cout<<"debug:"<<buffoutput.c_str()<<std::endl;//向客户写回信息,向缓冲区里写write(sockfd,info.c_str(),info.size());}}else {break;}}exit(0);}close(sockfd);}}~Tcpserver(){}private:Sock _listensock;uint16_t _port;func_t _callback;
};

主运行函数:

#include"Calculator.hpp"
#include"ServerCalculator.hpp"
#include <unistd.h>
void Hlper(char *s)
{std::cout<<"please enter correct command in '"<<s<<" port[1024+]'"<<std::endl;
}
int main(int argc,char *argv[])
{if(argc!=2){Hlper(argv[0]);exit(1);}uint16_t port=stoi(argv[1]);ServerCal Calculator;Tcpserver *Cal=new Tcpserver(port,std::bind(&ServerCal::DoCalculator,&Calculator,std::placeholders::_1));//这里用的是包装器bindCal->InitServer();Cal->Start();return 0;}

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

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

相关文章

高通 8255 基本通信(QUP)Android侧控制方法说明

一&#xff1a;整体说明 高通8255芯片中&#xff0c;SPI IIC UART核心统一由QUP V3 进行控制 QUP V3为可编程模块&#xff0c;可以将不同通道配置为SPI IIC UART通路&#xff0c;此部分配置在QNX侧 QUP 资源可以直接被QNX使用&#xff0c;Android侧可以通过两种方法使用QUP资源…

YOLOv5-Y5周:yolo.py文件解读

本文为&#x1f517;365天深度学习训练营 中的学习记录博客 原作者&#xff1a;K同学啊|接辅导、项目定制 我的环境&#xff1a; 1.语言&#xff1a;python3.7 2.编译器&#xff1a;pycharm 3.深度学习框架Tensorflow/Pytorch 1.8.0cu111 一、代码解读 import argparse i…

idea将非UTF-8的properties修改为UTF-8编码的文件

需求背景 由于项目初始化时&#xff0c;properties文件的编码格式为ASCII编码格式&#xff0c;此时用idea打开该文件会默认展示UTF-8的编码内容&#xff0c;其中汉字可以正常展示&#xff0c;但是使用notepad打开却依旧时ASCII编码格式 idea配置 打开idea-setting-editor-f…

QT C++ QButtonGroup应用

//QT 中&#xff0c;按钮数量比较少&#xff0c;可以分别用各按钮的信号和槽处理。 //当按钮数量较多时&#xff0c;用QButtonGroup可以实现共用一个槽函数&#xff0c;批量处理&#xff0c;减少垃圾代码&#xff0c; //减少出错。 //开发平台&#xff1a;win10QT6.2.4 MSVC…

【LIMS】微服务

目录 一、服务解决方案-Spring Cloud Alibaba1.1选用原因&#xff08;基于Spring Cloud Alibaba的试用场景&#xff09;1.2 核心组件使用前期规划 部署 nacos部署 mino使用JavaFreemarker模板引擎&#xff0c;根据XML模板文件生成Word文档使用JavaFlowable 工作流引擎前端 -vue…

taro之Picker,PickerView基础用法

1.Picker 直接上代码 import Taro,{Component} from "tarojs/taro"; import {View,Picker} from tarojs/components import { AtIcon } from taro-ui import { putKey } from /src/utils/storage-utilsclass AgriculturePolicy extends Component{constructor (prop…

什么是浏览器指纹识别?Maskfog指纹浏览器有用吗?

浏览器指纹识别是好是坏&#xff1f;这现在确实是一个有争议的话题。83%的消费者经常或偶尔会根据浏览历史记录看到广告。其实这就是利用了浏览器指纹技术。 如果您想了解浏览器指纹识别是什么&#xff0c;那就看下去&#xff01; 一、什么是浏览器指纹识别 浏览器指纹是指无…

Linux基础命令[20]-useradd

文章目录 1. useradd 命令说明2. useradd 命令语法3. useradd 命令示例3.1 不加参数3.2 -d&#xff08;指定家目录&#xff09;3.3 -g&#xff08;指定用户组&#xff09;3.4 -G&#xff08;指定附属组&#xff09;3.5 -p&#xff08;加密密码&#xff09;3.6 -e&#xff08;指…

多线程libtorch推理问题

一、环境 我出问题的测试环境如下: pytorch1.10+cu113 pytorch1.10+cu116 pytorch2.2+cu118 libtorch1.10.1+cu113 libtorch1.10.1+cu111 libtorch1.9.0+cu111 二、问题现象 最近封装libtorch的推理为多线程推理的时候,遇到一个现象如下: (1)只要是将模型初始化放到一个…

HDFS概述及常用shell操作

HDFS 一、HDFS概述1.1 HDFS适用场景1.2 HDFS优缺点1.3 HDFS文件块大小 二、HDFS的shell操作2.1 上传2.2 下载2.3 HDFS直接操作 一、HDFS概述 1.1 HDFS适用场景 因为HDFS里所有的文件都是维护在磁盘里的 在磁盘中对文件的历史内容进行修改 效率极其低(但是追加可以) 1.2 HDF…

电力柜智能蓝牙锁控解决方案

一、行业背景 随着智能电网的快速发展&#xff0c;电力柜作为电网的重要组成部分&#xff0c;其安全性和可靠性对于保障电力供应至关重要。传统的电力柜锁控系统多依赖于物理钥匙&#xff0c;存在管理不便、安全隐患大、难以实时监控等问题&#xff0c;为了提高电力柜的安全管…

品牌方年度抖音店铺打造流量运营孵化方案

【干货资料持续更新&#xff0c;以防走丢】 品牌方年度抖音店铺打造流量运营孵化方案 部分资料预览 资料部分是网络整理&#xff0c;仅供学习参考。 PDF共120页&#xff08;完整资料包含以下内容&#xff09; 目录 抖音年度短视频直播运营规划方案 1. 帐号视频发布规划 问…

Java微服务轻松部署服务器

我们在日常开发微服务之后需要再服务器上面部署&#xff0c;那么如何进行部署呢&#xff0c;先把微服务的各个服务和中间件以及对应的端口列举出来&#xff0c;都打包成镜像&#xff0c;以及前端代码部署的nginx&#xff0c;使用docker-compose启动&#xff0c;访问服务器nginx…

C++关键字:const

文章目录 一、const的四大作用1.修饰 变量、数组2.修饰 函数的形参、修饰 引用 (最常用&#xff09;3.修饰 指针&#xff1a;常量指针、指针常量 、只读指针4.修饰 类的成员函数、修饰 类的对象 一、const的四大作用 1.修饰 变量、数组 1.const修饰变量&#xff1a; 被const修…

[LLM]大语言模型文本生成—解码策略(Top-k Top-p Temperature)

{"top_k": 5,"temperature": 0.8,"num_beams": 1,"top_p": 0.75,"repetition_penalty": 1.5,"max_tokens": 30000,"message": [{"content": "你好","role": "user&…

C语言学习过程总结(18)——指针(6)

一、数组指针变量 在上一节中我们提到了&#xff0c;指针数组的存放指针的数组&#xff0c;那数组指针变量是什么呢&#xff1f; 显而易见&#xff0c;数组指针变量是指针 同样类比整型指针变量和字符指针变量里面分别存放的是整型变量地址和字符变量地址&#xff0c;我们可以…

元宇宙VR数字化艺术展降低办展成本

元宇宙AI时代已经来临&#xff0c;越来越多人期待在元宇宙数字空间搭建一个属于自己的虚拟展厅&#xff0c;元宇宙虚拟展厅搭建平台是VR公司深圳华锐视点为企业研发的可编辑工具&#xff0c;那么元宇宙虚拟展厅搭建平台有哪些新突破? 元宇宙虚拟展厅搭建平台采用了先进的web3D…

Navicat:设置mysql数据库表的主键为uuid

文章目录 1 问题描述2 解决方案3 其他方法 1 问题描述 当我使用Navicat新建表test_table之后&#xff0c;想通过导入向导将excel表中的数据导入到表test_tab中&#xff0c;由于没有excel表中没有主键对应的字段&#xff0c;导致导入失败&#xff0c;提示Field id doesnt have …

利用二分法求方程在某个范围内的根

问题描述&#xff1a; 利用二分法求方程在&#xff08;-10,10&#xff09;的根。 方法&#xff1a;先求出两端点的中点&#xff0c;然后将中点带入方程中检查是否等于0&#xff0c;如果等于0说明找到了根&#xff0c;如果大于0&#xff0c;说明根在左半部分&#xff0c;将rig…

自学rabbitmq入门到精通

交换机的fault &#xff08;发布与订阅模式&#xff09; 因为消息是由生产者发送给excahnge&#xff0c;exchange发送给队列&#xff0c; 然后由队列发送给消费者的。 展示使用图形化界面使用fanout模式。 创建交换机 然后创建三个队列&#xff0c;绑定对应的交换机&#xff…