6. 协议设计与解析

6.1 为什么需要协议?

TCP/IP 中消息传输基于流的方式,没有边界。

协议的目的就是划定消息的边界,制定通信双方要共同遵守的通信规则

例如:在网络上传输

下雨天留客天留我不留

是中文一句著名的无标点符号句子,在没有标点符号情况下,这句话有数种拆解方式,而意思却是完全不同,所以常被用作讲述标点符号的重要性

一种解读

下雨天留客,天留,我不留

另一种解读

下雨天,留客天,留我不?留

如何设计协议呢?其实就是给网络传输的信息加上“标点符号”。但通过分隔符来断句不是很好,因为分隔符本身如果用于传输,那么必须加以区分。因此,下面一种协议较为常用

定长字节表示内容长度 + 实际内容

例如,假设一个中文字符长度为 3,按照上述协议的规则,发送信息方式如下,就不会被接收方弄错意思了

0f下雨天留客
06天留
09我不留

6.2 redis 协议举例

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;import java.nio.charset.Charset;/*** @author chenxc* @date 2021/9/7 22:26*/
@Slf4j
public class TestRedis {public static void main(String[] args) {//回车、换行NioEventLoopGroup worker = new NioEventLoopGroup();byte[] LINE = {13, 10};try {Bootstrap bootstrap = new Bootstrap();bootstrap.channel(NioSocketChannel.class);bootstrap.group(worker);bootstrap.handler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) {ch.pipeline().addLast(new LoggingHandler());ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {// 会在连接 channel 建立成功后,会触发 active 事件@Overridepublic void channelActive(ChannelHandlerContext ctx) {set(ctx);get(ctx);}private void get(ChannelHandlerContext ctx) {ByteBuf buf = ctx.alloc().buffer();buf.writeBytes("*2".getBytes());buf.writeBytes(LINE);buf.writeBytes("$3".getBytes());buf.writeBytes(LINE);buf.writeBytes("get".getBytes());buf.writeBytes(LINE);buf.writeBytes("$3".getBytes());buf.writeBytes(LINE);buf.writeBytes("aaa".getBytes());buf.writeBytes(LINE);ctx.writeAndFlush(buf);}private void set(ChannelHandlerContext ctx) {ByteBuf buf = ctx.alloc().buffer();buf.writeBytes("*3".getBytes());buf.writeBytes(LINE);buf.writeBytes("$3".getBytes());buf.writeBytes(LINE);buf.writeBytes("set".getBytes());buf.writeBytes(LINE);buf.writeBytes("$3".getBytes());buf.writeBytes(LINE);buf.writeBytes("aaa".getBytes());buf.writeBytes(LINE);buf.writeBytes("$3".getBytes());buf.writeBytes(LINE);buf.writeBytes("bbb".getBytes());buf.writeBytes(LINE);ctx.writeAndFlush(buf);}@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {ByteBuf buf = (ByteBuf) msg;System.out.println(buf.toString(Charset.defaultCharset()));}});}});ChannelFuture channelFuture = bootstrap.connect("localhost", 6379).sync();channelFuture.channel().closeFuture().sync();} catch (InterruptedException e) {log.error("client error", e);} finally {worker.shutdownGracefully();}}}

6.3 http 协议举例

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.http.*;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import lombok.extern.slf4j.Slf4j;import java.nio.charset.StandardCharsets;/*** @author chenxc* @date 2021/9/1 22:48*/
@Slf4j
public class TestHttp {public static void main(String[] args){final NioEventLoopGroup boos = new NioEventLoopGroup(1);final NioEventLoopGroup worker = new NioEventLoopGroup();try {final ServerBootstrap bootstrap = new ServerBootstrap();bootstrap.group(boos,worker);bootstrap.channel(NioServerSocketChannel.class);bootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {@Overrideprotected void initChannel(NioSocketChannel ch) throws Exception {ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));//添加http协议 编、解码器ch.pipeline().addLast(new HttpServerCodec());//只有消息的类型为HttpRequest才会进入这个handler中ch.pipeline().addLast(new SimpleChannelInboundHandler<HttpRequest>() {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, HttpRequest msg) throws Exception {// 获取请求log.debug("{}",msg.uri());  //请求地址log.debug("{}",msg.headers());  //请求头//返回响应DefaultFullHttpResponse response = new DefaultFullHttpResponse(msg.protocolVersion(), HttpResponseStatus.OK);final byte[] bytes = "<h1>Hello world</h1>".getBytes(StandardCharsets.UTF_8);response.headers().setInt(HttpHeaderNames.CONTENT_LENGTH,bytes.length);response.content().writeBytes(bytes);ctx.writeAndFlush(response);}});/*ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {if (msg instanceof HttpRequest){  //请求行 请求头}else if (msg instanceof HttpContent){  //请求体}}});*/}});ChannelFuture channelFuture = bootstrap.bind(8080).sync();channelFuture.channel().closeFuture().sync();} catch (InterruptedException e) {log.error("server error {}", e.getLocalizedMessage());} finally {boos.shutdownGracefully();worker.shutdownGracefully();}}}

6.4 自定义协议要素

  • 魔数,用来在第一时间判定是否是无效数据包
  • 版本号,可以支持协议的升级
  • 序列化算法,消息正文到底采用哪种序列化反序列化方式,可以由此扩展,例如:json、protobuf、hessian、jdk
  • 指令类型,是登录、注册、单聊、群聊… 跟业务相关
  • 请求序号,为了双工通信,提供异步能力
  • 正文长度
  • 消息正文

编解码器

根据上面的要素,设计一个登录请求消息和登录响应消息,并使用 Netty 完成收发

import com.itcxc.message.Message;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageCodec;
import lombok.extern.slf4j.Slf4j;import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.List;/*** 必须和LengthFieldBasedFrameDecoder一起使用,确保接到的byteBuf消息是完整的* @author chenxc* @date 2021/9/9 23:35*/
@Slf4j
@ChannelHandler.Sharable
public class MessageCodecSharable extends MessageToMessageCodec<ByteBuf, Message> {@Overrideprotected void encode(ChannelHandlerContext ctx, Message msg, List<Object> outList) throws Exception {final ByteBuf out = ctx.alloc().buffer();//1、  5个字节的魔术out.writeBytes(new byte[]{'i','t','c','x','c'});//2、  1个字节的版本out.writeByte(1);//3、  1个字节的系列化算法 jdk-0 json-1out.writeByte(0);//4、  1个字节的指令类型out.writeByte(msg.getMessageType());//5、  4个字节的请求序列out.writeInt(msg.getSequenceId());//  系列化对象ByteArrayOutputStream bos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(bos);oos.writeObject(msg);final byte[] bytes = bos.toByteArray();//6、  4个字节的正文长度out.writeInt(bytes.length);//7、  正文out.writeBytes(bytes);outList.add(out);}@Overrideprotected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {byte[] magcNum = new byte[5];in.readBytes(magcNum,0, 5);String res = new String(magcNum);byte version = in.readByte();byte serializableType = in.readByte();byte messageType = in.readByte();int sequenceId = in.readInt();int length = in.readInt();byte[] bytes = new byte[length];in.readBytes(bytes,0, length);Message msg = null;if (serializableType == 0){ByteArrayInputStream bis = new ByteArrayInputStream(bytes);ObjectInputStream ois = new ObjectInputStream(bis);msg = (Message) ois.readObject();}log.debug("{},{},{},{},{},{}",res,version,serializableType,messageType,sequenceId,length);log.debug("{}",msg);out.add(msg);}
}

测试

import com.itcxc.message.LoginRequestMessage;
import io.netty.buffer.ByteBuf;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;/*** @author chenxc* @date 2021/9/8 22:45*/
public class TestMessageCodec {public static void main(String[] args) {EmbeddedChannel channel = new EmbeddedChannel(new LoggingHandler(LogLevel.DEBUG),new LengthFieldBasedFrameDecoder(1024,12,4,0,0),new MessageCodec());LoginRequestMessage message = new LoginRequestMessage("shangsan","123");channel.writeOutbound(message);ByteBuf buf = channel.readOutbound();//半包测试final ByteBuf buf1 = buf.slice(0, 100);buf1.retain();final ByteBuf buf2 = buf.slice(100, buf.readableBytes() - 100);buf2.retain();channel.writeInbound(buf);  //引用指数会减1channel.writeInbound(buf1);channel.writeInbound(buf2);}
}

解读

Netty 基础-协议设计与解析相关推荐

  1. Netty协议设计与解析

    Netty协议设计与解析 1. 为什么需要协议? TCP/IP 中消息传输基于流的方式,没有边界. 协议的目的就是划定消息的边界,制定通信双方要共同遵守的通信规则 例如:在网络上传输 下雨天留客天留我 ...

  2. 04、Netty学习笔记—(黏包半包及协议设计解析)

    文章目录 一.粘包与半包 1.1.现象分析 1.1.1.粘包.半包情况分析 1.1.2.滑动窗口.MSS限制.Nagle算法介绍 1.2.粘包.半包现象复现 1.2.1.粘包复现 1.2.2.半包复现 ...

  3. 蚂蚁通讯框架SOFABolt之私有通讯协议设计

    前言 SOFABolt 是蚂蚁金融服务集团开发的一套基于 Netty 实现的网络通信框架. 为了让 Java 程序员能将更多的精力放在基于网络通信的业务逻辑实现上,而不是过多的纠结于网络底层 NIO ...

  4. Netty系列之Netty基础概念与组件

    什么是Netty,Netty各个组件介绍 本部分转载自 Java技术债务[什么是Netty?为什么使用Netty?Netty有哪些组件?] 原文链接:https://blog.csdn.net/qq_ ...

  5. 从零实现RPC框架之:4协议设计

    前言 一提到协议,最先想到的可能是 TCP 协议.UDP 协议等等,这些网络传输协议的实现以及应用层的HTTP协议. 其实rpc协议和http协议都属于应用层协议 可能你会问:"前面你不是说 ...

  6. 浅谈基于TCP和UDP的协议设计

    From:http://blog.sina.com.cn/s/blog_48d4cf2d0101859x.html 一个基于TCP/WebSockets的超级精简的长连接消息协议:https://st ...

  7. Netty 高性能架构设计

    Netty 高性能架构设计 Netty 概述 原生 NIO 存在的问题 Netty 官网说明 Netty 的优点 Netty 版本说明 线程模型基本介绍 传统阻塞 I/O 服务模型 Reactor 模 ...

  8. 开源项目SMSS发开指南(三)——protobuf协议设计

    本文的第一部分将介绍protobuf使用基础以及如何利用protobuf设计通信协议.第二部分会给出smss项目的协议设计规范和源码讲解. 一.Protobuf使用基础 什么是protobuf pro ...

  9. 兼具高效与易用,融云 IM 即时通讯长连接协议设计思路

    无论是 PC 端还是移动端,接入网络实现通信都需要建立双端的连接.关注[融云全球互联网通信云]了解更多 客户端和服务端建立连接后不断开,然后进行通信(也就是发送报文)的方式就是长连接. 与之相反,短连 ...

最新文章

  1. 图灵七月书讯【Cassandra权威指南将在7月末上市】
  2. mysql 锁-比较详细、深入的介绍
  3. Training—Capturing Photos
  4. 类和对象—对象特性—函数的分类和调用
  5. ZOJ 3962 Seven Segment Display(数位DP)题解
  6. 蓝桥杯——输出米字形
  7. java 链表_java数据结构与算法之顺序表与链表深入分析(一)
  8. 海蜘蛛理由器做端口映射
  9. 再探Linux内核write系统调用操作的原子性
  10. sklearn kfold_sklearn函数:cross_val_score(交叉验证评分)
  11. 云WAF之语义分析引擎
  12. ActiveX控件的注册方法
  13. 三菱四节传送带控制梯形图_四节传送带控制
  14. 【c语言数学函数库】
  15. Linux 拷贝文件
  16. 2003- cant connect to MYSQL server on localhost(10061)
  17. bootstrap可视化布局(免费,自定义,方便下载)网页自定义,后台、前端页面自定义
  18. 人类Humankind for Mac(历史战略游戏)
  19. java语言多态性的表现形式_[Java教程]多态性的表现形式
  20. 【工具使用系列】关于 MATLAB Simulink 物理建模,你需要知道的事

热门文章

  1. 如何禁止WordPress站点前端显示管理工具栏?附3种方法
  2. leetcode每日一题——T70. 爬楼梯(易):斐波那契公式
  3. 为什么普通人在互联网上赚钱总是遇到坑?这是我的几条建议
  4. 一文读懂数仓建设和数据治理
  5. (程序猿专属)1024-我用代码写成浪漫情话表白你
  6. Ptyhon xlrd常用函数用法介绍
  7. python加密敏感信息_小技巧 | 用python给敏感信息加水印
  8. 2016年4月重庆计算机一级考试,2016年4月重庆市计算机一级机试题2
  9. 困惑我们人生的60个问题的答案--言简意赅的开心果,说的多好啊
  10. java·mysql编写快递e站