Netty进阶协议设计与解析HttpServerCodec、自定义协议Codec,源码分析

更新时间:2023-05-06 03:23:34 阅读: 评论:0

Netty进阶协议设计与解析HttpServerCodec、⾃定义协议
Codec,源码分析
概述
我们的客户端和服务器进⾏通信的时候肯定需要遵守⼀定的协议,这些协议有可能是已经提前设计好的如Http协议,也可以是我们⾃定义的。
我们讲解了⼀些Netty给我们提供的⼀些编解码器,这是本篇⽂章的基础,否则可能看不懂。
这⼀篇主要是讲解Netty中的Http协议和我们⾃定义的协议。
1.HttpServerCodec
服务器的编解码器,遵从HTTP协议,先看如下类
⾸先看看HttpServerCodec的类结构,其中继承的CombinedChannelDuplexHandler其实就是合并了它的两个泛型的任务,就不多说了
关键的两个解码和编码类继承如下:
⼀般以Codec结尾的既可以做解码也可以编码,Decoder意为解码,Encoder意为编码。其中解码是ChannelInboundHandlerAdapter 的⼦类(即⼊站handler),专门⽤于监听客户端发来的数据给他先进⾏⼀遍解码⼯作,Encoder为ChannelOutboundHandlerAdapter的⼦类(即出站handler),即需要发送的数据先进⾏⼀遍编码操作,再发去对应的客户端。
我们只需在服务器加上如下代码即可使⽤(完整代码略过)。
//HTTP协议的编解码器
ch.pipeline().addLast(new HttpServerCodec());
演⽰解码:
⼤家可能会好奇,Codec到底会把客户端发来的信息解码为什么类型呢?是以前的ByteBuf、String?具体的细节下⾯分析
服务器加上如下代码,启动服务器,打开浏览器输⼊localhost:xxxx(绑定的端⼝),查看输出
结果如下(还有⼀些浏览器发的请求头请求⾏什么的太多了就不展⽰了)
可以看到我在浏览器只发送了⼀次请求,却打印了两次。其实是HttpServerCodec把我们的请求解析成了两部分,第⼀部分为HttpRequest它包含请求头和请求⾏,第⼆部分为HttpContent代表请求体(即使是get请求,也会有请求体,顶多没有内容⽽已)。
按照这样的逻辑我们可能以后就需要在解码后的channelRead⾥区分⼀下请求头和请求体了:
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
/
/打印出msg类信息
//    System.out.Class());
if(msg instanceof HttpRequest){  //DefaultHttpRequest的⽗接⼝为HttpRequest
//执⾏请求头的代码逻辑
}el if (msg instanceof HttpContent){//同样⽐较⽗类即可,更通⽤
//执⾏请求体的代码逻辑
}
}
但是这样似乎过于⿇烦,如果我现在只关⼼其中的⼀种,不想做很多的if...el判断,那我们可以换另⼀个⽅式进⾏简化,即SimpleChannelInboundHandler<T>:
SimpleChannelInboundHandler的泛型就是他关⼼的消息类型,如果不是指定的消息类型则会跳过该handler。可以看到重写的channelRead0⾥的msg类型就是指定的泛型
ch.pipeline().addLast(new SimpleChannelInboundHandler<HttpRequest>() {//我们这⾥只关⼼请求头
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, HttpRequest msg) throws Exception {
//业务逻辑
}
}
响应客户端:
我们服务器收到客户端的请求后当然需要回复客户端啦,根据上⾯的说明我们知道我们需要响应⼀个同样符合Http协议的响应对象给客户端,同样的写回数据时会被HttpServerCodec进⾏编码动作,先参考下⾯⼏个类
DefaultFullHttpRespon:Netty提供的给客户端响应的类如下
HTTP协议版本类参考如下:
状态码类参考(⾮常多):
演⽰:
bootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new LoggingHandler());
//HTTP协议的编解码器,
ch.pipeline().addLast(new HttpServerCodec());
//SimpleChannelInboundHandler的泛型就是他关⼼的消息格式,如果不是指定的格式则会跳过该handler
ch.pipeline().addLast(new SimpleChannelInboundHandler<HttpRequest>() {//我们这⾥只关⼼请求头
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, HttpRequest msg) throws Exception {
log.debug("请求⽅法为{}",hod());//请求⽅法为GET
log.debug("请求路径为{}",msg.uri());//请求资源路径为/
log.debug("请求头为{}",msg.headers());//请求头为DefaultHttpHeaders[Host: localhost:8081, Connection: keep-alive……]
//向客户端返回响应
//netty提供的响应对象,这⾥指定响应版本和请求时候的版本相同,响应码为200
DefaultFullHttpRespon respon=new DefaultFullHttpRespon(msg.protocolVersion(),HttpResponStatus.OK);
//content⽅法是给上⾯的respon写返回内容,返回值为ByteBuf,具体可以看源码
//也可以构造该对象时就把消息体的ByteBuf放在构造⽅法⾥
//写回响应
channelHandlerContext.writeAndFlush(respon);
}
});
}
});
结果可以看到,netty已经把该响应进⾏了编码并写回了对应的channel中,客户端也收到了该响应
但是还有个问题,就是浏览器这边⼀直在转圈加载,这是因为服务器并没有告诉它我这边已经响应完了,浏览器就以为还有数据,所以会⼀直加载,等待接收。
解决:在响应头⾥加⼀个字段content-length 为我们要发送的响应的长度,改动代码为
各种响应头⾥的字段名字信息:
浏览器正确的接收到响应信息。
另外,浏览器可能会⾃动给服务器发送图标请求
今后我们需要根据不同的请求uri,返回该客户端不同的资源。
HttpClientCodec是客户端的Http协议编解码器
2.⾃定义协议
前⾯我们讲解了常⽤的HTTP协议,如果想设计⼀套适合⾃⼰业务的协议来增强效率和减⼩浪费。
2.1⾃定义协议要素
⾃定义协议,就需要考虑如下的⼏个地⽅:
魔数:⽤来在第—时间判定是否是⽆效数据包,了解jvm的⼩伙伴可能知道,class⽂件的魔数为CAFE BABY,如果你使⽤java命令执⾏⽂件,如果class⽂件前4个字节(魔数)不是约定的CAFE BABY,jvm就不会执⾏该calss⽂件。
版本号:可以⽀持协议的升级,⽐如你的协议升级后,增加了⼏个字段,如果你想⽤旧的协议就必须按照协议的版本号进⾏区分识别。
序列化算法:消息的正⽂采⽤哪种序列化和反序列化⽅式,如json,jdk等
指令类型:是登录、注册、单聊、群聊...?跟业务相关
请求序号:为了双⼯通信,提供异步能⼒,标志每⼀个不同的请求消息
正⽂长度
消息正⽂:消息正⽂就像是后台传给前端的各种复杂的数据,我们需要⽤序列化算法来解析该数据,否则的话数据接收的时候会乱,所以⼀般前后端交互采⽤json格式进⾏编解码
有兴趣的⼩伙伴可以查看HttpMessage类,看看Netty对Http协议是如何设计的(有点复杂)。

本文发布于:2023-05-06 03:23:34,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/fan/89/859910.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:协议   请求   响应   客户端   需要   服务器   编码
相关文章
留言与评论(共有 0 条评论)
   
验证码:
推荐文章
排行榜
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图