从零开始手写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数组传输
导航:从零开始手写mmo游戏从框架到爆炸(零)—— 导航-CSDN博客 Netty帧解码器 Netty中,提供了几个重要的可以直接使用的帧解码器。 LineBasedFrameDecoder 行分割帧解码器。适用场景:每个上层数据包,使…...

Elasticsearch:BM25 及 使用 Elasticsearch 和 LangChain 的自查询检索器
本工作簿演示了 Elasticsearch 的自查询检索器将非结构化查询转换为结构化查询的示例,我们将其用于 BM25 示例。 在这个例子中: 我们将摄取 LangChain 之外的电影样本数据集自定义 ElasticsearchStore 中的检索策略以仅使用 BM25使用自查询检索将问题转…...
uniapp的api用法大全
页面生命周期API uniApp中的页面生命周期API可以帮助开发者在页面的不同生命周期中执行相应的操作。常用的页面生命周期API包括:onLoad、onShow、onReady、onHide、onUnload等。其中,onLoad在页面加载时触发,onShow在页面显示时触发…...
笔记——asp.net core 中的 REST
REST(reprentational state transfer,表层状态转移) REST原则:提倡按照HTTP的语义使用HTTP。 如果一个系统符合REST原则,我们就说这个系统是Restful风格的。 在RPC风格的Web API系统中,我们把服务端的代码…...

排序算法---堆排序
原创不易,转载请注明出处。欢迎点赞收藏~ 堆排序(Heap Sort)是一种基于二叉堆数据结构的排序算法。它将待排序的元素构建成一个最大堆(或最小堆),然后逐步将堆顶元素与堆的最后一个元素交换位置,…...
Java字符串(包含字母和数字)通用排序
说明:本文章是之前查到的一篇安卓版的,具体原文路径忘记了。稍微改了一点,挺符合业务使用的! 一、看代码 /*** 包含数字的字符串进行比较(按照从小到大排序)*/private static Integer compareString(Stri…...

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

2024年安全员-B证证模拟考试题库及安全员-B证理论考试试题
题库来源:安全生产模拟考试一点通公众号小程序 2024年安全员-B证证模拟考试题库及安全员-B证理论考试试题是由安全生产模拟考试一点通提供,安全员-B证证模拟考试题库是根据安全员-B证最新版教材,安全员-B证大纲整理而成(含2024年…...
redis过期淘汰策略、数据过期策略与持久化方式
redis的过期淘汰策略 redis过期淘汰策略有很多,默认是no-eviction 不删除任何数据,内存不足存入会直接报错,可以在redis配置文件中进行设置,其中有两个非常重要的概念,LRU与LFU LRU表示最近最少使用,LFU为最少频率使用 又按照volatile已设置过期时间的数据集和allkeys所有数…...

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

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

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

spring boot学习第十一篇:发邮件
1、pom.xml文件内容如下(是我所有学习内容需要的,不再单独分出来,包不会冲突): <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"…...

Linux中ps/kill/execl的使用
ps命令: ps -aus或者ps -ajx或者 ps -ef可以查看有哪些进程。加上 | grep "xxx" 可以查看名为”xxx"的进程。 ps -aus | grep "xxx" kill命令: 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产品很多,操作方式各有不同,于是JAVA提供了一套规则 ——JMS,用于操作消息中间件。JMS即Java消息服务 (JavaMessage Service)应用程序接口,是一个Java平台中关于面 向消息中间件的…...

【工作学习 day04】 9. uniapp 页面和组件的生命周期
问题描述 uniapp常用的有:页面和组件,并且页面和组件各自有各自的生命周期函数,那么在页面/组件请求数据时,是用created呢,还是用onLoad呢? 先说结论: 组件使用组件的生命周期,页面使用页面的…...

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

【十二】【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镜像就是一个只读的模板。 例如:一个镜像可以包含一个完整的ubuntu操作系统环境,里面仅安装了Apache或用户需要的其它应用 程序。 镜像可以用来创建Docker容器。Docker提供了一个很简单的机制来创建镜像或者更新现有的镜…...

华为云AI开发平台ModelArts
华为云ModelArts:重塑AI开发流程的“智能引擎”与“创新加速器”! 在人工智能浪潮席卷全球的2025年,企业拥抱AI的意愿空前高涨,但技术门槛高、流程复杂、资源投入巨大的现实,却让许多创新构想止步于实验室。数据科学家…...
HTML 语义化
目录 HTML 语义化HTML5 新特性HTML 语义化的好处语义化标签的使用场景最佳实践 HTML 语义化 HTML5 新特性 标准答案: 语义化标签: <header>:页头<nav>:导航<main>:主要内容<article>&#x…...
服务器硬防的应用场景都有哪些?
服务器硬防是指一种通过硬件设备层面的安全措施来防御服务器系统受到网络攻击的方式,避免服务器受到各种恶意攻击和网络威胁,那么,服务器硬防通常都会应用在哪些场景当中呢? 硬防服务器中一般会配备入侵检测系统和预防系统&#x…...

页面渲染流程与性能优化
页面渲染流程与性能优化详解(完整版) 一、现代浏览器渲染流程(详细说明) 1. 构建DOM树 浏览器接收到HTML文档后,会逐步解析并构建DOM(Document Object Model)树。具体过程如下: (…...

MODBUS TCP转CANopen 技术赋能高效协同作业
在现代工业自动化领域,MODBUS TCP和CANopen两种通讯协议因其稳定性和高效性被广泛应用于各种设备和系统中。而随着科技的不断进步,这两种通讯协议也正在被逐步融合,形成了一种新型的通讯方式——开疆智能MODBUS TCP转CANopen网关KJ-TCPC-CANP…...
【HTTP三个基础问题】
面试官您好!HTTP是超文本传输协议,是互联网上客户端和服务器之间传输超文本数据(比如文字、图片、音频、视频等)的核心协议,当前互联网应用最广泛的版本是HTTP1.1,它基于经典的C/S模型,也就是客…...

群晖NAS如何在虚拟机创建飞牛NAS
套件中心下载安装Virtual Machine Manager 创建虚拟机 配置虚拟机 飞牛官网下载 https://iso.liveupdate.fnnas.com/x86_64/trim/fnos-0.9.2-863.iso 群晖NAS如何在虚拟机创建飞牛NAS - 个人信息分享...

协议转换利器,profinet转ethercat网关的两大派系,各有千秋
随着工业以太网的发展,其高效、便捷、协议开放、易于冗余等诸多优点,被越来越多的工业现场所采用。西门子SIMATIC S7-1200/1500系列PLC集成有Profinet接口,具有实时性、开放性,使用TCP/IP和IT标准,符合基于工业以太网的…...

解析两阶段提交与三阶段提交的核心差异及MySQL实现方案
引言 在分布式系统的事务处理中,如何保障跨节点数据操作的一致性始终是核心挑战。经典的两阶段提交协议(2PC)通过准备阶段与提交阶段的协调机制,以同步决策模式确保事务原子性。其改进版本三阶段提交协议(3PC…...
13.10 LangGraph多轮对话系统实战:Ollama私有部署+情感识别优化全解析
LangGraph多轮对话系统实战:Ollama私有部署+情感识别优化全解析 LanguageMentor 对话式训练系统架构与实现 关键词:多轮对话系统设计、场景化提示工程、情感识别优化、LangGraph 状态管理、Ollama 私有化部署 1. 对话训练系统技术架构 采用四层架构实现高扩展性的对话训练…...