关于Netty出现的那些跳不过去的坑以及Netty长连接单独获取服务端数据

本文属于小编纯手打文章,欢迎转载,注明出处即可 https://blog.csdn.net/Alex_81D/article/details/80606023

其实想了很久才写这篇文章,因为网上关于Netty的例子的确太多,但当初因为Netty获取数据问题困扰了我将近一个月,内心饱受煎熬,先说出自己的问题所在:

1.在实时通信时需要做长连接,让他开启一个持续的永不关闭(或者可以手动关闭的)通道,通道开启时,需要做一个三次握手的身份验证。 验证结束后才可以操作,在通道关闭之前都不需要在做二次验证。
2.Netty他是一个异步的请求方式,底层采用了回调的方法,只有服务器端发送给客户端消息他才执行
3.这里牵扯到一个回调的异步获取问题
4.Netty使用的是多线程的操作方法想要实时获取还面临一个线程阻塞问题

想到过用文件流读写的方式获取数据,最后失败了,原因是因为Netty属于异步传输数据,Netty线程与获取数据线程互不干扰,各自执行可能会导致数据会先读后写,获取数据失败;加入多线程后,发现依然没能解决这个问题;甚至加入回调函数也没有解决。其实博主的目的很简单,能实时长连接无阻塞通信,并且可以获取服务端返回的消息;接下来上代码(client端)

自定义协议头:

package com.uxsino.NettyClient.msgBean;

import com.uxsino.NettyClient.util.GetInfo;

import java.util.Arrays;

/**
 * 消息头部信息
 * @author admin
 *
 */

public class CesHeader {

	//数据包序号
	private byte[] fragNO = new byte[4];

	//总数据包
	private byte[] totalFrag  = new byte[4];

	//数据包发包序号
	private byte[] seq  = new byte[4];

	public byte[] getFragNO() {
		return fragNO;
	}

	public void setFragNO(long fragNO) {
		this.fragNO = GetInfo.getByte(fragNO);
	}

	public byte[] getTotalFrag() {
		return totalFrag;
	}

	public void setTotalFrag(long totalFrag) {
		this.totalFrag = GetInfo.getByte(totalFrag);
	}

	public byte[] getSeq() {
		return seq;
	}

	public void setSeq(long seq) {
		this.seq = GetInfo.getByte(seq);
	}

	public CesHeader(byte[] fragNO, byte[] totalFrag, byte[] seq) {
		this.fragNO = fragNO;
		this.totalFrag = totalFrag;
		this.seq = seq;
	}

    public CesHeader(long fragNO, long totalFrag,long seq) {
        setFragNO(fragNO);
        setTotalFrag(totalFrag);
        setSeq(seq);
    }

	@Override
	public String toString() {
		return "CesHeader{" +
				"fragNO=" + Arrays.toString(fragNO) +
				", totalFrag=" + Arrays.toString(totalFrag) +
				", seq=" + Arrays.toString(seq) +
				'}';
	}
}
package com.uxsino.NettyClient.msgBean;

import com.uxsino.NettyClient.util.GetInfo;

import java.util.Arrays;

/**
 * 消息主体
 * @author admin
 *
 */

public class CesMessage {

	//协议的消息头
	private CesHeader cesHeader;

	//消息类型
	private int orderType;

	//消息长度
	private byte[] dataLength;

	//消息内容
	public byte[] dataInfo = new byte[1019];

	public CesHeader getCesHeader() {
		return cesHeader;
	}

	public void setCesHeader(CesHeader cesHeader) {
		this.cesHeader = cesHeader;
	}

	public int getOrderType() {
		return orderType;
	}

	public void setOrderType(byte orderType) {
		this.orderType = orderType;
	}

	public byte[] getDataLength() {
		return dataLength;
	}

	public void setDataLength(long dataLength) {
		this.dataLength = GetInfo.getByte(dataLength);
	}

	public byte[] getDataInfo() {
		return dataInfo;
	}

	public void setDataInfo(String data) {
		for (int i=0;i<data.length();i++){
			this.dataInfo[i] = (byte) data.charAt(i);
		}
	}

	public CesMessage(CesHeader cesHeader, int orderType, byte[] dataLength, String data) {
		this.cesHeader = cesHeader;
		this.orderType = orderType;
		this.dataLength = dataLength;
		setDataInfo(data);
	}

    public CesMessage(CesHeader cesHeader, int orderType, long dataLength, String data) {
        this.cesHeader = cesHeader;
        this.orderType = orderType;
        setDataLength(dataLength);
        setDataInfo(data);
    }


	@Override
	public String toString() {
		return "CesMessage{" +
				"cesHeader=" + cesHeader +
				", orderType=" + orderType +
				", dataLength=" + Arrays.toString(dataLength) +
				", dataInfo=" + Arrays.toString(dataInfo) +
				'}';
	}
}

接下来就是Netty两个核心类

package com.uxsino.NettyClient.client;

import com.uxsino.NettyClient.codeMsgPack.MessageDecoder;
import com.uxsino.NettyClient.codeMsgPack.MessageEncoder;
import com.uxsino.NettyClient.msgBean.CesMessage;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

import java.io.FileInputStream;
import java.util.Properties;

/**
 * 做代理端通信连接
 * @author sunweidong
 */

public class NettyClient {


    private void getip() throws Exception {

    }


    private static class SingletonHolder {
        static final NettyClient instance = new NettyClient();
    }

    private static NettyClient getInstance() {
        return SingletonHolder.instance;
    }

    public static void colose() throws InterruptedException {
        final NettyClient c = NettyClient.getInstance();
        ChannelFuture cf = c.getChannelFuture();

        cf.channel().closeFuture().sync();
        c.release();
    }

    public static String response(CesMessage luck) {

        String str = null;
        try {
            SingletonHolder.instance.getChannelFuture().channel().writeAndFlush(luck);
            boolean flag = false;
            while (!flag) {
                flag = NettyClientHandler.flag;
            }

            str = NettyClientHandler.data;
        } catch (Exception e) {
            new RuntimeException("发送消息失败");
        } finally {
            NettyClientHandler.flag = false;
            NettyClientHandler.data = null;
        }
        return str;
    }

    public void release() {
        group.shutdownGracefully();
    }

    private EventLoopGroup group;
    private Bootstrap b;
    private ChannelFuture cf;

    private NettyClient() {
        group = new NioEventLoopGroup();
        b = new Bootstrap();
        b.group(group)
                //保持连接(可以不写,默认为true)
                .option(ChannelOption.SO_KEEPALIVE, true)
                //禁用nagle算法,有消息立即发送
                .option(ChannelOption.TCP_NODELAY, true)
                //发送 超过 1036 数据时
                .option(ChannelOption.RCVBUF_ALLOCATOR, new FixedRecvByteBufAllocator(1036))
                .channel(NioSocketChannel.class)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel sc) throws Exception {
                        // 初始化编码器,解码器,处理器
                        sc.pipeline().addLast(
                                new MessageDecoder(),
                                new MessageEncoder(),
                                new NettyClientHandler());
                    }
                });
    }


    public void connect() {
        try {
            Properties properties = new Properties();
            properties.load(new FileInputStream("src\\main\\resources\\Netty.properties"));
            String ip = properties.getProperty("ip");
            String p = properties.getProperty("port");
            int port = Integer.parseInt(p);
            this.cf = b.connect(ip, port).sync();
            System.out.println("远程服务器已连接,可以进行数据提交");
        } catch (Exception e) {
            new RuntimeException("服务器连接失败");
        }
    }

    public ChannelFuture getChannelFuture() {
        if (this.cf == null) {
            this.connect();
        }
        if (!this.cf.channel().isActive()) {
            this.connect();
        }
        return this.cf;
    }

}

客户端逻辑处理类:

package com.uxsino.NettyClient.client;

import com.uxsino.NettyClient.msgBean.CesMessage;
import com.uxsino.NettyClient.util.GetInfo;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.ReferenceCountUtil;
import sun.misc.BASE64Encoder;

/**
 * 客户端逻辑处理类
 * @author sunweidong
 */

public class NettyClientHandler extends ChannelInboundHandlerAdapter {

	public static  boolean flag = false;
	public static  String data = "";

	@Override
	public void channelActive(ChannelHandlerContext ctx) throws Exception {
		System.out.println("启动时会调用这个方法");
	}

	/**
	 * 接收服务端通信的核心代码,通过二进制ByteBuf
	 */
	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
		try {
			CesMessage message = (CesMessage) msg;
			//获取长度
			byte[] length = message.getDataLength();
			//获取消息内容,按长度进行截取
			byte[] msgData = message.getDataInfo();
			String dataInfo = GetInfo.getLength(length,msgData);
			data = dataInfo;
		} finally {
			ReferenceCountUtil.release(msg);
		}
	}

	@Override
	public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
		ctx.flush();
		flag = true;
	}

	/**
	 * 当出现异常时调用
	 */
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		cause.printStackTrace();
		ctx.close();
	}

}

接下来就是对自定义协议的一个编码和解码的过程:

package com.uxsino.NettyClient.codeMsgPack;

import com.uxsino.NettyClient.msgBean.CesHeader;
import com.uxsino.NettyClient.msgBean.CesMessage;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;

import java.util.List;

/**
 * 解码器
 * 继承自ByteToMessageDecoder,并重写decode方法
 * 将入站的数据从一种格式转换成另外一种格式
 * @author sunweidong
 *
 */

public class MessageDecoder extends ByteToMessageDecoder {

	@Override
	protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {

		int length = in.readableBytes();
		System.out.println("长度为:" + length);

		//获取数据包序号
		byte[] fragNO = new byte[4];
		in.readBytes(fragNO);

		//获取总数据包
		byte[] totalFrag = new byte[4];
		in.readBytes(totalFrag);

		//数据包发包序号
		byte[] seq = new byte[4];
		in.readBytes(seq);

		//组装协议头
		CesHeader header = new CesHeader(fragNO, totalFrag, seq);

		//获取协议类型,并进行判断
		byte infoType = in.readByte();

		//获取消息长度
		byte[] dataLength = new byte[4];
		in.readBytes(dataLength);

		//读取消息内容:in.readableBytes()即为剩下的字节数
		int len = in.readableBytes();
		System.out.println("剩余长度为:"+len);
		byte[] dataInfo = new byte[in.readableBytes()];
		in.readBytes(dataInfo);
		String dataStr = new String(dataInfo,"utf-8");

		CesMessage message = new CesMessage(header, infoType,dataLength, dataStr);

		out.add(message);
	}
}
package com.uxsino.NettyClient.codeMsgPack;

import com.uxsino.NettyClient.msgBean.CesHeader;
import com.uxsino.NettyClient.msgBean.CesMessage;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufOutputStream;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;

/**
 * 编码器
 * 继承自MessageToByteEncoder,并重写encode方法
 * 将出站的数据从一种格式转换成另外一种格式:将Message转换成二进制数据 发送出去(将自定义消息转换成byte[])
 * @author sunweidogng
 *
 */

public class MessageEncoder  extends MessageToByteEncoder<CesMessage> {

	@Override
	protected void encode(ChannelHandlerContext ctx, CesMessage msg, ByteBuf out) throws Exception {

		ByteBufOutputStream writer = new ByteBufOutputStream(out);

		//将msg转化为二进制数据
		if (msg != null){
			CesHeader header = msg.getCesHeader();
			//协议写入顺序
			writer.write(header.getFragNO());
			writer.write(header.getTotalFrag());
			writer.write(header.getSeq());

			writer.write(msg.getOrderType());
			writer.write(msg.getDataLength());
			writer.write(msg.getDataInfo());
		}
		writer.close();
	}
}

自定义协议工具类(这个可以不需要,纯属我的这个项目需要)

package com.uxsino.NettyClient.util;

import java.io.UnsupportedEncodingException;
import java.util.Arrays;

/**
 * 自定义协议编码的转换工具类
 */
public class GetInfo {

    public static String getLength(byte[] bytes, byte[] dataInfo) {
        String strDate = "";
        byte[] by = Arrays.copyOfRange(bytes, 0, 4);
        int length = GetInfo.bytesToInt(by);
        try {
            strDate = new String(dataInfo, "UTF-8");
            strDate = strDate.substring(0, length);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return strDate;
    }

    public static byte[] getByte(long number) {
        byte[] src = new byte[4];
        src[3] = (byte) ((number >> 24) & 0xFF);
        src[2] = (byte) ((number >> 16) & 0xFF);
        src[1] = (byte) ((number >> 8) & 0xFF);
        src[0] = (byte) (number & 0xFF);
        return src;
    }

    public static int bytesToInt(byte[] src) {
        int value;
        value = (int) ((src[0] & 0xFF)
                | ((src[1] & 0xFF) << 8)
                | ((src[2] & 0xFF) << 16)
                | ((src[3] & 0xFF) << 24));
        return value;
    }
}

至此通信整个过程结束,测试方法:博主是在controller层做的测试,将数据直接返给前台使用的,所以直接上controller的具体代码:

@GetMapping(value = "/Netty")
	public Map<String,String> getNetty(@RequestParam("userName") int count) throws InterruptedException {
		Map<String,String> map = new HashMap();
		CesMessage message = new CesMessage(new  CesHeader(1, 1, 0), 13, 8,"scnourse");
		String a1 = NettyClient.response(message);
		map.put("a1", a1);
		return map;
	}

说真话Netty是一个非常优秀的开源通信框架,在这个过程中的确出现过很多错误和弯路,代码中有很多封装以及单利之类的,大家要是有哪里看不明白或者对Netty有新见解的可以私信小编,[email protected],我们一起探讨美妙的Netty。

本文属于小编纯手打文章,欢迎转载,注明出处即可 https://blog.csdn.net/Alex_81D/article/details/80606023

猜你喜欢

转载自blog.csdn.net/alex_81d/article/details/80606023
今日推荐