当前位置: 首页 > news >正文

从零开始手写mmo游戏从框架到爆炸(八)— byte数组传输

导航:从零开始手写mmo游戏从框架到爆炸(零)—— 导航-CSDN博客   

Netty帧解码器

        Netty中,提供了几个重要的可以直接使用的帧解码器。

LineBasedFrameDecoder

       行分割帧解码器。适用场景:每个上层数据包,使用换行符或者回车换行符做为边界分割符。发送端发送的时候,每个数据包之间以换行符/回车换行符作为分隔。在这种场景下,只需要把这个解码器加到 pipeline 中,Netty 会使用换行分隔符,把底层帧分割成一个一个完整的应用层数据包,发送到下一站。

FixedLengthFrameDecoder

        固定长度帧解码器。适用场景:每个上层数据包的长度,都是固定的,比如 100。在这种场景下,只需要把这个解码器加到 pipeline 中,Netty 会把底层帧,拆分成一个个长度为 100 的数据包 (ByteBuf),发送到下一个 channelHandler入站处理器。

DelimiterBasedFrameDecoder

        自定义分隔符帧解码器 。DelimiterBasedFrameDecoder 是LineBasedFrameDecoder的通用版本。不同之处在于,这个解码器,可以自定义分隔符,而不是局限于换行符。如果使用这个解码器,在发送的时候,末尾必须带上对应的分隔符。

LengthFieldBasedFrameDecoder

        自定义长度帧解码器。这是一种基于灵活长度的解码器。在数据包中,加了一个长度字段(长度域),保存上层包的长度。解码的时候,会按照这个长度,进行上层ByteBuf应用包的提取。

        我们之前使用的是自定义分隔符帧解码器,现在我们改用自定义长度帧解码器。LengthFieldBasedFrameDecoder有几个参数,我们看一起看一下:

  • lengthFieldOffset 长度域的偏移量,简单而言就是偏移几个字节是长度域
  • lengthFieldLength : 长度域的所占的字节数
  • lengthAdjustment : 长度值的调整值
  • initialBytesToStrip : 需要跳过的字节数

参考:LengthFieldBasedFrameDecoder 详解-CSDN博客  

现在我们就来修改编解码器,同时修改相关的类。

byte数组传输

        前两章我们已经完成了对消息体的封装和编解码器,然后我们来修改相关的代码,其实就是把原来string类型改为StringMessage类型

TcpMessageStringHandler.java


import com.loveprogrammer.base.network.listener.INetworkEventListener;
import com.loveprogrammer.base.network.support.SessionManager;
import com.loveprogrammer.pojo.StringMessage;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;/*** @ClassName TcpMessageStringHandler* @Description tcp消息处理类* @Author admin* @Date 2024/2/4 15:16* @Version 1.0*/
@Component
@Scope("prototype")
public class TcpMessageStringHandler extends SimpleChannelInboundHandler<StringMessage> {private static final Logger logger = LoggerFactory.getLogger(TcpMessageStringHandler.class);@Autowiredprivate INetworkEventListener listener;//    public TcpMessageStringHandler(INetworkEventListener listener) {
//        this.listener = listener;
//    }@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable throwable) {listener.onExceptionCaught(ctx,throwable);}@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {super.channelRead(ctx, msg);}@Overrideprotected void channelRead0(ChannelHandlerContext ctx, StringMessage msg) {listener.channelRead(ctx,msg);// ctx.writeAndFlush(result);}@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {listener.onConnected(ctx);}@Overridepublic void channelInactive(ChannelHandlerContext ctx) throws Exception {listener.onDisconnected(ctx);}
}

TcpServerStringInitializer.java

import com.loveprogrammer.base.factory.SpringContextHelper;
import com.loveprogrammer.base.network.listener.INetworkEventListener;
import com.loveprogrammer.base.network.support.NetworkListener;
import com.loveprogrammer.codec.MessageDecoder;
import com.loveprogrammer.codec.MessageEncoder;
import com.loveprogrammer.constants.ConstantValue;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;public class TcpServerStringInitializer extends ChannelInitializer<SocketChannel> {@Overrideprotected void initChannel(SocketChannel ch) {ChannelPipeline pipeline = ch.pipeline();// pipeline.addLast("framer",new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));pipeline.addLast("decoder", new MessageEncoder());pipeline.addLast("encoder", new MessageDecoder(ConstantValue.MESSAGE_CODEC_MAX_FRAME_LENGTH,ConstantValue.MESSAGE_CODEC_LENGTH_FIELD_OFFSET, ConstantValue.MESSAGE_CODEC_LENGTH_FIELD_LENGTH,ConstantValue.MESSAGE_CODEC_LENGTH_ADJUSTMENT, ConstantValue.MESSAGE_CODEC_INITIAL_BYTES_TO_STRIP, false));TcpMessageStringHandler handler = (TcpMessageStringHandler) SpringContextHelper.getBean(TcpMessageStringHandler.class);pipeline.addLast(handler);}}

INetworkEventListener.java

public interface INetworkEventListener {/*** 连接建立** @param ctx ChannelHandlerContext*/void onConnected(ChannelHandlerContext ctx);/*** 连接断开* * @param ctx ChannelHandlerContext*/void onDisconnected(ChannelHandlerContext ctx);/*** 异常发生* * @param ctx ChannelHandlerContext* * @param throwable 异常*/void onExceptionCaught(ChannelHandlerContext ctx, Throwable throwable);void channelRead(ChannelHandlerContext ctx, StringMessage msg);}

NetworkListener.java

package com.loveprogrammer.base.network.support;import com.loveprogrammer.base.bean.session.Session;
import com.loveprogrammer.base.network.listener.INetworkEventListener;
import com.loveprogrammer.constants.CommonValue;
import com.loveprogrammer.pojo.StringMessage;
import io.netty.channel.ChannelHandlerContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;@Component
public class NetworkListener implements INetworkEventListener {protected static final Logger logger = LoggerFactory.getLogger(NetworkListener.class);@Overridepublic void onConnected(ChannelHandlerContext ctx) {logger.info("建立连接");SessionManager.getInstance().create(ctx.channel());}@Overridepublic void onDisconnected(ChannelHandlerContext ctx) {logger.info("建立断开");SessionManager.getInstance().close(ctx.channel());}@Overridepublic void onExceptionCaught(ChannelHandlerContext ctx, Throwable throwable) {logger.warn("异常发生", throwable);}@Overridepublic void channelRead(ChannelHandlerContext ctx, StringMessage msg) {logger.info("数据内容:data=" + msg.getBody());String result = "我是服务器,我收到了你的信息:" + msg.getBody();Session session = SessionManager.getInstance().getSessionByChannel(ctx.channel());result += ",sessionId = " + session.getId();StringMessage message = StringMessage.create(1,1);message.setStatusCode(CommonValue.MSG_STATUS_CODE_SUCCESS);message.setBody(result);SessionManager.getInstance().sendMessage(ctx.channel(),message);}
}

同样的client端也要跟着修改一下

SocketClient

public class SocketClient {private static final Logger logger = LoggerFactory.getLogger(SocketClient.class);private static final String IP = "127.0.0.1";private static final int PORT = 8088;private static EventLoopGroup group = new NioEventLoopGroup();protected static void run() throws InterruptedException {
//        Bootstrap bootstrap = new Bootstrap();
//        bootstrap.group(group);
//        bootstrap.channel(NioSocketChannel.class);
//        bootstrap.handler(new ChannelInitializer() {
//            protected void initChannel(Channel ch) throws Exception {
//                ChannelPipeline pipeline = ch.pipeline();
//                pipeline.addLast("framer",new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
//                pipeline.addLast("decoder",new StringDecoder());
//                pipeline.addLast("encoder",new StringEncoder());
//                pipeline.addLast(new SocketClientHandler());
//            }
//        });Bootstrap bootstrap = new Bootstrap();bootstrap.group(group);bootstrap.channel(NioSocketChannel.class);bootstrap.handler(new ChannelInitializer() {@Overrideprotected void initChannel(Channel ch) {ChannelPipeline pipeline = ch.pipeline();pipeline.addLast("encoder", new MessageEncoder());pipeline.addLast("decoder", new MessageDecoder(ConstantValue.MESSAGE_CODEC_MAX_FRAME_LENGTH,ConstantValue.MESSAGE_CODEC_LENGTH_FIELD_LENGTH, ConstantValue.MESSAGE_CODEC_LENGTH_FIELD_OFFSET,ConstantValue.MESSAGE_CODEC_LENGTH_ADJUSTMENT, ConstantValue.MESSAGE_CODEC_INITIAL_BYTES_TO_STRIP,false));pipeline.addLast(new SocketClientHandler());}});// 连接服务器ChannelFuture channelFuture = bootstrap.connect(IP, PORT).sync();StringMessage msg = StringMessage.create(1,1);msg.setStatusCode(200);msg.setBody("你好,我是eric");// msg += "\r\n";channelFuture.channel().writeAndFlush(msg);logger.info("向服务器发送消息 {}",msg);channelFuture.channel().closeFuture().sync();}public static void main(String[] args) throws InterruptedException {logger.info("开始连接Socket服务器...");try {run();} catch (Exception e) {e.printStackTrace();} finally {group.shutdownGracefully();}}}

SocketClientHandler.java


import com.loveprogrammer.pojo.StringMessage;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;/*** @ClassName SocketClientHandler* @Description TODO* @Author admin* @Date 2024/1/29 17:41* @Version 1.0*/
public class SocketClientHandler extends SimpleChannelInboundHandler<StringMessage> {private static final Logger logger = LoggerFactory.getLogger(SocketClientHandler.class);@Overridepublic void exceptionCaught(ChannelHandlerContext arg0, Throwable arg1) {logger.info("异常发生", arg1);}@Overridepublic void channelRead(ChannelHandlerContext arg0, Object msg) throws Exception {super.channelRead(arg0, msg);}@Overrideprotected void channelRead0(ChannelHandlerContext context, StringMessage data) {logger.info("数据内容:data=" + data);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}StringMessage msg = StringMessage.create(1,1);msg.setStatusCode(202);msg.setBody("你好,我是eric");// msg += "\r\n";context.channel().writeAndFlush(msg);logger.info("向服务器发送消息 {}",msg);}@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {logger.info("客户端连接建立");super.channelActive(ctx);}@Overridepublic void channelInactive(ChannelHandlerContext ctx) throws Exception {logger.info("客户端连接断开");super.channelInactive(ctx);}}

全部源码详见:

gitee : eternity-online: 多人在线mmo游戏 - Gitee.com

分支:step-06

相关文章:

从零开始手写mmo游戏从框架到爆炸(八)— byte数组传输

导航&#xff1a;从零开始手写mmo游戏从框架到爆炸&#xff08;零&#xff09;—— 导航-CSDN博客 Netty帧解码器 Netty中&#xff0c;提供了几个重要的可以直接使用的帧解码器。 LineBasedFrameDecoder 行分割帧解码器。适用场景&#xff1a;每个上层数据包&#xff0c;使…...

Elasticsearch:BM25 及 使用 Elasticsearch 和 LangChain 的自查询检索器

本工作簿演示了 Elasticsearch 的自查询检索器将非结构化查询转换为结构化查询的示例&#xff0c;我们将其用于 BM25 示例。 在这个例子中&#xff1a; 我们将摄取 LangChain 之外的电影样本数据集自定义 ElasticsearchStore 中的检索策略以仅使用 BM25使用自查询检索将问题转…...

uniapp的api用法大全

页面生命周期API uniApp中的页面生命周期API可以帮助开发者在页面的不同生命周期中执行相应的操作。常用的页面生命周期API包括&#xff1a;onLoad、onShow、onReady、onHide、onUnload等。其中&#xff0c;onLoad在页面加载时触发&#xff0c;onShow在页面显示时触发&#xf…...

笔记——asp.net core 中的 REST

REST&#xff08;reprentational state transfer&#xff0c;表层状态转移&#xff09; REST原则&#xff1a;提倡按照HTTP的语义使用HTTP。 如果一个系统符合REST原则&#xff0c;我们就说这个系统是Restful风格的。 在RPC风格的Web API系统中&#xff0c;我们把服务端的代码…...

排序算法---堆排序

原创不易&#xff0c;转载请注明出处。欢迎点赞收藏~ 堆排序&#xff08;Heap Sort&#xff09;是一种基于二叉堆数据结构的排序算法。它将待排序的元素构建成一个最大堆&#xff08;或最小堆&#xff09;&#xff0c;然后逐步将堆顶元素与堆的最后一个元素交换位置&#xff0c…...

Java字符串(包含字母和数字)通用排序

说明&#xff1a;本文章是之前查到的一篇安卓版的&#xff0c;具体原文路径忘记了。稍微改了一点&#xff0c;挺符合业务使用的&#xff01; 一、看代码 /*** 包含数字的字符串进行比较&#xff08;按照从小到大排序&#xff09;*/private static Integer compareString(Stri…...

【Spring】springmvc如何处理接受http请求

目录 ​编辑 1. 背景 2. web项目和非web项目 3. 环境准备 4. 分析链路 5. 总结 1. 背景 今天开了一篇文章“SpringMVC是如何将不同的Request路由到不同Controller中的&#xff1f;”&#xff1b;看完之后突然想到&#xff0c;在请求走到mvc 之前服务是怎么知道有请求进来…...

2024年安全员-B证证模拟考试题库及安全员-B证理论考试试题

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年安全员-B证证模拟考试题库及安全员-B证理论考试试题是由安全生产模拟考试一点通提供&#xff0c;安全员-B证证模拟考试题库是根据安全员-B证最新版教材&#xff0c;安全员-B证大纲整理而成&#xff08;含2024年…...

redis过期淘汰策略、数据过期策略与持久化方式

redis的过期淘汰策略 redis过期淘汰策略有很多,默认是no-eviction 不删除任何数据,内存不足存入会直接报错,可以在redis配置文件中进行设置,其中有两个非常重要的概念,LRU与LFU LRU表示最近最少使用,LFU为最少频率使用 又按照volatile已设置过期时间的数据集和allkeys所有数…...

Oracle Vagrant Box 扩展根文件系统

需求 默认的Oracle Database 19c Vagrant Box的磁盘为34GB。 最近在做数据库升级实验&#xff0c;加之导入AWR dump数据&#xff0c;导致空间不够。 因此需要对磁盘进行扩容。 扩容方法1&#xff1a;预先扩容 此方法参考文档Vagrant, how to specify the disk size?。 指…...

TDengine用户权限管理

Background 官方文档关于用户管理没有很详细的介绍&#xff0c;只有零碎的几条&#xff0c;这里记录下方便后面使用。官方文档&#xff1a;https://docs.taosdata.com/taos-sql/show/#show-users 1、查看用户 show users;super 1&#xff0c;表示超级用户权限 0&#xff0c;表…...

推荐一款开源的跨平台划词翻译和OCR翻译软件:Pot

Pot简介 一款开源的跨平台划词翻译和OCR翻译软件 下载安装指南 根据你的机器型号下载对应版本&#xff0c;下载完成后双击安装即可。 使用教程 Pot具体功能如下&#xff1a; 划词翻译输入翻译外部调用鼠标选中需要翻译的文本&#xff0c;按下设置的划词翻译快捷键即可按下输…...

spring boot学习第十一篇:发邮件

1、pom.xml文件内容如下&#xff08;是我所有学习内容需要的&#xff0c;不再单独分出来&#xff0c;包不会冲突&#xff09;&#xff1a; <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"…...

Linux中ps/kill/execl的使用

ps命令&#xff1a; ps -aus或者ps -ajx或者 ps -ef可以查看有哪些进程。加上 | grep "xxx" 可以查看名为”xxx"的进程。 ps -aus | grep "xxx" kill命令&#xff1a; kill -9 pid 杀死某个进程 kill -l 查看系统有哪些信号 execl函数&#…...

【web前端开发】HTML及CSS简单页面布局练习

案例一 网页课程 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevice-wi…...

2.7日学习打卡----初学RabbitMQ(二)

2.7日学习打卡 JMS 由于MQ产品很多&#xff0c;操作方式各有不同&#xff0c;于是JAVA提供了一套规则 ——JMS&#xff0c;用于操作消息中间件。JMS即Java消息服务 &#xff08;JavaMessage Service&#xff09;应用程序接口&#xff0c;是一个Java平台中关于面 向消息中间件的…...

【工作学习 day04】 9. uniapp 页面和组件的生命周期

问题描述 uniapp常用的有&#xff1a;页面和组件&#xff0c;并且页面和组件各自有各自的生命周期函数&#xff0c;那么在页面/组件请求数据时&#xff0c;是用created呢&#xff0c;还是用onLoad呢&#xff1f; 先说结论: 组件使用组件的生命周期&#xff0c;页面使用页面的…...

Mysql-数据库优化-客户端连接参数

客户端参数 原文地址 # 连接池配置 # 初始化连接数 spring.datasource.druid.initial-size1 # 最小空闲连接数&#xff0c;一般设置和initial-size一致 spring.datasource.druid.min-idle1 # 最大活动连接数&#xff0c;一个数据库能够支撑最大的连接数是多少呢&#xff1f; …...

【十二】【C++】vector用法的探究

vector类创建对象 /*vector类创建对象*/ #if 1 #define _CRT_SECURE_NO_WARNINGS#include <iostream> using namespace std; #include <vector> #include <algorithm> #include <crtdbg.h>class Date {public:Date(int year 1900, int month 1, int …...

Docker 基本介绍

Docker 基本介绍 镜像 Docker镜像就是一个只读的模板。 例如&#xff1a;一个镜像可以包含一个完整的ubuntu操作系统环境&#xff0c;里面仅安装了Apache或用户需要的其它应用 程序。 镜像可以用来创建Docker容器。Docker提供了一个很简单的机制来创建镜像或者更新现有的镜…...

如何彻底解决Windows电脑自动锁屏问题:终极鼠标模拟工具使用指南

如何彻底解决Windows电脑自动锁屏问题&#xff1a;终极鼠标模拟工具使用指南 【免费下载链接】mousejiggler Mouse Jiggler is a very simple piece of software whose sole function is to "fake" mouse input to Windows, and jiggle the mouse pointer back and f…...

5分钟解决Mac NTFS读写难题:免费开源工具完全指南

5分钟解决Mac NTFS读写难题&#xff1a;免费开源工具完全指南 【免费下载链接】Free-NTFS-for-Mac Nigate: An open-source NTFS utility for Mac. It supports all Mac models (Intel and Apple Silicon), providing full read-write access, mounting, and management for NT…...

【2024最新】ChatGPT联网搜索避坑白皮书:已踩过137次坑的技术总监总结出的6条铁律

更多请点击&#xff1a; https://intelliparadigm.com 第一章&#xff1a;ChatGPT联网搜索功能的核心机制与能力边界 ChatGPT 的联网搜索功能并非内置实时浏览器&#xff0c;而是通过插件&#xff08;如 Bing Search Plugin&#xff09;或企业级 API 集成方式&#xff0c;在用…...

CircuitPython库管理全攻略:从手动安装到CircUp工具实战

1. 项目概述&#xff1a;CircuitPython库管理的核心价值 如果你刚开始接触CircuitPython&#xff0c;可能会被一个看似简单的问题绊住&#xff1a;为什么我复制了别人的代码&#xff0c;板子却毫无反应&#xff0c;或者串口里报了一堆看不懂的错误&#xff1f;十有八九&#x…...

03-eMMC性能实战解析:速率模式、引脚配置与上电时序的协同设计

1. eMMC高速模式实战&#xff1a;HS400与HS200的带宽对决 在嵌入式系统设计中&#xff0c;eMMC存储的性能直接影响设备响应速度和用户体验。实测数据显示&#xff0c;三星KLMCG2KETM-B041芯片在HS400模式下能达到269.4MB/s的读取速度&#xff0c;而东芝THGBMDG5D1LBAIL同模式下…...

避开这些坑!RT-Thread+lwip网卡驱动开发中的5个常见误区与实战解法

RT-Thread与lwIP网卡驱动开发中的五大性能陷阱与实战突围 在嵌入式网络开发领域&#xff0c;RT-Thread与lwIP的组合已经成为许多开发者的首选方案。然而&#xff0c;这套看似成熟的网络协议栈背后&#xff0c;却隐藏着诸多性能陷阱。本文将揭示五个最常见的开发误区&#xff0c…...

驾驶舱前端设计方案:从“花架子”到“真能用”的组件化实战

很多人一提起“驾驶舱”或“ dashboard ”&#xff0c;脑海里浮现的就是&#xff1a;满屏图表、深色背景、会转动的3D地球、还有一堆看起来很高端但没人点开的按钮。但真正在一线做过驾驶舱的人都知道——大部分驾驶舱&#xff0c;上线当天截个图发朋友圈之后&#xff0c;就再也…...

Harmonix:轻量级AI音乐源分离实战指南

1. 项目概述&#xff1a;当AI遇见音乐&#xff0c;Harmonix如何重塑音频处理如果你和我一样&#xff0c;既对音乐制作充满热情&#xff0c;又对AI技术的前沿应用保持好奇&#xff0c;那么最近在GitHub上悄然走红的awslabs/harmonix项目&#xff0c;绝对值得你花上一个下午的时间…...

Trae IDE 实战:打造“创建完美智能体助手”(交互式+自动生成+模板删减,新手无脑上手)

Trae IDE 实战:打造“创建完美智能体助手”(交互式+自动生成+模板删减,新手无脑上手) 前言:在AI研发提效浪潮中,Trae IDE的自定义Agent已成为开发者的核心协作工具。本文聚焦「创建完美智能体助手」的打造,全程贴合Trae原生能力,主打“交互式引导、全自动文件生成、模…...

设计模式实战指南:从理论到工程落地的技能库构建

1. 项目概述&#xff1a;设计模式技能库的构建初衷最近在整理团队的技术资产&#xff0c;发现一个挺普遍的现象&#xff1a;很多同学在面试时能把设计模式的概念背得滚瓜烂熟&#xff0c;什么“单例模式确保一个类只有一个实例”&#xff0c;但一到实际项目里&#xff0c;面对稍…...