本文完整代码,可以浏览:

https://github.com/hjj2017/xgame-code_server/blob/master/game_server/src/com/game/gameServer/framework/mina/MsgCumulativeFilter.java

我在网上查阅过的 MINA 黏包处理,一般都是放在 Decoder 中做的。也就是黏包处理和消息解码放在一起做,显得比较混乱不好打理。而以下这段代码,我是把黏包处理放在 Filter 中了。在具体使用时可以这样:

 // 创建 IO 接收器
NioSocketAcceptor acceptor = new NioSocketAcceptor(); // 获取责任链
DefaultIoFilterChainBuilder chain = acceptor.getFilterChain();
// 处理网络粘包
chain.addLast("msgCumulative", new MsgCumulativeFilter()); // 添加自定义编解码器
chain.addLast("msgCodec", new ProtocolCodecFilter(
new XxxEncoder(),
new XxxDecoder()
)); // 获取会话配置
IoSessionConfig cfg = acceptor.getSessionConfig(); // 设置缓冲区大小
cfg.setReadBufferSize(4096);
// 设置 session 空闲时间
cfg.setIdleTime(IdleStatus.BOTH_IDLE, 10); // 设置 IO 句柄
acceptor.setHandler(new XxxHandler());
acceptor.setReuseAddress(true); try {
// 绑定端口
acceptor.bind(new InetSocketAddress("127.0.0.1", 4400));
} catch (Exception ex) {
// 输出错误日志
System.error.println(ex);
}

目前 Netty 框架要比 MINA 流行的多,而且 Netty 对网络黏包处理也做了很好的处理,不用开发者自己费那么大劲。我也考虑过迁移到 Netty 框架上,不过目前还没有找到特别充分的理由。闲话不多说了,以下就是黏包处理代码:

 package com.game.gameServer.framework.mina;

 import java.util.concurrent.ConcurrentHashMap;

 import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.filterchain.IoFilterAdapter;
import org.apache.mina.core.session.IoSession; import com.game.gameServer.framework.FrameworkLog;
import com.game.gameServer.msg.SpecialMsgSerialUId;
import com.game.part.msg.IoBuffUtil; /**
* 消息粘包处理
*
* @author hjj2017
* @since 2014/3/17
*
*/
class MsgCumulativeFilter extends IoFilterAdapter {
/**
* 从客户端接收的消息估计长度,
* {@value} 字节,
* 对于从客户端接收的数据来说, 都是简单的命令!
* 很少超过 {@value}B
*
*/
private static final int DECODE_MSG_LEN = 64;
/** 容器 Buff 字典 */
private static final ConcurrentHashMap<Long, IoBuffer> _containerBuffMap = new ConcurrentHashMap<>(); @Override
public void sessionClosed(NextFilter nextFilter, IoSession sessionObj) throws Exception {
if (nextFilter == null ||
sessionObj == null) {
// 如果参数对象为空,
// 则直接退出!
FrameworkLog.LOG.error("null nextFilter or sessionObj");
return;
} // 移除容器 Buff
removeContainerBuff(sessionObj);
// 向下传递
super.sessionClosed(nextFilter, sessionObj);
} @Override
public void messageReceived(
NextFilter nextFilter, IoSession sessionObj, Object msgObj) throws Exception {
if (nextFilter == null ||
sessionObj == null) {
// 如果参数对象为空,
// 则直接退出!
FrameworkLog.LOG.error("null nextFilter or sessionObj");
return;
} // 获取会话 UId
long sessionUId = sessionObj.getId(); if (!(msgObj instanceof IoBuffer)) {
// 如果消息对象不是 ByteBuff,
// 则直接向下传递!
FrameworkLog.LOG.warn("msgObj is not a IoBuff, sessionUId = " + sessionUId);
super.messageReceived(nextFilter, sessionObj, msgObj);
} // 获取输入 Buff
IoBuffer inBuff = (IoBuffer)msgObj; if (!inBuff.hasRemaining()) {
// 如果没有剩余内容,
// 则直接退出!
FrameworkLog.LOG.error("inBuff has not remaining, sessionUId = " + sessionUId);
return;
} else if (inBuff.remaining() <= 8) {
// 如果 <= 8 字节,
// 那还是执行粘包处理过程吧 ...
// 8 字节 = 消息长度 ( Short ) + 消息类型 ( Short ) + 时间戳 ( Int )
// 如果比这个长度都小,
// 那肯定不是一条完整消息 ...
this.msgRecv_0(nextFilter, sessionObj, inBuff);
return;
} // 获取消息长度
final int msgSize = inBuff.getShort();
inBuff.position(0); if (msgSize == inBuff.limit() &&
containerBuffIsEmpty(sessionObj)) {
//
// 如果消息长度和极限值刚好相同,
// 并且容器 Buff 中没有任何内容 ( 即, 上一次消息没有粘包 ),
// 那么直接向下传递!
//
super.messageReceived(
nextFilter, sessionObj, inBuff
);
} else {
//
// 如果消息长度和极限值不同,
// 则说明是网络粘包!
// 这时候跳转到粘包处理过程 ...
//
this.msgRecv_0(nextFilter, sessionObj, inBuff);
}
} /**
* 接收连包消息
*
* @param nextFilter
* @param sessionObj
* @param inBuff
* @throws Exception
*
*/
private void msgRecv_0(
NextFilter nextFilter, IoSession sessionObj, IoBuffer inBuff) throws Exception {
if (nextFilter == null ||
sessionObj == null) {
// 如果参数对象为空,
// 则直接退出!
FrameworkLog.LOG.error("null nextFilter or sessionObj");
return;
} // 获取会话 UId
long sessionUId = sessionObj.getId();
// 获取容器 Buff
IoBuffer containerBuff = getContainerBuff(sessionObj); // 添加新 Buff 到容器 Buff 的末尾
IoBuffUtil.append(containerBuff, inBuff);
// 令 position = 0
containerBuff.position(0); // // 记录调试信息
// FrameworkLog.LOG.debug("\nin = [ " + inBuff.getHexDump() + " ]"); for (int i = 0; ; i++) {
// // 记录调试信息
// FrameworkLog.LOG.debug(
// "i = " + i
// + "\nco = [ " + containerBuff.getHexDump() + " ]"
// + "\nco.pos = " + containerBuff.position()
// + "\nco.lim = " + containerBuff.limit()
// ); if (containerBuff.remaining() < 4) {
//
// 如果剩余字节数 < 4,
// 这样根本无法识别出消息类型 msgSerialUId ...
// 直接退出!
// 在退出前,
// 准备好接收下一次消息!
//
IoBuffUtil.readyToNext(containerBuff);
return;
} // 获取原始位置
final int oldPos = containerBuff.position();
// 获取消息长度和类型
final int msgSize = containerBuff.getShort();
final int msgSerialUId = containerBuff.getShort(); // // 记录调试信息
// FrameworkLog.LOG.debug(
// "i = " + i
// + "\nmsgSize = " + msgSize
// + "\nmsgSerialUId = " + msgSerialUId
// ); // 还原原始位置
containerBuff.position(oldPos); if (msgSerialUId == SpecialMsgSerialUId.CG_FLASH_POLICY ||
msgSerialUId == SpecialMsgSerialUId.CG_QQ_TGW) {
//
// 如果是 Flash 安全策略消息,
// 或者是腾讯网关消息,
// 则尝试找一下 0 字节的位置 ...
//
int pos0 = IoBuffUtil.indexOf(containerBuff, (byte)0); if (pos0 <= -1) {
// 如果找不到 0 字节的位置,
// 则说明消息还没接收完,
// 准备接受下次消息并直接退出!
IoBuffUtil.readyToNext(containerBuff);
return;
} // 复制 Buff 内容
containerBuff.position(0);
IoBuffer realBuff = IoBuffUtil.copy(containerBuff, pos0); // 更新 Buff 位置
final int newPos = containerBuff.position() + pos0;
containerBuff.position(newPos);
// 压缩容器 Buff
IoBuffUtil.compact(containerBuff); // 向下传递
super.messageReceived(
nextFilter, sessionObj, realBuff
);
continue;
} if (msgSize <= 0) {
//
// 如果消息长度 <= 0,
// 则直接退出!
// 这种情况可能是消息已经乱套了 ...
// 还是重新来过吧!
//
FrameworkLog.LOG.error("i = " + i + ", msgSize = " + msgSize + ", sessionUId = " + sessionUId);
// 将容器 Buff 内容清空
containerBuff.position(0);
containerBuff.flip();
// 压缩容器 Buff
IoBuffUtil.compact(containerBuff);
return;
} if (containerBuff.remaining() < msgSize) {
//
// 如果消息长度不够,
// 则可能是出现网络粘包情况了 ...
// 直接退出就可以了!
//
FrameworkLog.LOG.warn(
"i = " + i
+ ", msgSize = " + msgSize
+ ", containerBuff.remaining = " + containerBuff.remaining()
+ ", sessionUId = " + sessionUId
); // 准备接受下一次消息
IoBuffUtil.readyToNext(containerBuff);
return;
} // 创建新 Buff 并复制字节内容
IoBuffer realBuff = IoBuffUtil.copy(containerBuff, msgSize); if (realBuff == null) {
//
// 如果真实的 Buff 为空,
// 则直接退出!
// 这种情况可能也是消息乱套了 ...
// 记录一下错误信息
//
FrameworkLog.LOG.error("i = " + i + ", null realBuff, sessionUId = " + sessionUId);
} else {
// // 记录调试信息
// FrameworkLog.LOG.debug(
// "i = " + i
// + "\nreal = [ " + realBuff.getHexDump() + " ]"
// + "\nreal.pos = " + realBuff.position()
// + "\nreal.lim = " + realBuff.limit()
// ); // 向下传递
super.messageReceived(
nextFilter, sessionObj, realBuff
);
} // 更新位置
containerBuff.position(containerBuff.position() + msgSize);
// 压缩容器 Buff
IoBuffUtil.compact(containerBuff);
}
} /**
* 获取玩家的 Buff, 如果为空则新建一个!
*
* @param sessionObj
* @return
*
*/
private static IoBuffer getContainerBuff(IoSession sessionObj) {
if (sessionObj == null) {
// 如果参数对象为空,
// 则直接退出!
return null;
} // 获取会话 UId
long sessionUId = sessionObj.getId();
// 获取容器 Buff
IoBuffer containerBuff = _containerBuffMap.get(sessionUId); if (containerBuff == null) {
// 创建缓存 Buff
containerBuff = IoBuffer.allocate(DECODE_MSG_LEN);
containerBuff.setAutoExpand(true);
containerBuff.setAutoShrink(true);
containerBuff.position(0);
containerBuff.flip();
// 缓存 Buff 对象
Object oldVal = _containerBuffMap.putIfAbsent(sessionUId, containerBuff); if (oldVal != null) {
FrameworkLog.LOG.warn("exists oldVal");
}
} return containerBuff;
} /**
* 移除容器 Buff
*
* @param sessionObj
*
*/
private static void removeContainerBuff(IoSession sessionObj) {
if (sessionObj == null) {
// 如果参数对象为空,
// 则直接退出!
return;
} // 获取会话 UId
long sessionUId = sessionObj.getId();
// 获取容器 Buff
IoBuffer containerBuff = _containerBuffMap.get(sessionUId); if (containerBuff != null) {
// 是否所占资源
containerBuff.clear();
} // 移除玩家的 Buff 对象
_containerBuffMap.remove(sessionUId);
} /**
* 容器 Buff 为空 ?
*
* @param sessionObj
* @return
*
*/
private static boolean containerBuffIsEmpty(IoSession sessionObj) {
if (sessionObj == null) {
// 如果参数对象为空,
// 则直接退出!
return false;
} // 获取容器 Buff
IoBuffer containerBuff = getContainerBuff(sessionObj); if (containerBuff == null) {
// 如果容器为空,
// 则直接退出!
FrameworkLog.LOG.error("null containerBuff, sessionUId = " + sessionObj.getId());
return false;
} else {
// 如果当前位置和极限值都为 0,
// 则判定为空!
return (containerBuff.position() == 0
&& containerBuff.limit() == 0);
}
}
}

MINA 网络黏包处理代码的更多相关文章

  1. Python学习笔记【第十四篇】:Python网络编程二黏包问题、socketserver、验证合法性

    TCP/IP网络通讯粘包问题 案例:模拟执行shell命令,服务器返回相应的类容.发送指令的客户端容错率暂无考虑,按照正确的指令发送即可. 服务端代码 # -*- coding: utf- -*- # ...

  2. Python 之网络编程之socket(2)黏包现象和socketserver并发

    一:黏包 ###tcp协议在发送数据时,会出现黏包现象.     (1)数据粘包是因为在客户端/服务器端都会有一个数据缓冲区,     缓冲区用来临时保存数据,为了保证能够完整的接收到数据,因此缓冲区 ...

  3. 2、网络并发编程--套接字编程、黏包问题、struct模块、制作简易报头、上传文件数据

    昨日内容回顾 面向对象复习(json序列化类) 对象.类.父类的概念 三大特性:封装 继承 多态 双下开头的方法(达到某个条件自动触发) __init__:对象实例化自动触发 __str__:对象执行 ...

  4. 《Python》网络编程之黏包

    黏包 一.黏包现象 同时执行多条命令之后,得到的结果很可能只有一部分,在执行其他命令的时候又接收到之前执行的另外一部分结果,这种显现就是黏包. server端 import socket sk = s ...

  5. python之路----网络编程--黏包

    黏包现象 让我们基于tcp先制作一个远程执行命令的程序(命令ls -l ; lllllll ; pwd) res=subprocess.Popen(cmd.decode('utf-8'), shell ...

  6. 网络编程- 解决黏包现象方案二之struct模块(七)

    上面利用struct模块与方案一比较,减少一次发送和接收请求,因为方案一无法知道client端发送内容的长度到底有多长需要和接收OK.多一次请求防止黏包,减少网络延迟

  7. 网络TCp数据的传输设计(黏包处理)

    //1.该片为引用别人的文章:http://www.cnblogs.com/alon/archive/2009/04/16/1437599.html 解决TCP网络传输"粘包"问题 ...

  8. Python网络编程之黏包问题

    二.解决黏包问题 2.1 解决黏包方法1 计算消息实体的大小 服务端接受两次,一次时消息大小,二次是消息实体,解决消息实体黏包 客户端发送两次,一次是消息大小,一次是消息实体 在两次收发之间加入一次多 ...

  9. Python网络编程基础 struct模块 解决黏包问题 FTP

    struct模块 解决黏包问题 FTP

随机推荐

  1. 对工具的反思 & deadlines与致歉

    人和动物最大的区别就是使用工具的水平. 有些人只凭着对工具的熟练掌握便成了牛人. 工具,到底应该以何种态度去看待? 在我小的时候,工具仅仅是指树枝.线.粉笔,可以让自己有更多游戏可玩:上学之后,便又有 ...

  2. Android Open Source Projects(汇总与整理)

    Android Open Source Projects 目前包括: Android开源项目第一篇——个性化控件(View)篇  包括ListView.ActionBar.Menu.ViewPager ...

  3. TP5 急速上手 语法规则

    Tp5  规则 命名规范 目录和文件名采用‘小写+下划线’,并且以小写字母开头: 类库.函数文件统一以.php为后缀: 类的文件名均以命名空间定义,并且命名空间的路径和类库文件所在路径一致(包括大小写 ...

  4. golang交叉编译笔记

    GOOS:目标平台的操作系统(darwin.freebsd.linux.windows) GOARCH:目标平台的体系架构(386.amd64.arm) Mac 下编译 Linux 和 Windows ...

  5. [leetcode-646-Maximum Length of Pair Chain]

    You are given n pairs of numbers. In every pair, the first number is always smaller than the second ...

  6. 并查集——poj1308(并查集延伸)

    题目链接:Is It A Tree? 题意:给你一系列形如u v的点对(u v代表一条由u指向v的有向边),请问由给你的点构成的图是不是一棵树? 树的特征:①每个节点(除了根结点)只有一个入度:②只有 ...

  7. AMF3 在Unity中使用AMF3和Java服务器通信

    现在在做的项目是一个网页游戏的移植到移动端. 所以服务器直接使用原来的代码.原来的游戏是as3实现,使用flash amf3数据通信. Unity 使用C#作为脚本语言,所以就需要.net的amf3解 ...

  8. cygwin上编译RPC框架thrift

    cygwin的配置就不多说了,缺什么就通过安装器安装好了. 在thrift的官网下载 thrift-0.11.0源码,通过cmake编译,生成Makefile之后,make出现编译错误 /cygdri ...

  9. 【EasyNetQ】- 自动订阅者

    从v0.7.1.30开始,EasyNetQ简单易用AutoSubscriber.你可以用它来轻松地扫描实现任何接口的类的特定组件IConsume<T>或IConsumeAsync<T ...

  10. 【UML】状态图介绍

    1.Statechart Diagram 即状态图,主要用于描述一个对象在其生存期间的动态行为,表现为一个对象所经历的状态序列.引起状态转移的事件(Event).因状态转移而伴随的动作(Action) ...