MINA 网络黏包处理代码
本文完整代码,可以浏览:
我在网上查阅过的 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 网络黏包处理代码的更多相关文章
- Python学习笔记【第十四篇】:Python网络编程二黏包问题、socketserver、验证合法性
TCP/IP网络通讯粘包问题 案例:模拟执行shell命令,服务器返回相应的类容.发送指令的客户端容错率暂无考虑,按照正确的指令发送即可. 服务端代码 # -*- coding: utf- -*- # ...
- Python 之网络编程之socket(2)黏包现象和socketserver并发
一:黏包 ###tcp协议在发送数据时,会出现黏包现象. (1)数据粘包是因为在客户端/服务器端都会有一个数据缓冲区, 缓冲区用来临时保存数据,为了保证能够完整的接收到数据,因此缓冲区 ...
- 2、网络并发编程--套接字编程、黏包问题、struct模块、制作简易报头、上传文件数据
昨日内容回顾 面向对象复习(json序列化类) 对象.类.父类的概念 三大特性:封装 继承 多态 双下开头的方法(达到某个条件自动触发) __init__:对象实例化自动触发 __str__:对象执行 ...
- 《Python》网络编程之黏包
黏包 一.黏包现象 同时执行多条命令之后,得到的结果很可能只有一部分,在执行其他命令的时候又接收到之前执行的另外一部分结果,这种显现就是黏包. server端 import socket sk = s ...
- python之路----网络编程--黏包
黏包现象 让我们基于tcp先制作一个远程执行命令的程序(命令ls -l ; lllllll ; pwd) res=subprocess.Popen(cmd.decode('utf-8'), shell ...
- 网络编程- 解决黏包现象方案二之struct模块(七)
上面利用struct模块与方案一比较,减少一次发送和接收请求,因为方案一无法知道client端发送内容的长度到底有多长需要和接收OK.多一次请求防止黏包,减少网络延迟
- 网络TCp数据的传输设计(黏包处理)
//1.该片为引用别人的文章:http://www.cnblogs.com/alon/archive/2009/04/16/1437599.html 解决TCP网络传输"粘包"问题 ...
- Python网络编程之黏包问题
二.解决黏包问题 2.1 解决黏包方法1 计算消息实体的大小 服务端接受两次,一次时消息大小,二次是消息实体,解决消息实体黏包 客户端发送两次,一次是消息大小,一次是消息实体 在两次收发之间加入一次多 ...
- Python网络编程基础 struct模块 解决黏包问题 FTP
struct模块 解决黏包问题 FTP
随机推荐
- centos linux 因别名问题引起的麻烦及解决技巧
老男孩儿-19期 L005-13节中分享.自己整理后发到自己微博中留档. 原文:http://oldboy.blog.51cto.com/2561410/699046 实例:老男孩linux实战培训第 ...
- 【JS笔记】闭包
首先看执行环境和作用域的概念.执行环境定义了变量或函数有权访问的其他数据,决定它们的行为,每个执行环境都有一个与其关联的变量对象,保存执行环境中定义的变量.当代码在一个环境中执行时,会创建变量对象的一 ...
- Ubuntu下使用Git_4
在这个第四个文章中,我将练习GIT的高级阶段了,由于高级阶段的内容转的比较多,我自己的代码除了我自己可以看懂意外,大家可能看不懂,所以我将会按照 http://git.wiki.navisec.it/ ...
- 爬取妹子图(requests + BeautifulSoup)
刚刚入门爬虫,今天先对于单个图集进行爬取,过几天再进行翻页爬取. 使用requests库和BeautifulSoup库 目标网站:妹子图 今天是对于单个图集的爬取,就选择一个进行爬取,我选择的链接为: ...
- AcCoder Contest-115 D - Christmas
D - Christmas Time limit : 2sec / Memory limit : 1024MB Score : 400 points Problem Statement In some ...
- 如何在指定文件夹下进入jupyter notebook
第一步: 打开 Anaconda Prompt 第二步: 查看文件夹所在路径 例如:你有个jupyterwork文件夹在 D:\ 路径下 第三步: 在Anaconda Prompt依次输入一下命令: ...
- POJ 2161 Chandelier(动态规划)
Description Lamps-O-Matic company assembles very large chandeliers. A chandelier consists of multipl ...
- 点击查看大图Activity
1.使用方式 Intent intent = new Intent(FriendCircleActivity.this, ImageGralleryPagerActivity.class);//0,索 ...
- Introduction to TCP/IP
目录 First Week DHCP 子网掩码 ip路由表 Second Week ipv4 ipv6 TCP和UDP Third Week NAT RPC FTP E-mail Fouth Week ...
- incorrect integer value for column 问题解决
最近在用zend框架,然后装了一个项目,发现注册的时候出现 General error: 1366 Incorrect integer value: '' for column 'user_id' a ...