• 【迷你微信】基于MINA、Hibernate、Spring、Protobuf的即时聊天系统:2.技术简介之MinaFilter(1)


    项目Logo
    欢迎阅读我的开源项目《迷你微信》服务器《迷你微信》客户端

    Filter

    filter:过滤器?(不知道是不是这么翻译,算了知道意思就好了╮(╯▽╰)╭),这种东西在很多语言中都是有的,功能是在某两层之间插入一层,进行拦截加工处理,这样可以方便的添加和删除处理,并且可以添加多层的Fileter。

    在Mina的Filter之中,filter是有序的,也就是说,根据你加入filter的方式不同,运行的顺序也是不同的,加入方法有点类似于链表。对Mina中Filter的详细使用请参考Mina Filter,在这里,我们只概述两种Filter的使用:

    ProtocolCodecFilter

    协议编码过滤器,这是一个在服务器接收到客户端数据,或者服务器往客户端发送数据时使用的一个编码器(在客户端的使用亦然),由于网络的传输只能通过字节流或者字符串,所以数据对象的发送接收都必须通过编码(成字节流)和解码(成对象)。这时,只要在网络层添加这么一个ProtocolCodecFilter,便可以隐藏编码和解码的实现模块,而对逻辑层的表现为直接发送对象,接收对象,简便了使用。废话不多说,咱们先来看看ProtocolCodecFilter的使用:

    首先,在开启服务器网络连接的时候添加一个ProtocolCodecFilter (代码来自开源项目《迷你微信》服务器

        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();
            }
        }
    

    MinaEncoder

    大家发现,插入的这个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 写入通往客户端的输出流。其中

    • IoBuffer buffer = IoBuffer.allocate(sizeOfAll); 表示只开sizeOfAll这么大的输出流,超出后超出的部分将被直接抛弃。

    MinaDecoder

    接着,轮到MinaDecoder了** (代码来自开源项目《迷你微信》服务器)**

    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

  • 相关阅读:
    vue绑定值与字符串拼接两种写法
    cmd 总是很卡,执行一条指令就卡死
    生产工具vscode
    js 关于 array 的相关操作––
    webAssembly
    github上fork别人的分支到目录下  
    68.Promise和setTimeout的区别
    67、Promise 构造函数是同步执行还是异步执行,那么 then 方法呢?
    66、深入理解 promise:promise的三种状态与链式调用
    65.ES6新的特性有哪些?
  • 原文地址:https://www.cnblogs.com/xiaozefeng/p/4480926.html
Copyright © 2020-2023  润新知