当前位置: 首页 > news >正文

自定义协议与序列化

前言

书接上回,我们上一篇提到了协议并且我们草率的写了一个协议,然后又对TCP的R和W留了一个伏笔,我们今天彻底做个了断。

UDP是面向数据报的,它要么不读,要么就是一次读完的,所以不存在数据断断续续的问题,但是TCP是面向字节流的,所以它一次读的数据并不一定是完整的。

然后我们在来谈RW的问题,我们的RW真的是直接读到数据就发给对方吗?

其实不是的

我们可以先跳过序列化的过程直接看底层,所以实际上我们的RW只是负责把信息发给缓冲区或者从缓冲区读,它们的本质只是拷贝,怎么发,发多少,发错了咋办,这些它都不管。

这一块归TCP协议自主决定,所以TCP叫传输控制协议,再进一步,我们TCP就算发,它叫发吗,它也只是把本地信息拷贝到网络中,所以通信即拷贝。

我们两台TCP的主机,四个缓冲区,其实就是生产者消费者模型。

序列化/反序列化

我们上面那张图,中间好像缺了一块,不是我画的丑,我们现在来讲缺失的那一块。

我们如果传消息,它有消息的内容、时间、谁等等属性,那我们是一段一段发吗,这也太麻烦了,那用结构体合并起来发怎么样呢,可以,但是每个语言对齐标准不一样,你用结构体虽然可以,但是只能是同语言之间发,限制太大了,所以,有了序列化的方法,它把上面的信息全合并成一条字符串,中间可能用什么符号隔开,这一条字符串发过去,啥语言没字符串,也不需要考虑对齐。

对方收到以后,我们都可以通信了诶,那我们总要有共同的协议吧,它就按协议里的规则再把字符串给拆分,就拿到信息了,这叫反序列化。

把这一块加入上图,是不是就完整了,也更能明白,为啥会断断续续的发,因为就算我们合成一条字符串信息,但是发多少还是TCP协议自主决定的,所以这种情况下我们是不能反序列化的,得等拿到完整的信息才能反序列化。它能少,也能多,你也有可能一次收到多条信息呢。这种就叫面向字节流。

 模版模式封装Socket

直接进入代码环节

我们之前讲完UDP和TCP,我们发现它俩方法都差不多,虽然我们都各自封装了,但是还不够,我们C++学完后就能感觉有点似曾相识的感觉呢,继承多态。

我们用一个基类,而基类具体实现哪种方法取决于我们传什么子类。

是的,我们之前UDP和TCP的偷懒封装在这更进一步了,我们直接用一个基类合并了它们俩。

具体的实现,就取决于我们是UDP还是TCP,但是上层,只关心你传的基类。而我基类的初始化,那不就是调用对应类型的方法,我传TCP就调TCP的初始化,UDP就UDP的。

今天我们是TCP它还有个accept连接呢。

为啥我们不简单返回套接字呢,因为我们返回子类对象,后续基类拿到以后也可以直接调用子类的方法,去除差异化。

服务端

我们方法都封装好了,所以服务端反而变简单了。

老样子提供端口号,然后基类对象,判断是否在运行,然后提供信息处理方法就完了。唯一不太一样的就是今天我们的listen是去构建一个子类对象,而不是自己listen。

然后又是熟悉的死循环起手,接收客户端信息,建立连接,然后用多线程的方法,让父进程负责寻找客户端信息,带给我们的子进程,而子进程收到信息后,先关闭不需要的套接字,然后自己退出,让孙子进程对我们的客户端信息做处理。

那么方法还不是我们要做啥操作传啥方法。

那现在,我们来详谈方法。我们先得明确,我客户端对你服务端有特定的协议规定,而你服务端对客户端也有特定的协议,这俩不是共用一套协议的,或者说是在一套大协议下,用里面约束自己的那一套协议。

客户端协议

这要是按我们自己定义的,中间加个符号之类的隔开作为区分,那这个协议和我们前面写的是没啥区别的,而且局限性很大,往往方法一改变就得作废,所以我们今天用新的方法。

有点麻烦,但是没办法,它有点像pair,也是key:value对应的。那这不就能让字符串和数据自动关联上了吗。 

结果大概就长这样。

所以序列化就是把数据线变字符串,反序列化就是把数据先恢复成对应的类型,然后提取内容。

服务端协议

服务端和客户端大同小异,本质就是转化 、操作、把结果再转化发回给对方。可是,我们TCP是面向字节流啊,你序列化了也不能保证一次就是完整的一条数据不多不少。所以我们需要进一步完善协议。

协议部分

OK 我们现在有对客户端和服务端的协议了,我们现在需要给它俩包起来,在我们真正使用时可以自由使用任意一个。

我们这个更大的外层,包含了客户端转服务端的方法,这样服务端才知道如何具体处理客户端的信息。

然后我们规定了客户端发送信息以什么格式发送。

然后服务端又如何接收,实际上就是拆分的过程,我们先找\r\n,如果可以找到,那它至少可能是个报文,如果\r\n都没有,说明它无法构成完整报文了。

然后我们再读取出它的长度部分,把它一转化就得到了信息内容的长度,那么加上两个分隔符的长度就是我们这一条报文的总长度。

如果传入的信息和我们的长度对不上,那说明也有问题,它也不算一条正常的报文。

走到这一步,上面就算找到完整的报文了,那我们把它从第一个分隔符之后开始一直提取到长度位置,就得到了这一条报文,还不算完,我们还要善后,把我们读取到的部分删除,因为我们无法确定是不是刚好TCP就传了一条协议,也许后面还有内容。

这样下次进来就是新的开始。

消息处理的具体方法

今天我们的方法是实现计算器,那么我们就得规定具体如何处理客户端传入的信息。这也就是我们如何让Request转成Response。

所以我们明确规定一进来,Response的两个参数都是0,而想要它变化,就看Switch走哪条了,并且针对除法,还设置了code的值,以及除了+-*/四种运算符外的情况。

最后返回计算结果,这既是我们的处理方法。

而我们的外部也变成了这样,先声明一个Cal方法,把它传入协议里,再把协议传入线程里,这样就实现了多层次的解决方法。

我线程一调用到GetResq函数,它就先接收客户端信息,然后利用while反复判断是否拿到一条完整报文,并不断将它写入到我们传入的容器内,然后对它反序列化得到客户端传入的信息,这时候再用Response接收我们回调后的结果,也就是Cal的运算结果。

取得运算结果后,我们还得序列化转成字符串,再加上我们的协议格式,然后给客户端发回去。

客户端部分

OK我们铺垫这么久,客户端要做的其实没啥了,就发消息罢了,上面把序列化反序列化、怎么接收怎么发回都搞定了。

所以我们先拿一下服务端的信息再创建一个套接字。

然后又是让用户输入要计算的内容,并且不是算一次就退,所以又是while死循环。

然后就让客户端的数据先初始化一下Request,再序列化它然后添加我们的格式。

格式也设置好以后就可以发出了,我们不必担心不知道发给谁的问题,底层已经给我们封装好了我们的send函数,它在你Build的时候就拿到你的套接字了。

然后就是等待服务端计算完毕,然后返回结果给我们打印了。

至于GetResponse和我们上面的GetResq它是大同小异的,因为我们的协议规定了格式,所以不论是服务端还是客户端,它的读取方式都是一样的。

同样是先收数据,因为我们不一定能一次读完一段完整的数据,所以我们的采用+=的方式。

如果没有读到完整的数据,比如半条,那我们就continue继续读呗,直到有一条完整数据。

然后老样子反序列化,这次就不用序列化了,因为我们就是客户端了。

那么我们现在再来看,我们就会发现我们的操作都是定义上三层的部分,其他层都由操作系统和协议解决了,所以,书上说TCP协议好是好但是无法完全模拟实现就是这种原因,操作系统和协议再牛,也无法统一用户的需求。

守护进程

我们先来从下往上说。

我们可以看到我们三个命令用管道联通起来,然后我已查询就查出了他们仨,我们细看PGID,这个是进程组ID,进程组由多个进程构成,进程组帮我们完成任务,一个进程组可以只有一个进程。

多个进程进程组ID以第一个为组ID,如果只有一个的话就是自己。

只要组里还有成员,进程组组就存在,即使组长GG。

进程组就像一个小团队,它内部的进程共同负责某一块任务,当然也有可能组内就一个成员。

SID,这个叫:会话ID,一个会话内可以有多个或一个进程组,我们一登录,操作系统就会给我们创建一个会话,而这个会话至少有一个进程组也就是bash,会话ID就是bash的ID。

我们进行的任意操作,都是在这个会话内创建新的进程组。

那在会话内也是分前后台进程的,输出的话无所谓,你就算100个一起输出它也能输出,用户也看得见,但是输入,只能有一个,因为你不能同时给100个进程输入命令。

所以区分为一个前台多个后台进程,输入的命令只给前台,后台不受影响。

以前的电脑有注销这个选项,注销即删除会话,然后创建新的会话,这样上一位的会话就被结束了,可是我们的服务器不能受到销毁会话的影响,所以就有了守护进程的概念。

守护进程

守护进程就是把我们的服务器迁移到新的会话,这样旧会话即使被销毁也不会影响我们的服务器了

setsid(); 这样就能创建新会话,很简单吧。

前提是它不能是进程组的组长,所以我们采取fork创建子进程,然后关闭父进程,用子进程setsid ,所以守护进程算一种孤儿进程。并且守护进程不想和标准输入输出错误产生联系,一般是直接和网络通信。

所以我们把守护进程的输入输出错误全部链到/dev/null 这个路径下的文件其实就是垃圾桶,输入的内容会被丢弃,读取的内容为空。

实现

我们看上面那么多字,其实它很简单,说白了就是子进程调用个函数把我们的服务器迁移过去罢了。

是的,它很简单就能实现,甚至系统调用都给你准备好了。

好了下一篇我们就要真正进入网络了。

相关文章:

  • 大型商用货运无人机强势入局2025深圳eVTOL展
  • 【硬核数学】2. AI如何“学习”?微积分揭秘模型优化的奥秘《从零构建机器学习、深度学习到LLM的数学认知》
  • 亚远景-ASPICE与ISO 21434在汽车电子系统开发中的应用案例
  • 微服务项目->在线oj系统(Java版 - 5)
  • 机器学习第十六讲:K-means → 自动把超市顾客分成不同消费群体
  • Java—— File详解
  • VTK|箱体切割器
  • 【git进阶】git rebase(变基)
  • 前端子项目打包集成主项目实战指南
  • SOC-ESP32S3部分:1、ESP32开发IDF-5.4环境搭建
  • RV1126多线程获取SMARTP的GOP模式数据和普通GOP模式数据
  • 第二届parloo杯的RSA_Quartic_Quandary
  • 系统架构设计师考前冲刺笔记-第3章-软件架构设计
  • Pod 节点数量
  • 【Redis】快速列表结构
  • 没有公网ip怎么端口映射外网访问?使用内网穿透可以解决
  • upload-labs通关笔记-第12关 文件上传之白名单GET法
  • 氩气模块压力异常时的维修方法,要注意仪器的安全操作规范,避免对仪器造成二次损坏
  • Python训练营打卡 Day30
  • JavaWeb:Spring配置文件( @ConfigurationProperties)
  • 达恩当选罗马尼亚总统
  • 秦洪看盘|小市值股领涨,A股交易情绪复苏
  • 抖音开展“AI起号”专项治理,整治利用AI生成低俗猎奇视频等
  • 周慧芳任上海交通大学医学院附属上海儿童医学中心党委书记
  • 国家统计局:4月全国规模以上工业增加值同比增长6.1%
  • 假冒政府机构账号卖假货?“假官号”为何屡禁不绝?媒体调查