使用netty的tcp收发消息的时候发现会出现分包问题,发送三次短数据后,再发送长数据就会分包。
查询后发现大致有两种方法:一种是手动拼接包,一种是重写编解码。然后就各种百度,选择了重写编解码,自己写两个编解码,然后发现发送数据还是有问题,服务端收到的包还是少数据,未知原因。
后来在公司发现有本叫《Netty权威指南(第2版)》,翻了翻,发现可以不用重写编解码,用已经有的也可以解决分包问题:在ChannelPipeline使用的编解码前面加上LengthFieldBasedFrameDecoder和LengthFieldPrepender就能自动解决问题。
代码结构:(maven工程,使用netty和spring)
maven导入包,pom.xml加入:
org.springframework
spring-core
4.3.2.RELEASE
org.springframework
spring-context
4.3.2.RELEASE
org.springframework
spring-test
4.3.2.RELEASE
test
io.netty
netty-all
5.0.0.Alpha2
spring-testnetty.xml文件
配置文件nettytest.properties
#netty tcp server info
serveripforclient=192.168.1.30
serverportforclient=8888
#netty tcp client port
clientport=6666
服务端代码:
NettyServer.java
/**
* createtime : 2018年8月23日 下午3:52:44
*/
package com.testnetty.server;
import java.util.ArrayList;
import java.util.List;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.AdaptiveRecvByteBufAllocator;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.bytes.ByteArrayDecoder;
import io.netty.handler.codec.bytes.ByteArrayEncoder;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.Log4JLoggerFactory;
/**
* TODO
* @author XWF
*/
@Component
public class NettyServer {
private static InternalLogger logger = Log4JLoggerFactory.getInstance(NettyServer.class);
//保存客户端
private List ctxs = new ArrayList();
EventLoopGroup eventLoop = new NioEventLoopGroup();
@Value("${serverportforclient:1234}")
private int tcpserverport;
public void startNettyServer() {
System.out.println("启动Netty服务端");
new Thread(new Runnable() {
@Override
public void run() {
bind();
}
}).start();
NettyServerHandler.getInstance().setServer(this);
}
private void bind() {
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(eventLoop);
bootstrap.channel(NioServerSocketChannel.class);
bootstrap.option(ChannelOption.SO_BACKLOG, 1024); //连接数
bootstrap.option(ChannelOption.TCP_NODELAY, true); //不延迟,消息立即发送
bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true); //长连接
bootstrap.option(ChannelOption.RCVBUF_ALLOCATOR,new AdaptiveRecvByteBufAllocator(512, 1024, 2048));//缓冲大小,,initial要介于minimum和maximum之间
bootstrap.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(SocketChannel socketChannel)
throws Exception {
ChannelPipeline p = socketChannel.pipeline();
// p.addLast(new MsgDecoder(1024, 0, 4, 0, 4));
// p.addLast(new MsgEncoder());
p.addLast(new LengthFieldBasedFrameDecoder(1024,0,4,0,4));
p.addLast(new ByteArrayDecoder());
p.addLast(new LengthFieldPrepender(4));
p.addLast(new ByteArrayEncoder());
p.addLast(NettyServerHandler.getInstance());
}
});
ChannelFuture f = bootstrap.bind(tcpserverport).sync();
if (f.isSuccess()) {
logger.info("启动Netty服务成功,端口号:" + tcpserverport);
}
f.channel().closeFuture().sync();
} catch (Exception e) {
logger.error("启动Netty服务异常,异常信息:" + e.getMessage());
e.printStackTrace();
} finally {
eventLoop.shutdownGracefully();
}
}
public void shotdownNetty(){
eventLoop.shutdownGracefully();
}
//客户端连接或者断开通知
public void NotifyConnectStatus(boolean isConnected, ChannelHandlerContext ctx)
{
if(isConnected){
if(!ctxs.contains(ctx)){
ctxs.add(ctx);
}
}else{
if(ctxs.contains(ctx)){
ctxs.remove(ctx);
}
}
}
/**
* 发送消息
* @param ctx 不指定则群发
* @param data
*/
public void sendMessage(ChannelHandlerContext ctx, byte[] data) {
if(data != null && data.length > 0){
System.out.println(">>>发送的消息长度:"+data.length);
if(null == ctx) {//不指定ctx则给全部客户端发送消息
for(ChannelHandlerContext channel:ctxs) {
NettyServerHandler.getInstance().writeCommand(channel, data);
}
}else {
NettyServerHandler.getInstance().writeCommand(ctx, data);
}
}
}
/**
* 接收消息
* @param ctx
* @param data
*/
int i=1;
public void receiveMessage(ChannelHandlerContext ctx, byte[] data) {
if(ctxs.contains(ctx)){
System.out.println("<<<接收的消息长度:"+data.length);
System.out.println("收到的消息:"+new String(data));
//测试回发
// if(i++ < 5) {
// sendMessage(ctx,"test".getBytes());
// }else {
// sendMessage(ctx, "中文三四无六其把就时中文三四无六其把就时中文三四无六其把就时中文三四无六其把就时中文三四无六其把就时中文三四无六其把就时中文三四无六其把就时中文三四无六其把就时中文三四无六其把就时中文三四无六其把就时中文三四无六其把就时中文三四无六其把就时中文三四无六其把就时中文三四无六其把就时中文三四无六其把就时".getBytes());
// }
}
}
}
NettyServerHandler.java
/**
* createtime : 2018年8月23日 下午3:55:26
*/
package com.testnetty.server;
import java.net.InetSocketAddress;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.Log4JLoggerFactory;
/**
* TODO
* @author XWF
*/
@Sharable
public class NettyServerHandler extends SimpleChannelInboundHandler {
private static InternalLogger logger = Log4JLoggerFactory.getInstance(NettyServerHandler.class);
private static NettyServerHandler serverHandler = new NettyServerHandler();
private NettyServerHandler() {}
public static NettyServerHandler getInstance() {
return serverHandler;
}
private NettyServer nettyServer;
public void setServer(NettyServer nettyServer) {
this.nettyServer = nettyServer;
}
//客户端下线或者强制退出
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
String ipStr = ((InetSocketAddress) ctx.channel().remoteAddress()).getAddress().getHostAddress();
int port = ((InetSocketAddress) ctx.channel().remoteAddress()).getPort();
logger.info("捕获异常或Netty客户端主动退出,退出的客户端:"+ipStr + ":" + port);
super.channelInactive(ctx);
if(null != nettyServer) {
nettyServer.NotifyConnectStatus(false, ctx);
}
}
//有客户端连接的通知
@Override
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
logger.info("NettyServer 收到一个TCP客户端连接");
String ipStr = ((InetSocketAddress) ctx.channel().remoteAddress()).getAddress().getHostAddress();
int port = ((InetSocketAddress) ctx.channel().remoteAddress()).getPort();
logger.info("客户端的IP和port为:"+ ipStr + "," + port);
super.channelRegistered(ctx);
if(null != nettyServer) {
nettyServer.NotifyConnectStatus(true, ctx);
}
}
//向客户端发送消息
public void writeCommand(ChannelHandlerContext ctx, byte[] data) {
try {
//之前服务端发送数据客户端总是java.lang.IllegalArgumentException,minimumReadableBytes是负值
//原来是这里用ByteBuf处理了下
// ByteBuf byteBuf = Unpooled.buffer();
// byteBuf.writeBytes(data);
// ctx.writeAndFlush(byteBuf);
// ctx.channel().writeAndFlush(data);
ctx.writeAndFlush(data);//两者区别参考:https://blog.csdn.net/FishSeeker/article/details/78447684
logger.debug("向客户端发送信息成功");
} catch (Exception e) {
logger.error(e.getMessage());
}
}
//继承的是ChannelHandlerAdapter,使用
// //接收客户端的消息
// @Override
// public void channelRead(ChannelHandlerContext ctx, Object msg) {
//// ByteBuf buf = (ByteBuf) msg;
//// byte[] data = new byte[buf.readableBytes()];
//// buf.readBytes(data);
// if(msg instanceof byte[]) {
// byte[] data = (byte[]) msg;
// if(data != null && ctx != null && data.length > 0){
// if(nettyServer != null){
// nettyServer.receiveMessage(ctx, data);
// }
// }
// }
// }
//继承的是SimpleChannelInboundHandler,使用
@Override
protected void messageReceived(ChannelHandlerContext ctx, byte[] msg) throws Exception {
if(msg != null && ctx != null && msg.length > 0){
if(nettyServer != null){
nettyServer.receiveMessage(ctx, msg);
}
}
}
}
客户端代码:
NettyClient.java
/**
* createtime : 2018年8月23日 下午3:18:18
*/
package com.testnetty.client;
import java.net.InetSocketAddress;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.AdaptiveRecvByteBufAllocator;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.bytes.ByteArrayDecoder;
import io.netty.handler.codec.bytes.ByteArrayEncoder;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.Log4JLoggerFactory;
/**
* TODO
* @author XWF
*/
@Component
public class NettyClient {
private static InternalLogger logger = Log4JLoggerFactory.getInstance(NettyClient.class);
EventLoopGroup worker = null;
ChannelFuture futrue = null;
private static boolean needReset = false;//是否需要重连
NettyClientHandler clientHandler = new NettyClientHandler();
@Value("${serveripforclient:'127.0.0.1'}")
private String serveripforclient;
@Value("${serverportforclient:1234}")
private int serverportforclient;
@Value("${clientport:4321}")
private int clientport;
public void startNettyClient() {
System.out.println("启动Netty客户端");
new Thread(new Runnable() {
@Override
public void run() {
startClient();
}
}).start();
}
private void startClient() {
//worker负责读写数据
worker = new NioEventLoopGroup();
try {
//辅助启动类
Bootstrap bootstrap = new Bootstrap();
//设置线程池
bootstrap.group(worker);
//设置socket工厂
bootstrap.channel(NioSocketChannel.class);
bootstrap.localAddress(clientport);
bootstrap.option(ChannelOption.TCP_NODELAY, true); //不延迟,消息立即发送
//如果设置缓冲过小,消息过长可能会:java.lang.IllegalArgumentException: minimumReadableBytes: -1769478984 (expected: >= 0)
bootstrap.option(ChannelOption.RCVBUF_ALLOCATOR,new AdaptiveRecvByteBufAllocator(512, 1024, 2048));//缓冲大小,initial要介于minimum和maximum之间
clientHandler.setClient(this);
//设置管道
bootstrap.handler(new ChannelInitializer() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
//获取管道
ChannelPipeline pipeline = socketChannel.pipeline();
// pipeline.addLast(new MsgDecoder(1024, 0, 4, 0, 4));
// pipeline.addLast(new MsgEncoder());
pipeline.addLast(new LengthFieldBasedFrameDecoder(1024,0,4,0,4));
pipeline.addLast(new ByteArrayDecoder());
pipeline.addLast(new LengthFieldPrepender(4));
pipeline.addLast(new ByteArrayEncoder());
//处理类
pipeline.addLast(clientHandler);
}
});
//发起异步连接操作
futrue = bootstrap.connect(new InetSocketAddress(serveripforclient,serverportforclient)).sync();
startReConnectThread();
//等待客户端链路关闭
futrue.channel().closeFuture().sync();
} catch (InterruptedException e) {
needReset = true;
logger.error("发生异常1:"+e.getMessage());
} catch (Exception e){
needReset = true;
logger.error("发生异常2:"+e.getMessage());
}finally {
//优雅的退出,释放NIO线程组
worker.shutdownGracefully();
}
}
public void startReConnectThread() {
while(true){
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(needReset){
logger.warn("与服务器连接丢失,重新连接服务器。");
restartClient();
}else{
logger.info("与服务器已经连接...");
}
}
}
public void closeClient(){
try {
//等待客户端链路关闭
futrue.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//优雅的退出,释放NIO线程组
worker.shutdownGracefully();
}
}
public void restartClient(){
logger.info("关闭客户端释放资源,5s后重新连接服务器");
closeClient();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
startClient();
}
public void connectEstablished(boolean bool){
needReset = !bool;
}
//发消息
public void sendMessage(byte[] bytes){
System.out.println(">>>>>>给服务端发消息的长度:"+bytes.length);
clientHandler.sendMessage(bytes);
}
//接收消息
protected void messageReceived(byte[] bytes){
System.out.println("<<<<<<接收服务端消息长度:"+bytes.length);
System.out.println("客户端收到了消息:"+new String(bytes));
}
}
NettyClientHandler.java
/**
* createtime : 2018年8月23日 下午3:29:04
*/
package com.testnetty.client;
import java.net.InetSocketAddress;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.Log4JLoggerFactory;
/**
* TODO
* @author XWF
*/
@Sharable
public class NettyClientHandler extends SimpleChannelInboundHandler {
private static InternalLogger logger = Log4JLoggerFactory.getInstance(NettyClientHandler.class);
private NettyClient nettyClient;
ChannelHandlerContext ctx = null;
public void setClient(NettyClient nettyClient) {
this.nettyClient = nettyClient;
}
//与服务器建立连接
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
this.ctx = ctx;
String ipStr = ((InetSocketAddress) ctx.channel().remoteAddress()).getAddress().getHostAddress();
int port = ((InetSocketAddress) ctx.channel().remoteAddress()).getPort();
logger.info("连接建立,连接到服务器:"+ipStr+":"+port);
nettyClient.connectEstablished(true);
}
//与服务器断开连接
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
ctx = null;
logger.info("与服务器连接已断开");
nettyClient.connectEstablished(false);
}
//发生异常
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//关闭管道
ctx.channel().close();
logger.error("异常");
nettyClient.connectEstablished(false);
//打印异常信息
cause.printStackTrace();
}
//发消息
public void sendMessage(byte[] bytes){
if(ctx != null){
//给服务器发消息
ctx.channel().writeAndFlush(bytes);
}else{
logger.warn("连接已断开,无法发送消息。");
}
}
//如果继承SimpleChannelInboundHandler,使用
//接收消息
@Override
protected void messageReceived(ChannelHandlerContext ctx, byte[] bytes) throws Exception {
if(null != nettyClient) {
nettyClient.messageReceived(bytes);
}
}
//如果继承ChannelHandlerAdapter,使用
//接收消息
// @Override
// public void channelRead(ChannelHandlerContext ctx, Object msg) {
// if(msg instanceof byte[]) {
// byte[] data = (byte[]) msg;
// if(data != null && ctx != null && data.length > 0){
// if(nettyClient != null){
// nettyClient.messageReceived(data);
// }
// }
// }
// }
}
测试main:
/**
* createtime : 2018年8月23日 下午3:13:59
*/
package com.testnetty;
import org.springframework.context.support.AbstractXmlApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.testnetty.client.NettyClient;
import com.testnetty.server.NettyServer;
/**
* TODO
* @author XWF
*/
public class TestNettyMain {
public static AbstractXmlApplicationContext ac;
/**
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
ac = new ClassPathXmlApplicationContext("classpath:spring/spring-testnetty.xml");
NettyServer ns = ac.getBean(NettyServer.class);
NettyClient nc = ac.getBean(NettyClient.class);
// ns.startNettyServer();
// Thread.sleep(3000);
// while(true) {
// ns.sendMessage(null,"hello".getBytes());
// Thread.sleep(500);
// ns.sendMessage(null,"hello".getBytes());
// Thread.sleep(500);
// ns.sendMessage(null,"hello".getBytes());
// Thread.sleep(500);
// ns.sendMessage(null,"hello".getBytes());
// Thread.sleep(500);
// ns.sendMessage(null,"中文三四无六其把就时中文三四无六其把就时中文三四无六其把就时中文三四无六其把就时中文三四无六其把就时中文三四无六其把就时中文三四无六其把就时中文三四无六其把就时中文三四无六其把就时中文三四无六其把就时中文三四无六其把就时中文三四无六其把就时中文三四无六其把就时中文三四无六其把就时中文三四无六其把就时".getBytes());
// Thread.sleep(500);
// }
nc.startNettyClient();
Thread.sleep(3000);
while(true) {
nc.sendMessage("hello".getBytes());
Thread.sleep(500);
nc.sendMessage("hello".getBytes());
Thread.sleep(500);
nc.sendMessage("hello".getBytes());
Thread.sleep(500);
nc.sendMessage("hello".getBytes());
Thread.sleep(500);
nc.sendMessage("中文三四无六其把就时中文三四无六其把就时中文三四无六其把就时中文三四无六其把就时中文三四无六其把就时中文三四无六其把就时中文三四无六其把就时中文三四无六其把就时中文三四无六其把就时中文三四无六其把就时中文三四无六其把就时中文三四无六其把就时中文三四无六其把就时中文三四无六其把就时中文三四无六其把就时".getBytes());
Thread.sleep(500);
}
// System.out.println("main function");
}
}
先把服务端注释打开把客户端注释掉,运行服务端;再把服务端注释掉打开客户端的注释,运行客户端;然后互发消息4短1长while死循环;
结果:
存在问题的编解码代码也贴一个:
MsgEncoder.java
/**
* createtime : 2018年8月23日 下午3:37:09
*/
package com.testnetty.nettycodec;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
/**
* TODO
* @author XWF
*/
public class MsgEncoder extends MessageToByteEncoder {
//消息长度+消息
@Override
protected void encode(ChannelHandlerContext ctx, byte[] msg, ByteBuf out) throws Exception {
if(null == msg) {
System.err.println("msg == null");
return ;
}
int len = msg.length;
System.out.println("编码body长度:"+len);
out.writeInt(len);//消息长度,int,4 bytes
out.writeBytes(msg);//消息体
}
}
MsgDecoder.java
/**
* createtime : 2018年8月23日 下午3:39:52
*/
package com.testnetty.nettycodec;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
/**
* TODO
* @author XWF
*/
public class MsgDecoder extends LengthFieldBasedFrameDecoder {
//消息头只有消息长度字段,int类型,4 bytes
private static final int HEADER_SIZE = 4;
private int length;//消息体的长度,不包括消息头
/**
*
* @param maxFrameLength 每个帧数据的最大长度
* @param lengthFieldOffset 长度字段的偏移(消息长度就是第一个数据,偏移 0 byte)
* @param lengthFieldLength 长度字段的长度(这里用的int,4 bytes长度)
* @param lengthAdjustment 长度修正(length如果包括消息头和消息体的长度,则要修正成消息体的长度,用负的消息头长度修正)
* @param initialBytesToStrip 解码时要跳过的长度(这里解码不需要消息头,跳过4 bytes即可)
*/
public MsgDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength, int lengthAdjustment,
int initialBytesToStrip) {
super(maxFrameLength, lengthFieldOffset, lengthFieldLength, lengthAdjustment, initialBytesToStrip);
}
@Override
protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
if(null == in) {
return null;
}
System.out.println("======================================一共长度:"+in.readableBytes());
if(in.readableBytes() < HEADER_SIZE) {//可读长度比消息头长度小
return null;
}
length = in.readInt();//获得消息长度
System.out.println("in.readableBytes()="+in.readableBytes());
System.out.println("decode length:"+length);
System.out.println("比较:"+(in.readableBytes() < length));
if(in.readableBytes() < length) {//可读长度比消息长度小
throw new Exception("实际消息长度太小???????????");
}
ByteBuf bodyBuf = in.readBytes(length);
byte[] bytes = new byte[bodyBuf.readableBytes()];
bodyBuf.readBytes(bytes);
return bytes;//只返回消息体
}
}