【网络】Socket | RPC 通信细节 知识点
创始人
2024-06-02 10:51:22
0

文章目录

  • TCP拆包粘包问题
    • 发生原因
    • 解决方案
  • 字节序 | 大小端模式 | 字节翻转
    • 【拓】网络字节序
  • 序列化器
  • 服务发现


常用的两种通信方式,一种是HTTP通信,另一种就是Socket通信

  • HTTP 是基于TCP Socket的高级封装,是一种简单易用的通信协议,使用时一般不会碰到底层问题。
  • Socket 是操作系统提供的对于传输层(TCP/UDP)抽象的接口,使用时会接触到很多底层细节。

HTTP虽然方便,但性能较低。对性能有要求时,我们就需要直接使用 Socket编程 实现通信目的,加快传输效率,例如分布式服务间、游戏C/S端的高效、低延迟的通信需求。

Socket通信大多用于远程方法调用,即RPC
目前有大量成熟可靠、高效的第三方RPC框架可供我们选择,如Dubbo、gRPC等等。这些RPC框架封装了拆包粘包、异常、超时等常见的传输细节,不需要程序员自己处理,相对得简单易用。但做为技术人员,如果长期对底层细节失去了解,对技术生涯或有不好的影响。所以本文的立意在于自己动手进行Socket编程(也可以说是手写RPC),而非借用现有的RPC库使用Socket。

直接进行Socket编程时,我们会遇到一些底层问题,以及一些可以或需要自己选择的东西(组件)。本文即旨在描述 Socket层 通信的重要事项和常见问题。


TCP拆包粘包问题

摘自:如何解决TCP拆包粘包问题

发生原因

  1. 应用程序写入的数据大于套接字缓冲区大小,这将会发生拆包。
  2. 应用程序写入数据小于套接字缓冲区大小,网卡将应用多次写入的数据发送到网络上,这将会发生粘包。
  3. 接收端不及时读取套接字缓冲区数据,这将发生粘包。

解决方案

无论拆包还是粘包,本质问题都是无法区分数据包的界限,解决包界限的问题主要有以下几种方式

1. 将数据包定长,比如定长100字节,不足补空格,接收端收到后按照100字节长度解析出的数据即为完整数据包。但这样的做的缺点是会有大量的空格,浪费部分存储空间和带宽。

2. 消息数据使用特定分割符区分界限,比如使用换行符号做分割。接收端读到该符号即知道接收到了一段完整数据
比如Java Netty中,LineBasedFrameDecoder解析器就是利用换行符号做分隔

3. 数据包用"包头"+"包体"组成

  • 包头 包含了用来描述 包体等信息 的数据,比如包体长度、消息号(包体消息类型的唯一标识,作为对包体序列化和反序列化的依据)、包体解密密钥等数据。接收端收到后根据消息头中的长度解析一段完整的数据。
    例如包头可以用一个uint16(最大能表示65535)来表示包体长度,同时也间接限制了包体不能草好过65535byte,即64k
  • 包体 即消息的主要内容,也叫消息体

字节序 | 大小端模式 | 字节翻转

字节序指的是多字节的数据(如int、float)中各字节的存储顺序
在几乎所有计算机中,多字节数据被存储为连续的字节序列。例如,一个4字节的int类型变量a,其存储的起始地址为0x100,那么a的4个字节将被分别存在0x100,0x101,0x102,0x103的位置。实际上a的最低位可以存在最前面,也可以存在最后面。即有两种不同的存储顺序,也就是大端序和小端序。
在这里插入图片描述
我们规定:①内存空间是从低地址(起始地址)到高地址排列的。②字节序的 左->右 对应的是 高位->低位。于是有:

  • 大端序/模式 Big-Endian:将高位字节存储在起始地址(低地址)。高位字节在前,低位字节在后,这也符合人类的阅读习惯。
  • 小端序/模式 Little-Endian:将低位字节存储在起始地址(低地址)。低位字节在前,高位字节在后。

(大端/小端模式也俗称大尾/小尾模式)

影响字节序的因素
1. CPU架构
字节序通常只和处理器架构有关,而和编程语言无关。比如PowerPC系列的CPU采用Big-Endian方式存储数据,而x86与x86_64系列则采用Little-Endian方式存储数据。很多的ARM、DSP都为小端模式,有些ARM处理器还可以由硬件来选择是大端模式还是小端模式。
平常大多数PC与服务器如果使用的是Intel与AMD CPU,一般都是Little-Endian
2. 有关写入字节的代码
除了处理器架构的关系外,也有一些需要程序员手写字节数据的场景。比如当我们遇到要把int转[]byte的情况时,可能会采用位移运算符的方式实现。这时也需要明确选择以大端模式写入还是小端模式写入,读取时也应使用同样的模式读取。

// 大端序
var b [4]byte
var i int32 = 65536
b[3] = byte(i)
b[2] = byte(i >> 8)
b[1] = byte(i >> 16)
b[0] = byte(i >> 24)
fmt.Println(b) // [0 1 0 0]// 小端序
var b [4]byte
var i int32 = 65536
b[0] = byte(i)
b[1] = byte(i >> 8)
b[2] = byte(i >> 16)
b[3] = byte(i >> 24)
fmt.Println(b) // [0 0 1 0]

PS:当我们使用某些二进制序列化器时,序列化器就帮我们完成了"手动写入字节数据"的过程。
Protobuf的消息总是使用小端模式。(以下是Protobuf生成的代码)
在这里插入图片描述

以上涉及到字节序的情况需要我们注意。因为计算机从字节流中取出数据,和把数据存入字节流时,如果不遵循同一种字节序,就无法取到正确的数据
所以,在读写字节数据时,应当注意明确字节序

————
那有的同学可能就要问了,既然这么麻烦,为什么不统一字节序呢?
实际上,大小端各有优劣:

  • 在计算机内部的处理都是小端字节序,因为计算都是从低位开始的,先处理低位字节效率较高
  • 而大端序存储的时候,由于符号位在高位,因此对于数据正负、大小的判断也就方便许多。另外,大端序也更符合人类的阅读习惯。

再由于各个芯片厂商的坚持,字节序的问题也就一直没有统一。

————
关于"大端"和"小端"叫法的由来,还有一则趣谈。
《程序设计实践》第9章中提到,“大端”和“小端”可以追溯到1726年的Jonathan Swift的《格列佛游记》,其中一篇讲到有两个国家因为吃鸡蛋究竟是先打破较大的一端还是先打破较小的一端而争执不休,他们分别被称为"大端(big-endian)派"和"小端(little-endian)派",因为这种争执,甚至爆发了战争,其中一个皇帝送了命,另一个丢了王位。
(看到没有,仅仅是剥鸡蛋就能产生这么大的分歧,原来“大端”和“小端”真的有这么重要!)
在这里插入图片描述
1981年10月,Danny Cohen的文章《论圣战以及对和平的祈祷》(On holy wars and a plea for peace)将这一对词语引入了计算机界。这么看来,所谓大端和小端,也就是big-endian和little-endian,其实就是从描述鸡蛋的部位而引申到计算机地址的描述,也可以说,是从一个俚语衍化来的计算机术语。稍有些英语常识的人都会知道,如果单靠字面意思来理解俚语,那是很难猜到它的正确含义的。在计算机里,对于地址的描述,很少用“大”和“小”来形容;对应地,用的更多的是“高”和“低”;很不幸地,这对术语直接按字面翻译过来就成了“大端”和“小端”,让人产生迷惑也不是很奇怪的事了。

【拓】网络字节序

主要参考 网络字节顺序
以上谈论的都是围绕"主机字节序",与之相对的还有"网络字节序"。主机字节序与CPU架构有关,而网络字节序与任何都无关
如果不使用c/c++编程,网络字节序通常不需要我们关心。

网络传输一般采用大端序,也被称之为网络字节序,或网络序。TCP/IP协议中定义大端序为网络字节序。
网络字节序是TCP/IP中的一个约定,它与具体的CPU类型、操作系统等无关,从而可以保证在不同主机之间建立连接和传输

为什么要有网络字节序?
解决好主机序的问题 仅仅能保证通信传输的业务内容可以被对方正确解析,而这些工作要先建立在网络传输的基础之上。
但是网络传输过程中不仅仅只涉及两台机器,我们无法知道通信过程中所有路由机器的主机序,所以要有一种约定的数序,而TCP/IP通过约定统一采用网络字节序(即大端序)的方式解决了这个问题。

网络字节序并不影响我们传输的字节数据,因为它们仅仅是传输内容,与TCP/IP的传输过程无关。
网络字节序仅作用于TCP/IP所关心的数据,如IP、Port等。
在网络编程时,一般用htonl()宏将本机序装换成网络序。消息传输过程中,TCP/IP总会按照大端序解析出目标IP和Port

// 相关代码片段
add.sin_family = AF_INET; // 协议簇。使用互联网际协议,即IP协议
add.sin_port = htons(11111); // 本机序转为网络序

(感觉网络字节序的问题其实是可以隐藏在Socket内的,不知道为什么没有那么做?)

Berkeley套接字定义了一组转换函数:htons(), ntohs(), htons()htonl(),位于头文件
htons和ntohs完成16位无符号数的相互转换,htonl和ntohl完成32位无符号数的相互转换。
htons和htonl用于本机序转换到网络序,ntohl和ntohs用于网络序转换到本机序。
在使用little endian的系统中,这些函数会把字节序进行转换;
在使用big endian类型的系统中,这些函数会定义成空宏;


序列化器

序列化器(即序列化库)涉及到了"如何表示数据"这一问题。
Socket通信大多用于远程方法调用,即RPC
通信的数据包括了传递给方法的参数,以及方法执行后的返回值。
进程内的方法调用,使用程序语言预置的和程序员自定义的数据类型,就很容易解决数据表示问题,远程方法调用则完全可能面临交互双方各自使用不同程序语言的情况;即使只支持一种程序语言的 RPC 协议,在不同硬件指令集、不同操作系统下,同样的数据类型也完全可能有不一样表现细节,譬如数据宽度、字节序的差异等等。有效的做法是将交互双方所涉及的数据转换为某种事先约定好的中立数据流格式来进行传输,将数据流转换回不同语言中对应的数据类型来进行使用,这个过程说起来拗口,但相信大家一定很熟悉,就是序列化与反序列化
(摘自凤凰架构-RPC-三个基本问题-如何表示数据)

常见的序列化格式有:XML、JSON、二进制等等。

  • XML和JSON 会将程序对象序列化为字符串,所以序列化后依旧是人类可读的。进行网络传输时,字符串再转为二进制流传输。
    反序列化时,字节数组转回字符串,即XML/JSON字符串,然后再用XML/JSON库转为程序对象
  • 二进制序列化 会直接将程序对象序列化为二进制,虽然序列化后人类不可读,但效率是最高的。可直接用于网络传输。
    • 如果是手写自定义的序列化,则应当有接口文档 描述接口有多少字段、字段的类型、顺序,对于不确定长度的字段(如string),要在写入值之前先把长度写入(类似于在数据包头中写入包体的长度)。反序列化时按文档描述解析即可。
      —— 这是在没有protobuf前常用的方式
    • 如果是用现成的第三方序列化器(如protobuf),则上述细节均由序列化器实现,不再需要手动解析,十分方便。

常见的序列化器,除了语言自带的原生序列化器,还有更高效的第三方序列化器,如fastjson、msgpack、protobuf等等。其中msgpack和protobuf都属于二进制序列化格式

如果不追求效率,那么直接使用json、xml这种简单易用、人类可读的序列化格式即可,并且他们还都是语言中立、平台中立的
如果追求效率,那就要选用二进制序列化格式。很多语言本身也提供有二进制序列化格式,如Java的java.io.Serializable,go的gob,但它们都无法跨语言反序列化。如果需要跨语言通信,则应当选择语言中立、平台中立的序列化。Google开发的protobuf即是比较推荐的一款


服务发现

要向某个服务器建立连接,需要知道它的ip和端口。找到服务对应的ip和端口的过程,就是服务发现。

HTTP 依靠DNS做服务发现。通过域名+DNS解析服务器的ip,默认80端口(如果要用其它端口,可以在服务端做反向代理)

而直接采用Socket通信(TCP/UDP)时,一般会使用中间件来保存服务的ip和端口,比如consul等"服务发现"中间件,甚至Redis等存储服务。

此处只简单提一下。详细可看服务发现-简书

相关内容

热门资讯

安卓系统垃圾手动清理,手动优化... 手机里的安卓系统是不是越来越慢了?是不是觉得垃圾文件越来越多,清理起来麻烦又费时?别急,今天就来给你...
安卓怎么转成苹果系统,跨平台迁... 你是不是也和我一样,手里拿着一台安卓手机,突然之间对苹果的生态系统产生了浓厚的兴趣?想要体验一下那个...
安卓手机升级系统蓝屏,安卓手机... 你有没有遇到过这种情况?手机升级系统后,屏幕突然变成了蓝屏,心里那个慌啊!别急,今天就来和你聊聊安卓...
安卓系统变ios系统教程视频,... 你是不是也和我一样,对手机系统转换充满了好奇?想要从安卓系统切换到iOS系统,但又觉得操作复杂,不知...
哪种汽车是安卓系统,探索安卓系... 你有没有想过,在我们这个智能化的时代,汽车竟然也能装上安卓系统呢?没错,你没听错,就是那个我们日常使...
完美返利系统下载安卓,轻松赚取... 你有没有听说过那个超级给力的“完美返利系统”呀?最近在朋友圈里可是火得一塌糊涂呢!今天,我就要来给你...
安卓系统传文件app,轻松实现... 你是不是也和我一样,手机里存了超多好用的安卓系统传文件app?今天就来聊聊这些小宝贝,看看它们是如何...
车载导航安卓系统大全,全面解析... 你有没有想过,开车出门的时候,如果没有了导航,那可真是寸步难行啊!现在,车载导航安卓系统可是越来越流...
部落冲突关联安卓系统,安卓系统... 亲爱的玩家们,你是否曾在《部落冲突》的世界里,挥舞着你的战旗,与成千上万的玩家并肩作战?今天,就让我...
安卓手机系统好评推荐,这些热门... 你有没有发现,现在手机市场上安卓手机真的是越来越受欢迎了呢?这不,最近我可是深入研究了各种安卓手机系...
诺基亚925刷安卓系统,深度解... 你手中的诺基亚925是不是已经有点儿老气横秋了?别急,今天就来给你来点新鲜的!咱们聊聊如何给这款经典...
安卓系统应用这么关闭,安卓应用... 手机里的安卓系统应用这么多,有时候用完一个就想赶紧关闭,免得占用太多内存。但是,你知道怎么高效地关闭...
手机wp系统怎么刷安卓系统,轻... 你有没有想过,你的手机WP系统突然变得有点儿老气横秋,想要给它来个焕然一新的变身呢?没错,就是刷上安...
安卓原生系统进程锁,守护系统稳... 你知道吗?在安卓手机的世界里,有一个神秘的守护者,它就是安卓原生系统进程锁。今天,就让我带你一探究竟...
删除安卓系统的缓存,释放手机空... 手机用久了是不是感觉越来越卡?别急,今天就来教你怎么给安卓手机来个大扫除,把那些该死的缓存通通清理掉...
安卓系统的所有游戏,尽享千款精... 你有没有发现,手机里的游戏越来越丰富了呢?尤其是安卓系统,简直就是游戏爱好者的天堂!今天,就让我带你...
安卓系统流畅度测评,深度解析各... 你有没有发现,手机用久了,有时候就像老牛拉车一样,慢吞吞的,让人心里直发慌?这不,最近我闲来无事,就...
安卓4.4系统升6.0,系统变... 你有没有发现,你的安卓手机最近有点儿“老态龙钟”了呢?别急,别急,让我来给你支个招儿,让你的安卓4....
安卓点餐系统文档,功能解析与操... 你有没有想过,点餐这件小事,竟然也能变得如此高大上?没错,就是那个我们每天都要打交道,却又常常忽略的...
途昂装安卓系统,智能驾驶体验再... 哇,你有没有想过,你的途昂汽车也能装上安卓系统?是的,你没听错,就是那个我们日常使用的安卓系统!想象...