自定义 TCP 协议

Posted 百果科技研发团队

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自定义 TCP 协议相关的知识,希望对你有一定的参考价值。

自定义 TCP 协议

2019/01/18

自定义 TCP 协议
自定义 TCP 协议
自定义 TCP 协议
自定义 TCP 协议

01

TCP回顾

    TCP 是一种面向连接的、可靠的、基于字节流的传输层通信协议。从这句话中我们可以看出有以下一些重要信息。

    面向连接:通过 ip,端口等进行握手验证连接等。

    可靠:数据一定可以传输到对端。

    基于字节流:本文讨论的重点,怎么发,怎么收等。

    下图 TCP 报文格式,数据就是指我们真正传输的东西.。

自定义 TCP 协议

02

网络字节序

    网络上传输的数据都是字节流,对于一个多字节数值,在进行网络传输的时候,先传递哪个字节?也就是说,当接收端收到第一个字节的时候,它将这个字节作为高位字节还是低位字节处理,是一个比较有意义的问题。

    大端:

    小端:

    标准化结构即是网络字节序(在网络传输过程中的结构,一般而言指的是大端字节序)


03

什么是协议

    我们都知道网络传输中数据包的格式一定都是 010101 的二进制格式。

自定义 TCP 协议

    协议指的就是客户端与服务端事先商量好的,每一个二进制数据包中每一段字节分别代表什么含义的规则。


04

为什么要自定义协议

    1、互联网上充斥着各种各样的网络服务,在对外提供网络服务时,服务端和客户端需要遵循同一套数据通讯协议,才能正常的进行通讯;就好像你跟台湾人沟通用闽南语,跟广东人沟通就用粤语一样。

    2、实现自己的应用功能时,已知的知名协议(http,smtp,ftp等)在安全性、可扩展性等方面不能满足需求,从而需要设计并实现自己的应用层协议。   


05

协议的设计

    如下图的一个简单的登录协议:

自定义 TCP 协议

    在这个数据包中,第一个字节为 1 表示这是一个登录指令,接下来是用户名和密码,这两个值以 分割,客户端发送这段二进制数据包到服务端,服务端就能根据这个协议来取出用户名密码,进行登录逻辑。实际的通信协议设计中,我们会考虑更多细节,要比这个稍微复杂一些。

    那么,协议设计好之后,客户端与服务端的通信过程又是怎样的呢?

    

    1. 首先,客户端把一个 Java 对象按照通信协议转换成二进制数据包。

       

    2. 然后通过网络,把这段二进制数据包发送到服务端,数据的传输过程由 TCP/IP 协议负责数据的传输,与我们的应用层无关。

       

    3. 服务端接受到数据之后,按照协议取出二进制数据包中的相应字段,包装成 Java 对象,交给应用逻辑处理。

       

    4. 服务端处理完之后,如果需要吐出响应给客户端,那么按照相同的流程进行。


06

粘包问题

    粘包现象:发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾

    解决方案:拆包

    常见拆包逻辑:

    1、每条数据有固定的格式(开始符、结束符),这种方法简单易行,但选择开始符和结束符的时候一定要注意每条数据的内部一定不能出现开始符或结束符

    发送每条数据的时候,将数据的长度一并发送,比如可以选择每条数据的前 4 位是数据的长度,应用层处理时可以根据长度来判断每条数据的开始和结束。


07

通用的设计

    1、首先,第一个字段是魔数,通常情况下为固定的几个字节(我们这边规定为 4 个字节)。 为什么需要这个字段,而且还是一个固定的数?假设我们在服务器上开了一个端口,比如 80 端口,如果没有这个魔数,任何数据包传递到服务器,服务器都会根据自定义协议来进行处理,包括不符合自定义协议规范的数据包。例如,我们直接通过 http://服务器  ip 来访问服务器(默认为 80 端口), 服务端收到的是一个标准的 HTTP 协议数据包,但是它仍然会按照事先约定好的协议来处理 HTTP 协议,显然,这是会解析出错的。而有了这个魔数之后,服务端首先取出前面四个字节进行比对,能够在第一时间识别出这个数据包并非是遵循自定义协议的,也就是无效数据包,为了安全考虑可以直接关闭连接以节省资源。在 Java 的字节码的二进制文件中,开头的 4 个字节为 0xcafebabe 用来标识这是个字节码文件,亦是异曲同工之妙。


    2、接下来一个字节为版本号,通常情况下是预留字段,用于协议升级的时候用到,有点类似 TCP 协议中的一个字段标识是 IPV4 协议还是 IPV6 协议,大多数情况下,这个字段是用不到的,不过为了协议能够支持升级,我们还是先留着。


    3、第三部分,序列化算法表示如何把 Java 对象转换二进制数据以及二进制数据如何转换回 Java 对象,比如 Java 自带的序列化,json,hessian 等序列化方式。


    4、第四部分的字段表示指令,关于指令相关的介绍,我们在前面已经讨论过,服务端或者客户端每收到一种指令都会有相应的处理逻辑,这里,我们用一个字节来表示,最高支持 256 种指令,对于我们这个 IM 系统来说已经完全足够了。


    5、接下来的字段为数据部分的长度,占四个字节。


    6、最后一个部分为数据内容,每一种指令对应的数据是不一样的,比如登录的时候需要用户名密码,收消息的时候需要用户标识和具体消息内容等等。


08

通过 Netty 实现协议

    具体请参考 github 源码:https://github.com/yaxinjw/flash-netty.git

    分支:拆包与粘包解决方案!

    请读者自行研究代码,注释较全。

END


    1. 通信协议是为了服务端与客户端交互,双方协商出来的满足一定规则的二进制数据格式。

       

    2. 介绍了一种通用的通信协议的设计,包括魔数、版本号、序列化算法标识、指令、数据长度、数据几个字段,该协议能够满足绝大多数的通信场景。

       

    3. Java 对象以及序列化,目的就是实现 Java 对象与二进制数据的互转。

       

    4. 最后,我们依照我们设计的协议以及 ByteBuf 的 API 实现了通信协议,这个过程称为编解码过程。



End

致力于为更多的程序员提供更好的技术



以上是关于自定义 TCP 协议的主要内容,如果未能解决你的问题,请参考以下文章

TCP通讯socket自定义协议的实现

Java TCP/IP Socket构建和解析自定义协议消息(含代码)

第13章 TCP编程_基于自定义协议的多线程模型

实现 memcached 客户端:TCP连接池一致性哈希自定义协议

Netty框架之编解码机制二(自定义协议)

Netty框架之编解码机制二(自定义协议)