目录
一、发现问题
二、自定义协议
三、编解码链条
四、运行结果
源码地址:
继续上篇帖子的内容,https://blog.csdn.net/weixin_43599368/article/details/84206351
利用java原生序列化方式来编码解码,其余代码如下:
RpcClient:
package rpcserver.client;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
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 rpcserver.common.InputParam;
import rpcserver.common.OriginJava.ClientMessageDecoder;
import rpcserver.common.OriginJava.ClientMessageEncoder;
public class RpcClient {
int port;
String host;
public RpcClient(int port, String host) {
this.port = port;
this.host = host;
}
public void run() {
EventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
try {
bootstrap.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline()
// 第一种java原生的序列化方式
.addLast("decoder", new ClientMessageDecoder())
.addLast("encoder", new ClientMessageEncoder())
.addLast(new RpcClientHandler());
}
});
Channel channel = bootstrap.connect(host, port).sync().channel();
for(int i = 0; i < 100; i ++) {
InputParam inputParam = new InputParam();
inputParam.setNum1(i);
inputParam.setNum2(i * 2);
channel.writeAndFlush(inputParam);
System.out.println("client 发送出去的信息是" + inputParam.toString());
}
//从键盘读出一个字符,然后返回它的Unicode码;目的是等待client接收完消息再退出
System.in.read();
} catch (Exception e) {
e.printStackTrace();
} finally {
group.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
new RpcClient(8080, "127.0.0.1").run();
}
}
RpcClientHandler:
public class RpcClientHandler extends SimpleChannelInboundHandler {
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, Object outputParam) throws Exception {
System.out.println("client接受到的数据是:" + outputParam.toString());
}
}
RpcServer:
package rpcserver.server;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
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 rpcserver.common.OriginJava.ServerMessageDecoder;
import rpcserver.common.OriginJava.ServerMessageEncoder;
public class RpcServer {
private int port;
public RpcServer(int port) {
this.port = port;
}
public void run() {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline()
// 第一种java原生的序列化方式
.addLast("decoder", new ServerMessageDecoder())
.addLast("encoder", new ServerMessageEncoder())
.addLast(new RpcServerHandler());
}
})
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture f = serverBootstrap.bind(port).sync();
f.channel().closeFuture().sync();
} catch (Exception e) {
System.out.println("RpcServer error:" + e);
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
System.out.println("RpcServer 关闭了");
}
}
public static void main(String[] args) {
int port;
if (args.length > 0) {
port = Integer.parseInt(args[0]);
} else {
port = 8080;
}
new RpcServer(port).run();
}
}
RpcServerHandler:
public class RpcServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
InputParam inputParam = (InputParam) msg;
System.out.println("server 接受到的数据是 " + inputParam.toString());
OutputParam outputParam = new OutputParam();
outputParam.setStr1("第一个数是:"+String.valueOf(inputParam.getNum1()));
outputParam.setStr2("第二个数是:"+String.valueOf(inputParam.getNum2()));
ctx.writeAndFlush(outputParam);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
运行
client连续发送2条数据,运行结果如下:
而client发送100条数据,却出现问题:
server端接收到13条就报错,怀疑发生了tcp粘包与拆包。
我们通过自定义协议来解决tcp粘包拆包问题
首先定义一个常量,用来标记开始位
public class TcpPackageProtocolHead {
public static int head = 0X76;
}
然后定义协议类
public class TcpPackageProtocol {
private int head = TcpPackageProtocolHead.head;
private int messageLength;
private byte[] message;
public TcpPackageProtocol() {
}
public TcpPackageProtocol(int messageLength, byte[] message) {
this.messageLength = messageLength;
this.message = message;
}
public int getHead() {
return head;
}
public void setHead(int head) {
this.head = head;
}
public int getMessageLength() {
return messageLength;
}
public void setMessageLength(int messageLength) {
this.messageLength = messageLength;
}
public byte[] getMessage() {
return message;
}
public void setMessage(byte[] message) {
this.message = message;
}
}
协议解析作为一个单独的encoder和decoder,所以整个消息编解码流程变为如下:
原本:
添加了protocol之后:
1.ProtocolDecoder:
public class ProtocolDecoder extends ByteToMessageDecoder {
public final int BASE_LENGTH = 4 + 4;
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List
2.ClientMessageDecoder:
protocol -> message,protocol中有效的是byte[],相当于byte[]转成OutputParam,也就是OutputParam的反序列化,借助ObjectInputStream来实现。
public class ClientMessageDecoder extends MessageToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, TcpPackageProtocol msg, List
3.ClientMessageEncoder:
message->protocol,相当于InputParam的序列化.此处想了半天,因为发现ObjectOutputStream.writeObject(aa)返回值是void,而我们需要把序列化的结果设置给protocol的byte[],后来才知道返回值包含在aa中了,所以借助bytebuf来读取序列化后的结果。
public class ClientMessageEncoder extends MessageToMessageEncoder {
@Override
protected void encode(ChannelHandlerContext ctx, InputParam msg, List
参考原本encoder的写法:
就是将返回值写到了bytebuf out中。
public class ClientMessageEncoder extends MessageToByteEncoder {
@Override
protected void encode(ChannelHandlerContext ctx, InputParam msg, ByteBuf out) throws Exception {
ObjectOutputStream o = new ObjectOutputStream(new ByteBufOutputStream(out));
o.writeObject(msg);
}
}
4.ProtocolEncoder:
public class ProtocolEncoder extends MessageToByteEncoder {
@Override
protected void encode(ChannelHandlerContext ctx, TcpPackageProtocol msg, ByteBuf out) throws Exception {
// 1.写入消息的开头的信息标志(int类型)
out.writeInt(TcpPackageProtocolHead.head);
// 2.写入消息的长度(int 类型)
out.writeInt(msg.getMessageLength());
// 3.写入消息的内容(byte[]类型)
out.writeBytes(msg.getMessage());
}
}
ServerDecoder和Encoder类似,不解释直接贴代码:
public class ServerMessageDecoder extends MessageToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, TcpPackageProtocol msg, List
public class ServerMessageEncoder extends MessageToMessageEncoder {
@Override
protected void encode(ChannelHandlerContext ctx, OutputParam msg, List
RpcClient:
public class RpcClient {
int port;
String host;
public RpcClient(int port, String host) {
this.port = port;
this.host = host;
}
public void run() {
EventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
try {
bootstrap.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline()
.addLast(new ClientMessageDecoder())
.addLast(new ProtocolEncoder())
.addLast(new ClientMessageEncoder())
.addLast(new RpcClientHandler());
}
});
Channel channel = bootstrap.connect(host, port).sync().channel();
for(int i = 0; i < 100; i ++) {
InputParam inputParam = new InputParam();
inputParam.setNum1(i);
inputParam.setNum2(i * 2);
channel.writeAndFlush(inputParam);
System.out.println("client 发送出去的信息是" + inputParam.toString());
}
//从键盘读出一个字符,然后返回它的Unicode码;目的是等待client接收完消息再退出
System.in.read();
} catch (Exception e) {
e.printStackTrace();
} finally {
group.shutdownGracefully();
}
}
public static void main(String[] args) throws Exception {
new RpcClient(8080, "127.0.0.1").run();
}
}
注意netty handler执行顺序问题,从其他博客贴过来的图比较直观一些:
Decoder顺序执行,Encoder逆序执行。
RpcServer类似,此处不贴代码,感兴趣的同学可以到帖子最后github中下载相关源码。
运行结果如下,server可以收到client发过来的100条数据,并且client可以收到server回传的另外100条。
https://github.com/tianyaning/code.git