欢迎阅读个人开源项目《迷你微信》服务器与《迷你微信》客户端html
filter:过滤器?(不知道是否是这么翻译,算了知道意思就行了╮(╯▽╰)╭),这种东西在不少语言中都是有的,功能是在某两层之间插入一层,进行拦截加工处理,这样能够方便的添加和删除处理,而且能够添加多层的Fileter。java
在Mina的Filter之中,filter是有序的,也就是说,根据你加入filter的方式不一样,运行的顺序也是不一样的,加入方法有点相似于链表。对Mina中Filter的详细使用请参考Mina Filter,在这里,咱们只概述两种Filter的使用:git
协议编码过滤器,这是一个在服务器接收到客户端数据,或者服务器往客户端发送数据时使用的一个编码器(在客户端的使用亦然),因为网络的传输只能经过字节流或者字符串,因此数据对象的发送接收都必须经过编码(成字节流)和解码(成对象)。这时,只要在网络层添加这么一个ProtocolCodecFilter,即可以隐藏编码和解码的实现模块,而对逻辑层的表现为直接发送对象,接收对象,简便了使用。废话很少说,我们先来看看ProtocolCodecFilter的使用:github
首先,在开启服务器网络链接的时候添加一个ProtocolCodecFilter (代码来自开源项目《迷你微信》服务器apache
public void init() { // 本身写的,负责处理网络层回调的类 MinaServerHandle minaServerHandle = new MinaServerHandle // 创建一个NIO(非阻塞)的链接 acceptor = new NioSocketAcceptor(); // 添加 ProtocolCodecFilter acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new MinaEncoder(), new MinaDecoder())); acceptor.setHandler(minaServerHandle); try { // 绑定端口 acceptor.bind(new InetSocketAddress(8081)); } catch (IOException e) { e.printStackTrace(); } }
你们发现,插入的这个ProtocolCodecFilter中,还有两个参数:MinaEncoder和MinaDecoder,这两个类都是帖主本身实现的,请继续看这两个类的实现: (代码来自开源项目《迷你微信》服务器)数组
public class MinaEncoder extends ProtocolEncoderAdapter { public final int INT_SIZE = 4 @Override public void encode(IoSession ioSession, Object message, ProtocolEncoderOutput output) throws Exception { byteArrayOutputStream = new ByteArrayOutputStream(); byte[] byteArray; if (message.getClass().equals(PacketFromServer.class)) { packetWillSend = (PacketFromServer) message; // 加入数据包 byteArray = byteArrayOutputStream.write(packetWillSend.getMessageBoty()); int sizeOfAll = byteArrayOutputStream.size() + INT_SIZE; byteArray = byteArrayOutputStream.toByteArray(); IoBuffer buffer = IoBuffer.allocate(sizeOfAll); buffer.put(DataTypeTranslater.intToByte(sizeOfAll)); // header buffer.put(byteArray); // body buffer.flip(); output.write(buffer); } } }
首先,是这个MinaEncoder ,这是在使用ioSession.write(myPacketFromServer)从服务器往客户端发送数据时调用,须要继承于ProtocolEncoderAdapter 并覆盖父类的encode方法。服务器
先解释一下这个类的用处,当你调用IoSession.write(myPacketFromServer) 的时候,传入了一个myPacketFromServer的对象,这是一个帖主本身编写的类PacketFromServer的实例化对象,然而,IO流并不能直接传输一个Java对象,即使可以传输(序列化,也会致使灵活性下降,由于客户端就必须使用Java了,因此,在网络层加上这么一个Filter,拦截住你发送的Java对象,将其转化为字节流,才能传输,而这个类MinaEncoder 即是作的这件事。微信
使用看看实现,首先,判断传进来的是否是PacketFromServer对象,接着,将内容一份一份的取出,转化成byte数组,放入byteArrayOutputStream中,最后,从byteArrayOutputStream中将以前塞入的所有byte数组所有取出,往IoBuffer中塞入,而后从ProtocolEncoderOutput 写入通往客户端的输出流。其中网络
接着,轮到MinaDecoder了** (代码来自开源项目《迷你微信》服务器)**ide
public class MinaDecoder extends CumulativeProtocolDecoder { @Override protected boolean doDecode(IoSession ioSession, IoBuffer ioBuffer, ProtocolDecoderOutput output) throws Exception {// 若是没有接收完Size部分(4字节),直接返回false if (ioBuffer.remaining() < 4) return false; else { // 标记开始位置,若是一条消息没传输完成则返回到这个位置 ioBuffer.mark(); byteArrayOutputStream = new ByteArrayOutputStream(); // 读取Size byte[] bytes = new byte[4]; ioBuffer.get(bytes); // 读取4字节的Size byteArrayOutputStream.write(bytes); int bodyLength = DataTypeTranslater.bytesToInt(bytes, 0) - DataTypeTranslater.INT_SIZE; // 按小字节序转int // 若是body没有接收完整,直接返回false if (ioBuffer.remaining() < bodyLength) { ioBuffer.reset(); // IoBuffer position回到原来标记的地方 return false; } else { byte[] bodyBytes = new byte[bodyLength]; ioBuffer.get(bodyBytes); // String body = new String(bodyBytes, "UTF-8"); byteArrayOutputStream.write(bodyBytes); // 建立对象 NetworkPacket packetFromClient = new NetworkPacket(ioSession, byteArrayOutputStream.toByteArray()); output.write(packetFromClient); // 解析出一条消息 return true; } } }
与MinaEncoder 差很少,MinaDecoder 是在接收到数据的时候被调用到,将数据转化为想要的对象,并交给逻辑层的一个模块。注意,因为帖主的网络协议是本身定义的size + objectByte 格式,因此首先接收到的事4byte,将其转化为int后代表整个包的大小。因此,在客户端传递数据前,要先将整个数据包的大小告诉服务器哦!这是防止粘包和缺包的一种很是有效的方法,这里,插播一下黏包和缺包的解释:
黏包:因为TCP协议在优化传输时,可能将多条小的数据包链接成一个大的数据包一块儿发送,以此来减小IO次数,提升效率,但这将致使本应该是两条消息的数据被服务器一次收到,黏在一块儿,因此叫作粘包。
缺包: 因为TCP协议在优化传输时,可能将一个大的数据包分割成几回发送,致使收到的数据包不全,因此,自行编写size来保证不会出现缺包问题。
欢迎阅读个人开源项目《迷你微信》服务器与《迷你微信》客户端 [1]: https://github.com/MrNerverDie/MiniWeChat-Server [2]: https://github.com/MrNerverDie/MiniWeChat-Client