本文属于小编纯手打文章,欢迎转载,注明出处即可 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