范文健康探索娱乐情感热点
投稿投诉
热点动态
科技财经
情感日志
励志美文
娱乐时尚
游戏搞笑
探索旅游
历史星座
健康养生
美丽育儿
范文作文
教案论文

从RedisHTTP协议,看Nett协议设计,我发现了个惊天大秘密

  1. 协议的作用
  TCP/IP 中消息传输基于流的方式,没有边界
  协议的目的就是划定消息的边界,制定通信双方要共同遵守的通信规则2. Redis 协议
  如果我们要向 Redis 服务器发送一条 set name Nyima 的指令,需要遵守如下协议// 该指令一共有3部分,每条指令之后都要添加回车与换行符 *3r  // 第一个指令的长度是3 $3r  // 第一个指令是set指令 setr  // 下面的指令以此类推 $4r  namer  $5r  Nyimar  复制代码
  客户端代码如下public class RedisClient {     static final Logger log = LoggerFactory.getLogger(StudyServer.class);     public static void main(String[] args) {         NioEventLoopGroup group =  new NioEventLoopGroup();         try {             ChannelFuture channelFuture = new Bootstrap()                     .group(group)                     .channel(NioSocketChannel.class)                     .handler(new ChannelInitializer() {                         @Override                         protected void initChannel(SocketChannel ch) {                             // 打印日志                             ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));                             ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {                                 @Override                                 public void channelActive(ChannelHandlerContext ctx) throws Exception {                                     // 回车与换行符                                     final byte[] LINE = {"r"," "};                                     // 获得ByteBuf                                     ByteBuf buffer = ctx.alloc().buffer();                                     // 连接建立后,向Redis中发送一条指令,注意添加回车与换行                                     // set name Nyima                                     buffer.writeBytes("*3".getBytes());                                     buffer.writeBytes(LINE);                                     buffer.writeBytes("$3".getBytes());                                     buffer.writeBytes(LINE);                                     buffer.writeBytes("set".getBytes());                                     buffer.writeBytes(LINE);                                     buffer.writeBytes("$4".getBytes());                                     buffer.writeBytes(LINE);                                     buffer.writeBytes("name".getBytes());                                     buffer.writeBytes(LINE);                                     buffer.writeBytes("$5".getBytes());                                     buffer.writeBytes(LINE);                                     buffer.writeBytes("Nyima".getBytes());                                     buffer.writeBytes(LINE);                                     ctx.writeAndFlush(buffer);                                 }                              });                         }                     })                     .connect(new InetSocketAddress("localhost", 6379));             channelFuture.sync();             // 关闭channel             channelFuture.channel().close().sync();         } catch (InterruptedException e) {             e.printStackTrace();         } finally {             // 关闭group             group.shutdownGracefully();         }     } } 复制代码
  控制台打印结果1600 [nioEventLoopGroup-2-1] DEBUG io.netty.handler.logging.LoggingHandler  - [id: 0x28c994f1, L:/127.0.0.1:60792 - R:localhost/127.0.0.1:6379] WRITE: 34B          +-------------------------------------------------+          |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f | +--------+-------------------------------------------------+----------------+ |00000000| 2a 33 0d 0a 24 33 0d 0a 73 65 74 0d 0a 24 34 0d |*3..$3..set..$4.| |00000010| 0a 6e 61 6d 65 0d 0a 24 35 0d 0a 4e 79 69 6d 61 |.name..$5..Nyima| |00000020| 0d 0a                                           |..              | +--------+-------------------------------------------------+----------------+ 复制代码
  Redis 中查询执行结果
  3. HTTP 协议
  HTTP 协议在请求行请求头中都有很多的内容,自己实现较为困难,可以使用 HttpServerCodec 作为服务器端的解码器与编码器,来处理 HTTP 请求// HttpServerCodec 中既有请求的解码器 HttpRequestDecoder 又有响应的编码器 HttpResponseEncoder // Codec(CodeCombine) 一般代表该类既作为 编码器 又作为 解码器 public final class HttpServerCodec extends CombinedChannelDuplexHandler         implements HttpServerUpgradeHandler.SourceCodec 复制代码
  服务器代码public class HttpServer {     static final Logger log = LoggerFactory.getLogger(StudyServer.class);      public static void main(String[] args) {         NioEventLoopGroup group = new NioEventLoopGroup();         new ServerBootstrap()                 .group(group)                 .channel(NioServerSocketChannel.class)                 .childHandler(new ChannelInitializer() {                     @Override                     protected void initChannel(SocketChannel ch) {                         ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));                         // 作为服务器,使用 HttpServerCodec 作为编码器与解码器                         ch.pipeline().addLast(new HttpServerCodec());                         // 服务器只处理HTTPRequest                         ch.pipeline().addLast(new SimpleChannelInboundHandler() {                             @Override                             protected void channelRead0(ChannelHandlerContext ctx, HttpRequest msg) {                                 // 获得请求uri                                 log.debug(msg.uri());                                  // 获得完整响应,设置版本号与状态码                                 DefaultFullHttpResponse response = new DefaultFullHttpResponse(msg.protocolVersion(), HttpResponseStatus.OK);                                 // 设置响应内容                                 byte[] bytes = "

Hello, World!

".getBytes(StandardCharsets.UTF_8); // 设置响应体长度,避免浏览器一直接收响应内容 response.headers().setInt(CONTENT_LENGTH, bytes.length); // 设置响应体 response.content().writeBytes(bytes); // 写回响应 ctx.writeAndFlush(response); } }); } }) .bind(8080); } } 复制代码   服务器负责处理请求并响应浏览器。所以只需要处理 HTTP 请求即可// 服务器只处理HTTPRequest ch.pipeline().addLast(new SimpleChannelInboundHandler() 复制代码   获得请求后,需要返回响应给浏览器。需要创建响应对象 DefaultFullHttpResponse,设置 HTTP 版本号及状态码,为避免浏览器获得响应后,因为获得 CONTENT_LENGTH 而一直空转,需要添加 CONTENT_LENGTH 字段,表明响应体中数据的具体长度// 获得完整响应,设置版本号与状态码 DefaultFullHttpResponse response = new DefaultFullHttpResponse(msg.protocolVersion(), HttpResponseStatus.OK); // 设置响应内容 byte[] bytes = "

Hello, World!

".getBytes(StandardCharsets.UTF_8); // 设置响应体长度,避免浏览器一直接收响应内容 response.headers().setInt(CONTENT_LENGTH, bytes.length); // 设置响应体 response.content().writeBytes(bytes); 复制代码   运行结果   浏览器   控制台// 请求内容 1714 [nioEventLoopGroup-2-2] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x72630ef7, L:/0:0:0:0:0:0:0:1:8080 - R:/0:0:0:0:0:0:0:1:55503] READ: 688B +-------------------------------------------------+ | 0 1 2 3 4 5 6 7 8 9 a b c d e f | +--------+-------------------------------------------------+----------------+ |00000000| 47 45 54 20 2f 66 61 76 69 63 6f 6e 2e 69 63 6f |GET /favicon.ico| |00000010| 20 48 54 54 50 2f 31 2e 31 0d 0a 48 6f 73 74 3a | HTTP/1.1..Host:| |00000020| 20 6c 6f 63 61 6c 68 6f 73 74 3a 38 30 38 30 0d | localhost:8080.| |00000030| 0a 43 6f 6e 6e 65 63 74 69 6f 6e 3a 20 6b 65 65 |.Connection: kee| |00000040| 70 2d 61 6c 69 76 65 0d 0a 50 72 61 67 6d 61 3a |p-alive..Pragma:| .... // 响应内容 1716 [nioEventLoopGroup-2-2] DEBUG io.netty.handler.logging.LoggingHandler - [id: 0x72630ef7, L:/0:0:0:0:0:0:0:1:8080 - R:/0:0:0:0:0:0:0:1:55503] WRITE: 61B +-------------------------------------------------+ | 0 1 2 3 4 5 6 7 8 9 a b c d e f | +--------+-------------------------------------------------+----------------+ |00000000| 48 54 54 50 2f 31 2e 31 20 32 30 30 20 4f 4b 0d |HTTP/1.1 200 OK.| |00000010| 0a 43 6f 6e 74 65 6e 74 2d 4c 65 6e 67 74 68 3a |.Content-Length:| |00000020| 20 32 32 0d 0a 0d 0a 3c 68 31 3e 48 65 6c 6c 6f | 22....

Hello| |00000030| 2c 20 57 6f 72 6c 64 21 3c 2f 68 31 3e |, World!

| +--------+-------------------------------------------------+----------------+ 复制代码4. 自定义协议   组成要素魔数:用来在第一时间判定接收的数据是否为无效数据包版本号:可以支持协议的升级序列化算法:消息正文到底采用哪种序列化反序列化方式如:json、protobuf、hessian、jdk指令类型:是登录、注册、单聊、群聊… 跟业务相关请求序号:为了双工通信,提供异步能力正文长度消息正文编码器与解码器public class MessageCodec extends ByteToMessageCodec { @Override protected void encode(ChannelHandlerContext ctx, Message msg, ByteBuf out) throws Exception { // 设置魔数 4个字节 out.writeBytes(new byte[]{"N","Y","I","M"}); // 设置版本号 1个字节 out.writeByte(1); // 设置序列化方式 1个字节 out.writeByte(1); // 设置指令类型 1个字节 out.writeByte(msg.getMessageType()); // 设置请求序号 4个字节 out.writeInt(msg.getSequenceId()); // 为了补齐为16个字节,填充1个字节的数据 out.writeByte(0xff); // 获得序列化后的msg ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(msg); byte[] bytes = bos.toByteArray(); // 获得并设置正文长度 长度用4个字节标识 out.writeInt(bytes.length); // 设置消息正文 out.writeBytes(bytes); } @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List out) throws Exception { // 获取魔数 int magic = in.readInt(); // 获取版本号 byte version = in.readByte(); // 获得序列化方式 byte seqType = in.readByte(); // 获得指令类型 byte messageType = in.readByte(); // 获得请求序号 int sequenceId = in.readInt(); // 移除补齐字节 in.readByte(); // 获得正文长度 int length = in.readInt(); // 获得正文 byte[] bytes = new byte[length]; in.readBytes(bytes, 0, length); ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes)); Message message = (Message) ois.readObject(); // 将信息放入List中,传递给下一个handler out.add(message); // 打印获得的信息正文 System.out.println("===========魔数==========="); System.out.println(magic); System.out.println("===========版本号==========="); System.out.println(version); System.out.println("===========序列化方法==========="); System.out.println(seqType); System.out.println("===========指令类型==========="); System.out.println(messageType); System.out.println("===========请求序号==========="); System.out.println(sequenceId); System.out.println("===========正文长度==========="); System.out.println(length); System.out.println("===========正文==========="); System.out.println(message); } } 复制代码编码器与解码器方法源于父类 ByteToMessageCodec,通过该类可以自定义编码器与解码器, 泛型类型为被编码与被解码的类。此处使用了自定义类 Message,代表消息public class MessageCodec extends ByteToMessageCodec 复制代码编码器负责将附加信息与正文信息写入到 ByteBuf 中,其中附加信息总字节数最好为 2n,不足需要补齐。正文内容如果为对象,需要通过序列化将其放入到 ByteBuf 中解码器负责将 ByteBuf 中的信息取出,并放入 List 中,该 List 用于将信息传递给下一个 handler   编写测试类public class TestCodec { static final org.slf4j.Logger log = LoggerFactory.getLogger(StudyServer.class); public static void main(String[] args) throws Exception { EmbeddedChannel channel = new EmbeddedChannel(); // 添加解码器,避免粘包半包问题 channel.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024, 12, 4, 0, 0)); channel.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG)); channel.pipeline().addLast(new MessageCodec()); LoginRequestMessage user = new LoginRequestMessage("Nyima", "123"); // 测试编码与解码 ByteBuf byteBuf = ByteBufAllocator.DEFAULT.buffer(); new MessageCodec().encode(null, user, byteBuf); channel.writeInbound(byteBuf); } } 复制代码测试类中用到了 LengthFieldBasedFrameDecoder,避免粘包半包问题通过 MessageCodec 的 encode 方法将附加信息与正文写入到 ByteBuf 中,通过 channel 执行入站操作。入站时会调用 decode 方法进行解码   运行结果   @Sharable 注解   为了提高 handler 的复用率,可以将 handler 创建为 handler 对象,然后在不同的 channel 中使用该 handler 对象进行处理操作LoggingHandler loggingHandler = new LoggingHandler(LogLevel.DEBUG); // 不同的channel中使用同一个handler对象,提高复用率 channel1.pipeline().addLast(loggingHandler); channel2.pipeline().addLast(loggingHandler); 复制代码   但是并不是所有的 handler 都能通过这种方法来提高复用率的,例如 LengthFieldBasedFrameDecoder。如果多个 channel 中使用同一个 LengthFieldBasedFrameDecoder 对象,则可能发生如下问题channel1 中收到了一个半包,LengthFieldBasedFrameDecoder 发现不是一条完整的数据,则没有继续向下传播此时 channel2 中也收到了一个半包,因为两个 channel 使用了同一个 LengthFieldBasedFrameDecoder,存入其中的数据刚好拼凑成了一个完整的数据包。LengthFieldBasedFrameDecoder 让该数据包继续向下传播,最终引发错误   为了提高 handler 的复用率,同时又避免出现一些并发问题,Netty 中原生的 handler 中用 @Sharable 注解来标明,该 handler 能否在多个 channel 中共享。   只有带有该注解,才能通过对象的方式被共享,否则无法被共享自定义编解码器能否使用 @Sharable 注解   这需要根据自定义的 handler 的处理逻辑进行分析   我们的 MessageCodec 本身接收的是 LengthFieldBasedFrameDecoder 处理之后的数据,那么数据肯定是完整的,按分析来说是可以添加 @Sharable 注解的   但是实际情况我们并不能添加该注解,会抛出异常信息 ChannelHandler cn.nyimac.study.day8.protocol.MessageCodec is not allowed to be shared因为 MessageCodec 继承自 ByteToMessageCodec,ByteToMessageCodec 类的注解如下   这就意味着 ByteToMessageCodec 不能被多个 channel 所共享的原因:因为该类的目标是:将 ByteBuf 转化为 Message,意味着传进该 handler 的数据还未被处理过。所以传过来的 ByteBuf 可能并不是完整的数据,如果共享则会出现问题   如果想要共享,需要怎么办呢?   继承 MessageToMessageDecoder 即可。 该类的目标是:将已经被处理的完整数据再次被处理。传过来的 Message 如果是被处理过的完整数据,那么被共享也就不会出现问题了,也就可以使用 @Sharable 注解了。实现方式与 ByteToMessageCodec 类似@ChannelHandler.Sharable public class MessageSharableCodec extends MessageToMessageCodec { @Override protected void encode(ChannelHandlerContext ctx, Message msg, List out) throws Exception { ... } @Override protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List out) throws Exception { ... } }
泰山爆红,究竟做对了什么引网友疯狂种草?记者黄寿赓王建伟刘国林薛瑞荆新年泰山现在究竟有多红?3月7日,星期二,泰山景区便对外公告,截至当日下午16时,当周星期五星期六夜间的0元门票预约量就已满额。而来自泰山景区的数据显示顾村公园樱花节开幕啦!顾村公园樱花节开幕时间3。154。15!趁着昨天天气好,一早就开车去顾村公园,可没曾想路上那么堵,41公里的距离用了一个半小时,建议没有太多东西的人,地铁更方便哦!我们从2号门进园速来,闵行这片玉兰花海美翻了春日渐暖,闵行文化公园里的玉兰花开了。刻玉玲珑吹兰芬馥,美得让人心醉。趁春光正好,来这片玉兰花海看看吧。闵行文化公园是上海种植玉兰面积最大数量最多的玉兰特色公园,现有白玉兰黄玉兰紫提前认知八大旅游陷阱,开启尽兴旅游的乐趣!每个人心中都有独属于他的诗和远方。计划着什么时候有闲暇,约上仨俩好友或是携同家人一起去踏足一个风景如画的地方。为了能有一段愉悦的旅程,你需要提防几个众所周知的旅游陷阱!1。错峰陷阱古城五福春早古城五福春早我少年時代古城的舊居是中山街70號,臨近五福巷,尚書洪範中對五福的詮釋曰五福,一曰壽,二曰富,三曰康寧,四曰攸好德,五曰考終命。壽富康寧比較好理解。攸好德是說所愛好珍惜来林芝一定要去的13个旅游景点,你都打卡了吗?(下篇)头条旅游旅游西藏西藏旅游林芝林芝旅游西藏头条这里有被称为西藏瑞士的巴松措这里是世界上落差最大的垂直地貌分布地区这里拥有丰富的森林自然资源,自然出产了大量的经济植物和药用植物。这里是山东斥巨资修建假古镇,门票免费,本地的市民最先并不看好越来越多的人们喜欢在节假日的时候旅行,放松身心的同时也能够欣赏到大江山河不同的美丽景色,而且还可以增加我们对于民俗风情的认识。在所有景区中,带有丰富历史文化底蕴的景区吸引了更多的人犹如一幅春日油画!一起领略花开中国春暖花开之季,踏青赏花时节,让我们一起领略花开中国。园博园紫花马缨丹盛开眼下,在广西南宁市园博园,5000多平方米的紫花马缨丹竞相开放。紫花马缨丹喜欢湿润温暖的环境,南宁园博园在荷美国签证面签时,你是这几种情况基本上是没有问题的很多朋友在办理美签的时候会被各种原因拒签,那么在面签时你是以下这几种情况会给你加分的,进一步促进你拿到签证。图片来源于网络1。旅游购物美国人是欢迎多金的中国人去拉动当地GDP的,但勐腊县坚持旅游业态创新打造康养旅居第二居所勐腊县森林覆盖率达到惊人的87,民风淳朴,拥有得天独厚的资源优势。我县坚持国际生态康养智慧的发展原则,聚集文游医养体学智全产业链,积极融入大滇西旅游环线建设,发展新型乡村旅游,开发数字中国通信行业核心资产!电信运营商夯实数字经济底座,受益上市公司梳理财联社3月12日讯(编辑笠晨)中国联通近日发布2022年年报,信达证券表示,公司总营收增速创近九年新高,剔除非经营性损益后,净利润规模创上市新高。中国电信周五盘中再创历史新高,年初
四月中旬A股将会突破4000点,牛市第一步已经跨出!其实目前A股市场的大致方向已经定调,开门红已经没有任何悬念了!因为这两天不管是外围还是港股都有良好的表现,而我们却已经在节前持仓拿了先手,静待节后红包到来后再做轮动节奏放大利润罢了为争先高质量发展,这个开发区一口气推出53条新政1月28日,春节假期后上班首日,东营经济技术开发区马不停蹄地召开了企业高质量发展服务年动员大会,并围绕服务企业高质量发展主题,推出了东营经济技术开发区2023年支持企业高质量发展政2023将是炒房客的灾难年!房价下跌已经是板上钉钉的事情了,为什么这样说呢,从几个点可以分析的出来,第一,国家出台了那么多的政策去刺激楼市,可是楼市却没有任何的起色,开发商各种打折甚至是零首付,去化也非常的差武汉2023年重大项目清单出炉,总投资4。5万亿武汉2023年重大项目清单出炉,共有1154个项目入列,总投资达到4。5万亿元。兔年开工第一天,第一财经记者从武汉市发改委获悉,武汉今年将实施项目投资攻坚年行动,全市全年共有115存款新增近18万亿,揭开年轻人的另一面,唯品会上早有征兆风向变了,这届年轻人越来越理性务实。近期央行公布了2022第四季度的调查报告,在消费投资和储蓄意愿上,有6成以上居民倾向于更多储蓄,同比增加3。7个百分点。并且这是2022年央行第从平均工资看居民消费,推动经济发展当务之急需要控制贫富差距大家都知道,2023年经济工作重点就是建立内循环为主的国内循环。甚至有人说,这是中国经济发展的出路。那什么是内循环为主的国内循环呢?通俗点说,就是提振居民消费水平,让大家多消费花钱快讯!2022年泉州民营企业纳税排名出炉!1月27日,泉州市民营经济发展大会在泉州隆重召开,千余名企业家金融机构和行业协会负责人等及市县两级四套班子领导等在主分会场,围绕传承弘扬晋江经验,勇当实施新时代民营经济强省战略主力正确展望楼市房住不炒与鼓励住房消费是否矛盾我与宪法40年不少自媒体,或者专家展望楼市,其倾向为,住宅商品房还有涨价的空间不讨论商品房的定价根据,房住不炒政策可能被解读为鼓励住房消费。住宅商品房库存大量增加不仅浪费社会财富,歼20首飞地成都青羊区聚焦航空先进制造业,2023年航空产业营收破880亿1月28日上午,成都青羊区召开聚焦建圈强链推进先进制造业高质量发展大会,全区航空产业链主企业和规模以上工业企业代表到场。会上提出,2023年,青羊航空产业营收要突破880亿元,招引天赐材料2022年净利润同比预增超1。5倍集微网消息,近日,天赐材料发布业绩预告称,2022年归属于上市公司股东的净利润为555,000万元595,000万元,比上年同比增长151。32169。43扣除非经常性损益后的净利以数智技术为底座京东云助力绘就链上产业新图景新年新气象。从智能工厂到田间地头,从云购年货到游子归乡,从技术保障到温暖守护,京东云数智技术助力产业新春开门红,在升腾的烟火气中,描绘生机勃勃的产业新图景。01虚拟工厂为智能制造加