【计算机网络】序列化与反序列化
文章目录
- 1. 如何处理结构化数据?
- 序列化 与 反序列化
- 2. 实现网络版计算器
- 1. Tcp 套接字的封装——sock.hpp
- 创建套接字——Socket
- 绑定——Bind
- 将套接字设置为监听状态——Listen
- 获取连接——Accept
- 发起连接——Connect
- 2. 服务器的实现 ——TcpServer.hpp
- 初始化
- 启动
- 多线程的使用
- 3. 自定义协议定制——Protocol.hpp
- Request的自定义序列化
- Request的自定义反序列化
- Until.hpp (存放 StringSlit | toInt 函数)
- StringSlit——将字符串存放入数组中
- toInt——字符串转化为整数
- Response的自定义序列化
- Response的自定义反序列化
- 4. Tcpserver.hpp的调用
- 1.如何保证读到完整的字符串报文?
- ReadPackage的实现
- 2.获取有效载荷部分
- RmoveHeader的实现
- 3. 假设已经读到完整的sring
- 4.提取用户的请求数据
- 5. 给用户响应——序列化
- 6.添加报头
- 7. 发送
- 3. 整体代码实现
- Util.hpp(单独存放两个函数的实现)
- makefile
- TcpServer.hpp (Tcp服务端 已封装)
- Tcpclient.hpp(未封装)
- Sock.hpp(Tcp套接字)
- Protocol.hpp(序列化与反序列化)
- log.hpp(日志)
- err.hpp(报错)
- CalculatorServer.cc (服务端 主函数)
- CalculatorClient.cc (客户端 主函数)
1. 如何处理结构化数据?

通过打包的方式,将结构体message发送给对方
对方收到后就会报告给上层QQ客户端
结构化的数据 是由 多个 string 构成的
而以前在网络套接字 发送时,都是按照一个字符串的方式来发送和接收的
序列化 与 反序列化

所以想办法 ,把多个字符串 转化为 一个大"字符串",对方在接收时也是一个长的字符串,
再想办法把这个字符串转回结构化的数据,就可以让上层使用
把一个结构化的数据 转化为 一个长的字符串 的 过程 称之为 序列化
把一个长的字符串 转化为 一个结构化的数据的 过程 称之为 反序列化
2. 实现网络版计算器
实现一个服务器版的加法器,把客户端把要计算的两个加数发过去,由服务器计算,最后把结果返回给客户端
1. Tcp 套接字的封装——sock.hpp
Sock.hpp 表示 对Tcp套接字的封装
设置一个私有变量 监听套接字 (与accept返回的文件描述符 进行区分)
创建套接字——Socket
输入 man socket,创建套接字

第一个参数 domain ,用于区分 进行网络通信还是 本地通信
若想为网络通信,则使用 AF_INET
若想为本地通信,则使用 AF_UNIX
第二个参数 type, 套接字对应的服务类型
SOCK_STREAM 流式套接
SOCK_DGRAM 无连接不可靠的通信(用户数据报)
第三个参数 protocol ,表示想用那种协议,协议默认为0
若为 流式套接,则系统会认为是TCP协议 ,若为用户数据报,则系统会认为是UDP协议
套接字的返回值:若成功则返回文件描述符,若失败则返回 -1

使用socket 创建一个TCP的网络通信,并返回文件描述符到 _listensock中
把上篇博客的 日志(log.hpp)与错误信息枚举(err.hpp)拷贝过来
若套接字创建失败,则通过日志将错误信息打印处来,并借助 错误信息枚举 终止程序
绑定——Bind
输入 man 2 bind ,查看绑定

给一个套接字绑定一个名字
第一个参数 sockfd 为 套接字
第二个参数 addr 为 通用结构体类型
第三个参数 addrlen 为 第二个参数的实际长度大小
bind返回值:若成功,则返回0,若失败,返回 -1
想要使用bind函数,就需要先创建一个网络通信类型的变量,通过该变量存储端口号 IP地址 16位地址类型
所以要先定义一个 struct sockaddr_in(网络通信) 类型的 变量 local

htons 主机序列转化为 网络序列
需要借助 htons 将传进来的参数 port端口号进行转化
INADDR_ANY 表示 本机的所有IP

若小于0,则绑定失败
依旧使用日志打印处错误码和错误原因,再终止程序

将套接字设置为监听状态——Listen
输入 man 2 listen
设置当前套接字状态为 监听状态

第一个参数 sockfd 为 套接字
第二个参数 暂不做解释,一般设为整数
若成功则返回0,若失败返回-1

若小于0,则监听失败
依旧使用日志打印处错误码和错误原因,再终止程序
获取连接——Accept
输入 man 2 accept

需要知道谁连的你,所以要获取到客户端的相关信息
第一个参数 sockfd 为套接字
第二个参数 addr 为通用结构体类型的 结构体 这个结构体是用来记录客户端内的port号以及IP地址 、16位地址类型等信息
第三个参数 addrlen 为 结构体的大小
返回值:
若成功,则返回一个合法的整数 即文件描述符
若失败,返回-1并且设置错误码

sock 这个文件描述符 是真正给用户提供IO服务的
若连接失败,则返回-1,使用日志将错误信息打印出来

若连接成功,则需获取到对应的客户端的 端口号 与客户端的IP地址
使用 inet_ntoa 4字节风格IP转化为字符串风格IP
使用 ntohs 网络序列转主机序列

发起连接——Connect
connect 函数功能为客户端主动连接服务器
成功返回0,失败返回-1

2. 服务器的实现 ——TcpServer.hpp

使用Sock这个类,实例化对象_listensock
初始化

在初始化中,使用_listensock这个对象 去访问 Scok类中实现过的 Socket Bind Listen 等函数
启动
作为一款服务器,就需要一直运行 作数据的分析

通过_listensock对象访问Accept函数获取客户端的IP地址和端口号
多线程的使用

在类中的函数如果不加static修饰,就会导致存在隐藏的this指针
所以 回调函数 需加 static 修饰
使用 pthread_join 默认是阻塞的 ,即主线程等待 新线程退出
在这个过程中,主线程会直接卡住,就没办法继续向后运行,也就什么都干不了
若主线程 想做其他事情 ,所以就提出了线程分离的概念
创建一个结构体ThreadData内部包含sock套接字以及一个指向服务器的指针 ip地址 port端口号
在初始化 多线程部分,new对象,将sock clientip client port 与this指针传递过去作为参数 完成构造

再将td传过去作为回调函数的参数

在回调函数内部调用 serviceIO函数 来完成协议
3. 自定义协议定制——Protocol.hpp
在命名空间Protocol_n中,定义两个类,分别为Request类和Reponse类

若读到 字符串风格的Request ,就需要通过 序列化 转成 结构化的数据
Request的自定义序列化

自己定义 将结构化的数据 转化为 字符串
假设空格作为分割符

使用to_string 将任意类型转化为string

使用 宏, 将SEP表示为空格
将_x _y _op 使用空格连接起来
Request的自定义反序列化

提供一个函数StringSplit ,去掉字符串中的空格,分别填入vector数组中,作为vetcor数组中的元素
下标为0开始的位置 填入_x ,下标为1开始的位置 填入 _op
下标为2开始的位置 填入 _y
借助函数 toInt,将string类型的元素 转化为 整数
_op在 vector数组的1号下标中,对应其中的一个字符

Until.hpp (存放 StringSlit | toInt 函数)
StringSlit——将字符串存放入数组中

寻找SEP分割符所在位置,即可分割出区间
使用find函数,从start位置开始寻找分隔符sep,找到分割符sep后,将区间内的子串插入vector数组中
当sep为空格时,只占用一个位置,pos处于空格位置 ,只需加1即可跳出空格
故start的位置 只需 从pos 位置 加上 sep长度即可得到
若出了循环str中依旧有子串没有被插入vector中,则全部当做一个整体放入vector中

toInt——字符串转化为整数

使用 atoi 函数 将字符串转化为 整形

Response的自定义序列化

使用to_string 将任意类型转化为string
将 res_string SEP 和 code_string 连接起来
Response的自定义反序列化

同样取调用 StringSplit函数 将字符串 转换为 vector数组中的元素
分别将结果和错误码提取出来
4. Tcpserver.hpp的调用
1.如何保证读到完整的字符串报文?

定义一个string类型的package,从套接字sock读取,将结果添加到package中
若有完整报文就交给package,没有完整报文,则一直读取
inbuffer 用于记录报文的所有数据
ReadPackage的实现
输入 man recv

第一个参数为 套接字
第二个参数为缓冲区
第三个参数 为缓冲区长度
第四个参数为 读取方式 ,一般默认为0
返回值为读取到的字节数,若字节数小于0,则表示读取出错

先使用recv,将sock中的数据读取到buffer中,再将数据传入inbuffer中

通过find 查找inbuffer中的\r\n的位置,在使用substr将提取到的头部字符串(报头) ,
使用 toInt 将字符串转化为数字 ,即获取到字符串长度
最终将有效载荷数据传入 package中

若返回值为-1,则表示读取失败,若返回值为0,则表示继续读取
若返回值为1,则表示读取成功,即可进入下面步骤
2.获取有效载荷部分
RmoveHeader的实现

从后面先减去一个分隔符,再减去有效载荷的长度
从有效载荷位置开始 取 有效载荷的长度个字符 即 取到有效载荷
3. 假设已经读到完整的sring

构建一个Request 对象
通过该对象去访问请求的 反序列化 ,将字符串str转化为结构化的数据
4.提取用户的请求数据

定义一个包装器,其返回值类型为Response ,参数为 Request ,并重命名为 func_t

使用func_t类型 定义 一个func的私有成员变量

将Request处理完 变为 Response
在Calculatorserver.cc中,进行请求处理

先将结果与错误码默认都设置为0,表示成功
使用 switch case 把request变量的req 中的 _x _y 通过 加 减 乘 除 取模 等进行运算
若期间错误码 出现 1 2 3,则表示错误
最终 将执行后的结果 返回resp中
5. 给用户响应——序列化

对response结构进行序列化,将其转化为字符串
6.添加报头

将send_string字符串 中 添加字符串长度 分隔符 \r\n
7. 发送
输入 man send

第一个参数为 套接字
第二个参数为特定字符串数据
第三个参数为 数据长度
第四个参数为 默认为0

3. 整体代码实现
Util.hpp(单独存放两个函数的实现)
#pragma once
#include<iostream>
#include<string>
#include<vector>
#include<cstdlib>using namespace std;
class Util
{public://将字符串str 按照sep分隔符 把结果放入 result中//分割成功 则为true 分割失败,则为falsestatic bool StringSplit(const string &str,const string &sep,std::vector<string>*result){size_t start=0;// 10 + 20while(start<str.size()){auto pos=str.find(sep,start);//从start位置开始寻找sepif(pos==string::npos)//找不到分隔符了{break;}//从start位置开始 寻找pos-start个字符,并将其放入vector数组中result->push_back(str.substr(start,pos-start));//位置的重新加载start=pos+sep.size();}//若出了循环str中依旧有子串没有被插入vector中,则全部当做一个整体放入if(start<str.size()){result->push_back(str.substr(start));}return true;}//字符串转整数static int toInt(string &s){return atoi(s.c_str());}
};
makefile
.PHONY:all
all:calserver calclientcalclient:CalculatorClient.ccg++ -o $@ $^ -std=c++11 -lpthread -ljsoncpp
calserver:CalculatorServer.ccg++ -o $@ $^ -std=c++11 -lpthread -ljsoncpp.PHONY:clean
clean:rm -f calclient calserver
TcpServer.hpp (Tcp服务端 已封装)
#pragma once
#include<iostream>
#include<pthread.h>
#include<functional>
#include"Sock.hpp"
#include"Protocol.hpp"
//服务器namespace tcpserver_ns
{using namespace protocol_ns;class TcpServer;//定义包装器using func_t =std::function<Response(const Request&)>;class ThreadData{public:ThreadData(int sock,std::string ip,uint16_t port,TcpServer*tsvrp)//构造:_sock(sock),_ip(ip),_port(port),_tsvrp(tsvrp){}~ThreadData(){}public:int _sock;//套接字TcpServer *_tsvrp;//指针指向Tcp服务器std::string _ip;uint16_t _port;};class TcpServer{public:TcpServer(func_t func,uint16_t port ):_func(func),_port(port){}void InitServer()//初始化{//1.初始化服务器_listensock.Socket();//创建套接字_listensock.Bind(_port);//绑定_listensock.Listen();//监听logmessage(INFO,"init server done,listensock:%d",_listensock.Fd());}//该函数被多线程调用void serviceIO(int sock,const std::string ip,const uint16_t port)//提供服务{std::string inbuffer;//用于记录报文的所有数据while(true){//1.如何保证读到完整的字符串报文? ----7\r\n""10 + 20"\r\n//不能保证//所以要一直循环读取,边读取 边检测 测试std::string package; //从sock中读,将结果添加到packageint n=ReadPackage(sock,inbuffer,&package);if(n==-1) //-1表示读取失败{break;;}else if(n==0)//0表示继续读{continue;}else //读取成功,返回报头长度{//2.需要的只是有效载荷的部分 "10 + 20"package=RemoveHeader(package,n);//将package中的有效载荷提取出来//3.假设已经读到一个完整的stringRequest req; //构建一个Request对象 std::string str;req.Deserialize(str);//对读到的request字符串进行反序列化//4.提取用户的请求数据Response resp= _func(req);//5.给用户返回响应std::string send_string;resp.Serialize(&send_string);//对计算完毕的response结构进行序列化,形成可发送字符串 //6. 添加报头send_string =AddHeader(send_string);//添加报头//7. 发送send(sock,send_string.c_str(),send_string.size(),0);}}close(sock);}static void*ThreadRoutine(void*args){pthread_detach(pthread_self());//线程分离ThreadData* td=(ThreadData*)args;td->_tsvrp->serviceIO(td->_sock,td->_ip,td->_port);delete td;return nullptr;}void Start()//启动{for(; ;){std::string clientip;uint16_t clientport;int sock=_listensock.Accept(&clientip,&clientport);//获取连接if(sock<0)//连接失败{continue;}logmessage(DEBUG,"get a new client,client info:[%s:%d]",clientip.c_str(),clientport);//多线程pthread_t tid;ThreadData*td=new ThreadData(sock,clientip,clientport,this);pthread_create(&tid,nullptr, ThreadRoutine,td);}}~TcpServer(){_listensock.Close();}private:func_t _func;uint16_t _port;//端口号Sock _listensock;};
}
Tcpclient.hpp(未封装)
#pragma once
#include<iostream>
#include<string>
Sock.hpp(Tcp套接字)
#pragma once
#include<iostream>
#include<cstring>
#include<cstdlib>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<unistd.h>
#include"log.hpp"
#include"err.hpp"static const int gbacklog=32;
static const int defaultfd=-1;
class Sock
{public:Sock() //构造:_sock(defaultfd){}void Socket()//创建套接字{_sock=socket(AF_INET,SOCK_STREAM,0);if(_sock<0)//套接字创建失败{logmessage(FATAL,"socket error,code:%s,errstring:%s",errno,strerror(errno));exit(SOCKET_ERR);}}void Bind(uint16_t port)//绑定{struct sockaddr_in local;memset(&local,0,sizeof(local));//清空local.sin_family=AF_INET;//16位地址类型local.sin_port= htons(port); //端口号local.sin_addr.s_addr= INADDR_ANY;//IP地址//若小于0,则绑定失败if(bind(_sock,(struct sockaddr*)&local,sizeof(local))<0){logmessage(FATAL,"bind error,code:%s,errstring:%s",errno,strerror(errno));exit(BIND_ERR);}}void Listen()//将套接字设置为监听状态{//小于0则监听失败if(listen(_sock,gbacklog)<0){logmessage(FATAL,"listen error,code:%s,errstring:%s",errno,strerror(errno));exit(LISTEN_ERR);}}int Accept(std::string *clientip,uint16_t * clientport)//获取连接{struct sockaddr_in temp;socklen_t len=sizeof(temp);int sock=accept(_sock,(struct sockaddr*)&temp,&len);if(sock<0){logmessage(WARNING,"accept error,code:%s,errstring:%s",errno,strerror(errno));}else {//inet_ntoa 4字节风格IP转化为字符串风格IP*clientip = inet_ntoa(temp.sin_addr) ; //客户端IP地址//ntohs 网络序列转主机序列*clientport= ntohs(temp.sin_port);//客户端的端口号}return sock;//返回新获取的套接字}int Connect(const std::string&serverip,const uint16_t &serverport )//发起链接{struct sockaddr_in server;memset(&server,0,sizeof(server));//清空server.sin_family=AF_INET;//16位地址类型server.sin_port=htons(serverport);//端口号//inet_addr 字符串风格IP转化为4字节风格IPserver.sin_addr.s_addr=inet_addr(serverip.c_str());//IP地址//成功返回0,失败返回-1return connect(_sock, (struct sockaddr*)&server,sizeof(server));}int Fd(){return _sock;}void Close(){if(_sock!=defaultfd){close(_sock);}}~Sock()//析构{}private:int _sock;};
Protocol.hpp(序列化与反序列化)
#pragma once
#include<iostream>
#include<string>
#include<cstring>
#include<vector>
#include<jsoncpp/json/json.h>
#include"Util.hpp"//#define MYSELF 1 //用于条件编译
//给网络版本计算器定制协议
namespace protocol_ns
{#define SEP " "
#define SEP_LEN strlen(SEP)
#define HEADER_SEP "\r\n"
#define HEADER_SEP_LEN strlen("\r\n")
//"长度"\r\n "_x _op _y"\r\n
//Request与 Response 都要提供序列化和反序列化功能// "10 + 20" -> "7\r\n""10 + 20"\r\n
std::string AddHeader(const std::string&str)//添加报头
{std::string s=std::to_string(str.size());//字符串的长度s+= HEADER_SEP;//加上分隔符s+= str;//加上正文s+= HEADER_SEP;
}// "7\r\n""10 + 20"\r\n" -> "10 + 20"
std::string RemoveHeader(const std::string &str,int len)//提取数据
{return str.substr(str.size()-HEADER_SEP_LEN-len,len);//获取有效载荷
}// 0表示继续读 1表示读取成功 -1表示读取失败int ReadPackage(int sock,std::string& inbuffer,std::string* package){//边读取char buffer[1024];ssize_t s=recv(sock,&buffer,sizeof(buffer-1),0);//将sock中的数据读取到buffer中if(s<=0)//读取出错{return -1;}buffer[s]=0;inbuffer += buffer;//边分析 7\r\n""10 + 20"\r\nauto pos=inbuffer.find( HEADER_SEP);//查询\r\n的位置if(pos==std::string::npos)//没有找到\r\n,则说明报文不完整{return 0;}std::string lenstr=inbuffer.substr(0,pos);//获取头部字符串int len =Util::toInt(lenstr);//有效载荷长度 "123"-> 123 int targetPackageLen=lenstr.size() + len+2* HEADER_SEP_LEN;//总报文=报头长度+有效载荷长度+分隔符长度if(inbuffer.size()<targetPackageLen)//说明缓冲区不存在完整报文{return 0;}*package= inbuffer.substr(0,targetPackageLen); //提取有效载荷数据inbuffer.erase(0,targetPackageLen);//从inbuffer中移除整个报文return len;//返回有效载荷长度}class Request//请求{public:Request(){}Request(int x,int y,char op):_x(x),_y(y),_op(op){}//序列化 结构化的数据 转为字符串bool Serialize( std::string* outstr){*outstr="";//清空
#ifdef MYSELF// _x _op _y*outstr="";//清空std::string x_string =std::to_string(_x);std::string y_string =std::to_string(_y);//手动序列化*outstr=x_string + SEP + _op + SEP + y_string;
#elseJson::Value root;//value:一种万能对象,接收任意的kv类型root["x"]=_x;root["y"]=_y;root["op"]=_op;Json::FastWriter writer; // write 用于进行序列化 将结构化字段转化为字符串//Json::StyledWriter*outstr =writer.write(root);#endifreturn true;}//反序列化 字符串 转为 结构化的数据bool Deserialize(const std::string &instr){
#ifdef MYSELFstd::vector<std::string> result;Util::StringSplit(instr,SEP,&result);//根据协议规定必须等于3 _x _op _yif(result.size()!=3){return false;}_x=Util::toInt(result[0]);_y=Util::toInt(result[2]);if(result[1].size()!=1){return false;}_op= result[1][0];#else Json::Value root;Json::Reader reader;//Reader 用于进行反序列化reader.parse(instr,root);//将结果放入root中_x=root["x"].asInt();//将字符串类型转换为整形_y=root["y"].asInt();_op=root["op"].asInt();//转化为数字 放入char中,最后会被解释为字符#endif }~Request(){}public:// _x op _y//x y为操作数 op为操作符int _x;int _y;char _op;};class Response//响应{public:Response(){}Response(int result,int code):_result(result),_code(code){}//序列化 结构化的数据 转为字符串bool Serialize( std::string* outstr){
#ifdef MYSELF
*outstr="";//清空std::string res_string =std::to_string(_result);std::string code_string =std::to_string(_code);*outstr=res_string + SEP + code_string;
#else Json::Value root;root["reslut"]=_result;root["code"] =_code;Json::FastWriter writer;//用于反序列化*outstr=writer.write(root);#endif return true;}//反序列化 字符串 转为 结构化的数据bool Deserialize(const std::string &instr){
#ifdef MYSELF// 10 0 / 10 1 / 10 2 std::vector<std::string> result;Util::StringSplit(instr,SEP,&result);//当前只存在 结果和错误码if(result.size()!=2){return false;}_result=Util::toInt(result[0]);_code=Util::toInt(result[1]);
#else Json::Value root;Json::Reader reader;//用于反序列化reader.parse(instr,root);//将结果传给root_result=root["result"].asInt();//将字符串结果转化为整数_code=root["code"].asInt();
#endif return true;}~Response(){}public:int _result;//结果int _code;//默认为0 表示成功 1 2 3 4 不同的数字表示不同的错误码};
}
log.hpp(日志)
#pragma once
#include<iostream>
#include<string.h>
#include<cstdio>
#include<cstring>
#include<cstdarg>
#include<unistd.h>
#include<sys/types.h>
#include<time.h>const std::string filename="tecpserver.log";//日志等级
enum{DEBUG=0, // 用于调试INFO , //1 常规WARNING, //2 告警ERROR , //3 一般错误FATAL , //4 致命错误UKNOWN//未知错误
};static std::string tolevelstring(int level)//将数字转化为字符串
{switch(level){case DEBUG : return "DEBUG";case INFO : return "INFO";case WARNING : return "WARNING";case ERROR : return "ERROR";case FATAL : return "TATAL";default: return "UKNOWN";}
}
std::string gettime()//获取时间
{time_t curr=time(nullptr);//获取time_tstruct tm *tmp=localtime(&curr);//将time_t 转换为 struct tm结构体char buffer[128];snprintf(buffer,sizeof(buffer),"%d-%d-%d %d:%d:%d",tmp->tm_year+1900,tmp->tm_mon+1,tmp->tm_mday,tmp->tm_hour,tmp->tm_min,tmp->tm_sec);return buffer;}
void logmessage(int level, const char*format,...)
{//日志左边部分的实现char logLeft[1024];std::string level_string=tolevelstring(level);std::string curr_time=gettime();snprintf(logLeft,sizeof(logLeft),"%s %s %d",level_string.c_str(),curr_time.c_str());//日志右边部分的实现char logRight[1024]; va_list p;//p可以看作是1字节的指针va_start(p,format);//将p指向最开始vsnprintf(logRight,sizeof(logRight),format,p);va_end(p);//将指针置空//打印日志 printf("%s%s\n",logLeft,logRight);
}
err.hpp(报错)
#pragma once enum
{USAGE_ERR=1,SOCKET_ERR,//2BIND_ERR,//3LISTEN_ERR,//4SETSID_ERR,//5OPEN_ERR//6
};
CalculatorServer.cc (服务端 主函数)
#include"TcpServer.hpp"#include<memory>using namespace tcpserver_ns;// ./calserver 8888Response calculate(const Request& req){//一定保证req 是有具体数据的//默认将结果和错误码设置 为0Response 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=1;}else {resp._result= req._x / req._y;}break;case '%':if(req._y==0){resp._code=2;}else {resp._result= req._x % req._y;}break;default:resp._code=3;break;}return resp; } int main(){ uint16_t port=8888;std::unique_ptr<tcpserver_ns::TcpServer> tsvr(new tcpserver_ns::TcpServer(calculate,port));tsvr->InitServer();tsvr->Start();return 0;}
CalculatorClient.cc (客户端 主函数)
#include"TcpClient.hpp"
#include<iostream>
#include<string>
#include "Sock.hpp"
#include"Protocol.hpp"using namespace protocol_ns;
static void usage(std::string proc)
{std::cout<<"usage:\n\t"<< proc<<" serverip serverport\n"<<std::endl;
}//./tcpclient serverip serverportint main(int argc,char*argv[]){if(argc!=3){usage(argv[0]);exit(USAGE_ERR);//终止程序}std::string serverip= argv[1];uint16_t serverport=atoi(argv[2]);//将字符串转化为整形Sock sock;sock.Socket();//创建套接字int n=sock.Connect(serverip,serverport);//发起连接if(n!=0)//连接失败{return 1;}std::string buffer;while(true){std::cout<<" enter# "<<std::endl;//1+1std::string line;std::getline(std::cin,line);Request req;std::cout<<"data#1"<<std::endl;std::cin>>req._x;std::cout<<"data#2"<<std::endl;std::cin>>req._y;std::cout<<"op#3"<<std::endl;std::cin>>req._op;std::cout<<req._x<<req._op<<req._y<<std::endl;//1.序列化std::string sendstring;req.Serialize(&sendstring);//2.添加报头AddHeader(sendstring); //3.发送send(sock.Fd(),sendstring.c_str(),sendstring.size(),0);//4.获得响应std::string package;//返回有效载荷长度 若大于0则继续执行int n=0;START:n=ReadPackage(sock.Fd(),buffer,&package);if(n==0){goto START;}else if(n<0){break;}else {}//5. 去掉报头package=RemoveHeader(package,n);//获取有效载荷//6.反序列化Response resp;resp.Deserialize(package);//反序列化std::cout<<"result:"<<resp._result<<" "<<"code:"<<resp._code<<std::endl;} // sock.Close();return 0;}
相关文章:

【计算机网络】序列化与反序列化
文章目录 1. 如何处理结构化数据?序列化 与 反序列化 2. 实现网络版计算器1. Tcp 套接字的封装——sock.hpp创建套接字——Socket绑定——Bind将套接字设置为监听状态——Listen获取连接——Accept发起连接——Connect 2. 服务器的实现 ——TcpServer.hpp初始化启动…...

Linux内核学习(七)—— 定时器和时间管理(基于Linux 2.6内核)
目录 一、内核中的时间概念 二、节拍率:HZ 实时时钟 系统定时器 三、定时器 系统定时器是一种可编程硬件芯片,能以固定频率产生定时器中断,它所对应的中断处理程序负责更新系统时间,也负责执行需要周期性运行的任务。 一、内…...
Tortoise Git(乌龟git)常用命令总结
查看全局和本地 Git 配置 打开命令行终端(如 Git Bash),分别执行以下命令查看全局和本地的 Git 配置信息: git config --global -l git config --local -l确保配置中没有任何与 SSH 相关的设置 移除全局和本地 SSH 相关配置&…...
SSM商城项目实战:物流管理
SSM商城项目实战:物流管理 在SSM商城项目中,物流管理是一个重要的功能模块。通过物流管理,可以实现订单的配送、运输和签收等操作。本文将介绍如何在SSM商城项目中实现物流管理功能的思路和步骤代码。 实现SSM商城项目中物流管理的思路总结如…...

nlp系列(7)三元组识别(Bert+CRF)pytorch
模型介绍 在实体识别中:使用了Bert模型,CRF模型 在关系识别中:使用了Bert模型的输出与实体掩码,进行一系列变化,得到关系 Bert模型介绍可以查看这篇文章:nlp系列(2)文本分类&…...
Druid配置类、Dubbo配置类、Captcha配置类、Redis配置类、RestTemplate配置类
DruidConfig配置类package com.xdclass.app.config;import com.alibaba.druid.pool.DruidDataSource; import com.alibaba.druid.support.http.StatViewServlet; import com.alibaba.druid.support.http.WebStatFilter; import org.springframework.beans.factory.annotation.V…...
Pyecharts教程(十二):使用pyecharts创建带有数据缩放滑块和位置指示器的K线图
Pyecharts教程(十二):使用pyecharts创建带有数据缩放滑块和位置指示器的K线图 作者:安静到无声 个人主页 目录 Pyecharts教程(十二):使用pyecharts创建带有数据缩放滑块和位置指示器的K线图前言代码讲解总结完整代码推荐专栏前言 本博客将详细解释如何使用Python中的pyech…...
MySQL 基本操作
目录 数据库的列类型 数据库基本操作 SQL语言规范 SQL语句分类 查看表,使用表 管理数据库 创建数据库和表 删除数据库和表 向数据表中添加数据 查询数据表中数据 修改数据表的数据 删除数据表中数据 修改表明和表结构 扩展表结构(增加字段&…...

HHDESK一键改密功能
HHDESK新增实用功能——使用SSH连接,对服务器/端口进行密码修改。 1 测试 首页点击资源管理——客户端,选择需要修改的连接; 可以先对服务器及端口进行测试,看是否畅通; 右键——测试——ping; 以及右…...

瞬态电压抑制器(TVS)汽车级 SZESD9B5.0ST5G 工作原理、特性参数、封装形式
什么是汽车级TVS二极管? TVS二极管是一种用于保护电子电路的电子元件。它主要用于电路中的过电压保护,防止电压过高而损坏其他部件。TVS二极管通常被称为“汽车级”是因为它们能够满足汽车电子系统的特殊要求。 在汽车电子系统中,由于车辆启…...

ChatGPT 一条命令总结Mysql所有知识点
想学习Mysql的同学,可以使用ChatGPT直接总结mysql所有的内容与知识点大纲 输入 总结Mysql数据库所有内容大纲与大纲细分内容 ChatGPT不光生成内容,并且直接完成了思维导图。 AIGC ChatGPT ,BI商业智能, 可视化Tableau, PowerBI, FineReport, 数据库Mysql Oracle, Offi…...

Nginx-报错no live upstreams while connecting to upstream
1、问题描述 生产环境Nginx间歇性502的事故分析过程 客户端请求后端服务时一直报错 502 bad gateway,查看后端的服务是正常启动的。后来又查看Nginx的错误日志,发现请求后端接口时Nginx报错no live upstreams while connecting to upstream,…...

五种 CSS 位置类型以实现更好的布局
在 Web 开发中,CSS(层叠样式表)用于设置网站样式的设置。为了控制网页上元素的布局,使用CSS的position属性。因此,在今天这篇文章中,我们将了解 CSS 位置及其类型。 CSS 位置属性用于控制网页上元素的位置…...

【真题解析】系统集成项目管理工程师 2022 年下半年真题卷(综合知识)
本文为系统集成项目管理工程师考试(软考) 2022 年下半年真题(全国卷),包含答案与详细解析。考试共分为两科,成绩均 ≥45 即可通过考试: 综合知识(选择题 75 道,75分)案例分析&#x…...

视频中的声音怎么提取出来?这样做提取出来很简单
提取视频中的声音可以有多种用途。例如,我们可能希望从视频中提取音乐或音效,以在其他项目中使用。或者,可能需要将视频中的对话转录为文本,以便更轻松地编辑和共享内容。无论目的是什么,提取视频中的声音都可以帮助我…...

【Qt学习】05:自定义封装界面类
OVERVIEW 自定义封装界面类1.QListWidget2.QTreeWidget3.QTableWidget4.StackedWidget5.Others6.自定义封装界面类-显示效果(1)添加设计师界面类(2)在ui中设计自定义界面(3)在需要使用的界面中添加…...

网络服务第二次作业
[rootlocalhost ~]# vim /etc/httpd/conf.d/vhosts.conf <Virtualhost 192.168.101.200:80> #虚拟主机IP及端口 DocumentRoot /www/openlab #网页文件存放目录 ServerName www.openlab.com #服务器域名 </VirtualHost> …...

【记录】USSOCOM Urban3D 数据集读取与处理
Urban3D数据集内容简介 Urban3D数据集图像为正摄RGB影像,分辨率为50cm。 从SpaceNet上使用aws下载数据,文件夹结构为: |- 01-Provisional_Train|- GT|- GT中包含GTC,GTI,GTL.tif文件,GTL为ground truth b…...
flutter ios webview不能打开http地址
参考 1、iOS添加信任 webview_flutter 在使用过程中会iOS出现无法加载HTTP请求的情况, 但是Flutter 却可以加载HTTP请求。这就与两个的框架有关了,Flutter是独立于UIKit框架的。 解决方案就是在iOS 的info.plist中添加对HTTP的信任。 <key>NSApp…...
【SpringBoot】详细介绍SpringBoot中Entity类中的getters和setters
在Spring Boot中的Entity类中,getters和setters是用来获取和设置对象属性值的方法。它们是Java Bean规范的一部分,并且通常被用于向开发人员和框架公开类的属性。 在Entity类中,getters和setters方法通常通过property来实现,即将…...

376. Wiggle Subsequence
376. Wiggle Subsequence 代码 class Solution { public:int wiggleMaxLength(vector<int>& nums) {int n nums.size();int res 1;int prediff 0;int curdiff 0;for(int i 0;i < n-1;i){curdiff nums[i1] - nums[i];if( (prediff > 0 && curdif…...
MVC 数据库
MVC 数据库 引言 在软件开发领域,Model-View-Controller(MVC)是一种流行的软件架构模式,它将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。这种模式有助于提高代码的可维护性和可扩展性。本文将深入探讨MVC架构与数据库之间的关系,以…...

linux arm系统烧录
1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 (忘了有没有这步了 估计有) 刷机程序 和 镜像 就不提供了。要刷的时…...

P3 QT项目----记事本(3.8)
3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...

多模态大语言模型arxiv论文略读(108)
CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文标题:CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文作者:Sayna Ebrahimi, Sercan O. Arik, Tejas Nama, Tomas Pfister ➡️ 研究机构: Google Cloud AI Re…...

AI,如何重构理解、匹配与决策?
AI 时代,我们如何理解消费? 作者|王彬 封面|Unplash 人们通过信息理解世界。 曾几何时,PC 与移动互联网重塑了人们的购物路径:信息变得唾手可得,商品决策变得高度依赖内容。 但 AI 时代的来…...

排序算法总结(C++)
目录 一、稳定性二、排序算法选择、冒泡、插入排序归并排序随机快速排序堆排序基数排序计数排序 三、总结 一、稳定性 排序算法的稳定性是指:同样大小的样本 **(同样大小的数据)**在排序之后不会改变原始的相对次序。 稳定性对基础类型对象…...
怎么让Comfyui导出的图像不包含工作流信息,
为了数据安全,让Comfyui导出的图像不包含工作流信息,导出的图像就不会拖到comfyui中加载出来工作流。 ComfyUI的目录下node.py 直接移除 pnginfo(推荐) 在 save_images 方法中,删除或注释掉所有与 metadata …...
作为测试我们应该关注redis哪些方面
1、功能测试 数据结构操作:验证字符串、列表、哈希、集合和有序的基本操作是否正确 持久化:测试aof和aof持久化机制,确保数据在开启后正确恢复。 事务:检查事务的原子性和回滚机制。 发布订阅:确保消息正确传递。 2、性…...

android RelativeLayout布局
<?xml version"1.0" encoding"utf-8"?> <RelativeLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"match_parent"android:gravity&…...