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

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. Spring MVC - URL路径映射

    1. 普通映射 A. @RequestMapping("/test1") B. @RequestMapping(value={"/test1", "/ ...

  2. LeetCode高频题目(100)汇总-Java实现

    LeetCode高频题目(100)汇总-Java实现       LeetCode高频题目(100)汇总-Java实现 目录 第01-50题 [Leetcode-easy-1] Two Sum [Le ...

  3. 使用Visual Studio 2017构建.Net Core的Docker镜像

    1 Docker  镜像优化 微软在为开发人员生成 Docker 镜像时,提供以下三种主要方案: 用于开发 .NET Core 应用的 镜像 用于构建生成 .NET Core 应用的 镜像 用于运行 ...

  4. dispaly:-webkit-box 布局中的坑

    dispaly:-webkit-box 具体用法 这里大家可以网上查, 这里说下里面的坑 里面的子对象设置-webkit-box-flex: 1 -webkit-box-flex: 2 时:一般两个子 ...

  5. Android应用开发中的夜间模式实现(一)

    前言 在应用开发中会经常遇到要求实现夜间模式或者主题切换具体例子如下,我会先讲解第一种方法. 夜间模式 知乎 网易新闻 沪江开心词场 Pocket 主题切换 腾讯QQ 新浪微博 我今天主要是详述第一种 ...

  6. 在Android上Kotlin的单元测试(KAD22)

    作者:Antonio Leiva 时间:Apr 25, 2017 原文链接:https://antonioleiva.com/unit-tests-android-kotlin/ 当然,Kotlin也 ...

  7. 再见NullPointerException。在Kotlin里null的处理(KAD 19)

    作者:Antonio Leiva 时间:Apr 4, 2017 原文链接:https://antonioleiva.com/nullity-kotlin/ 关于Kotlin最重要的部分之一:无效处理, ...

  8. Charles的Https抓包及弱网配置

    一.Charles的主要功能 (1)截取Http 和 Https 网络封包. (2)支持重发网络请求,修改请求参数,方便后端调试. (3)支持模拟弱网环境. 二.配置简单抓包 1.设置系统代理:勾选P ...

  9. MySQL查询所有库中表名

    select table_name from information_schema.tables where table_schema='contract_ggpt' and table_type=' ...

  10. centos7 centos6中 更改默认的系统启动级别

    centos6中更改默认的启动级别 方法: 1.vi /etc/inittab 2.找到id:x:initdefault:,我的系统是id:3:initdefault:,即默认以字符模式启动. 3.将 ...