前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【计算机网络】序列化与反序列化

【计算机网络】序列化与反序列化

作者头像
lovevivi
发布2023-11-27 09:53:55
1910
发布2023-11-27 09:53:55
举报
文章被收录于专栏:萌新的日常萌新的日常

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(单独存放两个函数的实现)

makefile

TcpServer.hpp (Tcp服务端 已封装)

Tcpclient.hpp(未封装)

Sock.hpp(Tcp套接字)

Protocol.hpp(序列化与反序列化)

log.hpp(日志)

err.hpp(报错)

CalculatorServer.cc (服务端 主函数)

CalculatorClient.cc (客户端 主函数)

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-11-27,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 如何处理结构化数据?
    • 序列化 与 反序列化
    • 2. 实现网络版计算器
      • 1. Tcp 套接字的封装——sock.hpp
        • 创建套接字——Socket
        • 绑定——Bind
        • 将套接字设置为监听状态——Listen
        • 获取连接——Accept
        • 发起连接——Connect
      • 2. 服务器的实现 ——TcpServer.hpp
        • 初始化
        • 启动
      • 3. 自定义协议定制——Protocol.hpp
        • Request的自定义序列化
        • Request的自定义反序列化
        • Until.hpp (存放 StringSlit | toInt 函数)
        • Response的自定义序列化
        • Response的自定义反序列化
      • 4. Tcpserver.hpp的调用
        • 1.如何保证读到完整的字符串报文?
        • 2.获取有效载荷部分
        • 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 (客户端 主函数)
                        领券
                        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档