Netty开发入门示例
服务端代码
服务端启动代码
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class DiscardServer {
private int port ;
public DiscardServer(int port) {
this.port = port;
}
public void run() throws InterruptedException {
//事件循环处理io操作,这个用于处理连接请求
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
//事件循环处理:用于处理已经连接的请求,由bossGroup组处理后分发过来
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
try {
//创建服务启动类
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(bossGroup, workerGroup)
//指定使用实例化NioServerSocketChannel来处理进来的连接请求
.channel(NioServerSocketChannel.class)
//指定 ChannelInitializer去添加channel的流水线处理器,可以添加很多个
.childHandler(new ChannelInitializer<SocketChannel>() {
//这里可以添加多个Handler到流水线进行分步处理
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new DiscardServerHandler())
.addLast(new SecondHandler());
}
})
//设置参数:使用 ChannelOption的常量或者 ChannelConfig的实现类
.option(ChannelOption.SO_BACKLOG, 128) // (5)针对接收连接的事件循环
.childOption(ChannelOption.SO_KEEPALIVE, true); // (6)针对工作线程的循环
//绑定端口,同步等待连接
ChannelFuture f = bootstrap.bind(port).sync(); // (7)
// 等待服务端监听端口关闭
f.channel().closeFuture().sync();
}finally {
//优雅关闭释放相关所有资源
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
public static void main(String[] args) throws InterruptedException {
new DiscardServer(8080).run();
}
}
服务端逻辑处理代码 Handler
DiscardServerHandler
注意重写的方法中需要最后调用父类的方法才能够调用到下一个Hander的方法
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
/**
* @Description 通过基础ChannelInboundHandlerAdapter来处理读取到的消息,这是一个适配器,可以覆盖想要重写的方法
* @Date 2020/5/2 11:43
* @Version V1.0
*/
public class DiscardServerHandler extends ChannelInboundHandlerAdapter {
//事件处理方法,当收到消息的时候会调用,msg消息是ByteBuf类型的
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("DiscardServerHandler channelRead ~~~");
ByteBuf buf = (ByteBuf) msg;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
System.out.println("receive msg:"+new String(req, "UTF-8"));
//继续调用下一个handler的channelRead方法,将将消息解码后传递个下一个handler处理
super.channelRead(ctx, new String(req, "UTF-8"));
}
//回调完成的方法将消息发出,flush方法在这里调用,性能更好,在channelRead中调用会频繁唤醒Selector
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
System.out.println("DiscardServerHandler channelReadComplete ~~");
ctx.flush();
//继续调用下一个handler的该方法
super.channelReadComplete(ctx);
}
//发生异常的时候调用的方法,可以记录日志,关系相关的channel
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
System.out.println("发生异常了。。");
}
}
SecondHandler
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
/**
* @Date 2020/5/14 13:53
* @Version V1.0
*/
public class SecondHandler extends ChannelInboundHandlerAdapter {
//事件处理方法,当收到消息的时候会调用,msg消息是ByteBuf类型的
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("SecondHandler "+msg);
String resp = "server time is "+System.currentTimeMillis();
ByteBuf response = Unpooled.copiedBuffer(resp.getBytes());
// 将响应消息写入缓冲区
ctx.write(response);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
System.out.println("SecondHandler channelReadComplete ");
ctx.flush();
super.channelReadComplete(ctx);
}
//发生异常的时候调用的方法,可以记录日志,关系相关的channel
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx,cause);
}
}
客户端代码
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
public class DiscardClient {
public static void main(String[] args) {
new DiscardClient().connect(8080,"127.0.0.1");
}
public void connect(int port , String host){
EventLoopGroup eventExecutors = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventExecutors).channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.option(ChannelOption.SO_KEEPALIVE, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new TimeClientHandler());
}
});
//发起异步连接操作
ChannelFuture sync = bootstrap.connect(host, port).sync();
//等待客户端链路关闭
sync.channel().closeFuture().sync();
}catch (Exception e){
e.printStackTrace();
}finally {
if (eventExecutors != null)
eventExecutors.shutdownGracefully();
}
}
}
TimeClientHandler
public class TimeClientHandler extends ChannelInboundHandlerAdapter {
ByteBuf firstMsg ;
public TimeClientHandler() {
byte[] bytes = "client hander rquest time".getBytes();
firstMsg = Unpooled.copiedBuffer(bytes);
}
//客户端与服务端链路建立成功的时候调用这个方法
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(firstMsg);
}
//处理服务端应答的消息
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf req = (ByteBuf)msg;
byte[] by = new byte[req.readableBytes()];
req.readBytes(by);
System.out.println("clinet get time form server"+new String(by,"UTF-8"));
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
使用解码器
上面的没有解决由于粘包拆包导致的半包读写问题,可以使用netty自带的解码器。
导致拆包粘包问题原因:
1.应用程序写入的字节数大于套接字缓冲区的大小
2.进行MMS(最大报文段长度)大小的TCP分段
3.以太网帧的payload大于MTU进行IP分片
常用解决方案:
1.消息定长 ,例如固定数据报文的长度为200字节,不够长度则空格补齐
2.在包尾增加回车换行符进行分割, 例如FTP协议
3.将消息分为消息头和消息体,消息头中包含消息总长度(或者消息体长度)字段,通常的设计思路是为消息头的第一个字段使用int32来表示消息的总长度
4.使用更复杂的应用层协议
常用解码器如下:
protected void initChannel(SocketChannel ch) throws Exception {
ByteBuf byteBuf = Unpooled.copiedBuffer("$".getBytes());
ch.pipeline()
//.addLast(new LineBasedFrameDecoder(4096)) //添加换行符解码器,解决拆包粘包问题
//.addLast(new DelimiterBasedFrameDecoder(4096,byteBuf)) //使用自定义的符合作为分隔的解码器
.addLast(new FixedLengthFrameDecoder(100)) //固定长度解码器
.addLast(new StringDecoder(CharsetUtil.UTF_8)) //增加字符串解码器,返回的直接就是字符串
.addLast(new NettyClient.ClientHandler());
}