网络世界离不开的就是通信,不管是任何框架只要是提供互联网服务就必须有通信的能力。作为业务开发者可能我们业务中也会遇到这样的需求,或者是有啥好的创意性框架,但是无奈现在通信层框架都太厚重了,想要快速学习使用显然不太可能,所以呢 本文就是教你如何来实现一个封装一个通信层框架。
当看完本篇你将会对Java技术有一个更深入的了解。你可以利用本文学习的内容去实现一下业务
- 自己设计一个Tomcat容器;
- 自己设计一款RPC框架;
- 也可以在你的应用程序内部去在启动一个通信服务。
文章后面有演示。项目github地址关注私信: 01 自动回复
一、通信框架设计要考虑的点
通信肯定是双方间的,客户端发送数据,服务端处理数据。我们日常的开发都是基于http协议的,是不用考虑服务端和客户端如何去发送数据的。因为我们理解的数据都是明文的模型,而http协议底层会将其转换成二进制数据通过TCP/IP
协议传递给服务端和客户端。而下面内容是要讨论的如何将明文数据转换成二进制数据,以及让客户端和服务端都能理解这样的二进制数据。
1. 什么是协议?以及如何去设计协议?
协议就是通信双方约定的明文和二进制数据的转换格式。客户端按照约定将明文数据转换成二进制数据,服务端按照约定将二进制数据转换成明文数据。
如我们约定读取的第一个字节是协议类型,第二个字节是序列化类型,第三个是报文长度,第四是报文内容。
1.1 客户端根据协议去构建报文
那么客户端在发数据给服务端的时候就要根据前面定的协议去拼装二进制数据。
@Override
protected void doEncode(ChannelHandlerContext ctx, RpcProtocolHeader msg, ByteBuf out) throws Exception {
//1. 获取协议类型(1个字节)
out.writeByte(msg.getProtocolType());
//2. 获取序列化类型(1个字节)
out.writeByte(msg.getSerializationType());
//3. 根据序列化类型找到数据转换器生成二进制数据
Serializer serializer = SerializeEnum.ofByType(msg.getSerializationType()).getSerialize().newInstance();
byte[] data = serializer.serialize(msg);
//4. 写入报文长度(4个字节)
out.writeInt(data.length);
//5. 写入报文内容(数组)
out.writeBytes(data);
}
1.2 服务端根据协议去解析报文
这里通常会遇到很多问题,比如拆包和粘包问题
- 拆包是说一个数据发送时候发送的是hello,但是服务端收到的是2次的请求,第一次是hel,第二次是lo。
- 粘包是说发了客户端发了2次hello,但是服务端收到的是在一起的hellohello,两个报文粘在一起的。如果要自定义协议就必须要解决这两个问题,如何来解决呢? 其实很简单,就是要知道,报文什么时候结束的。就像上面的说的协议,为什么要把报文长度写放到报文里面呢? 前面的协议接受到的数据最少是6个字节。
1个字节的协议类型,1个字节的序列化类型,4个字节的数据长度,剩下的是数据包内容。
当服务端在处理二进制数据时候如果发现可读的字节不到6个字节,那肯定说明报文不完整,就先不处理,等待报文都到了在处理。如果到了6个字节,那么我们肯定就能知道真正的报文长度是多少,然后在读取真正的报文内容,就能知道什么时候报文是结束了。如果报文真正内容不够,继续等待,等待数据都到齐。
@Override
protected void doDecode(ChannelHandlerContext ctx, ByteBuf inByteBuf, List out) throws Exception {
byte[] dataArr;
//1. 不可读就关闭
if (!inByteBuf.isReadable()) {
Channel channel = ctx.channel();
SocketAddress socketAddress = channel.remoteAddress();
channel.close();
System.err.println(">>>>>&