1.不直接使用nio的理由:
直接使用nio你需要额外处理很多问题:网络闪断、客户端重复接入、客户端安全认证、消息的编解码、半包读写等。
nio使用复杂,还有小bug。
2.netty的简单例子:
netty使用比较简单,不同的场景对一个不同的hadler。
public class MyServer {
public static void main(String[] args) {
// EventLoopGroup包含了一组线程,bosstrap用于多线程接受客户端accept连接
EventLoopGroup bossstrap = new NioEventLoopGroup();
// workstrap用于多线程处理连接后的channel读写
EventLoopGroup workerstrap = new NioEventLoopGroup();
ServerBootstrap serverBootstrap = new ServerBootstrap();
// 绑定线程组,之后,设置创建的channel为nioserverSocketChannel了,//之后绑定日志处理handler,之后绑定自定义serverInitializer,handler使用bosstrap线程组,childhandler使用workerstrap线程组
serverBootstrap.group(bossstrap, workerstrap)
.channel(NioServerSocketChannel.class).handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new MyServerInitializer());
try {
// 绑定端口,异步返回
ChannelFuture channelFuture = serverBootstrap.bind(8899).sync();
// channelFuture类似于juc的Future,线程执行后的获取结果,线程没有执行完,则堵塞。channelFuture等服务端链路关闭之后回调。
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
bossstrap.shutdownGracefully();
workerstrap.shutdownGracefully();
}
}
}
// 绑定的channel初始化器
public class MyServerInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
// 责任链模式,添加解码器。
// LengthFieldBasedFrameDecoder是对固定长度消息头步(记录了消息体长度),消息体的这种数据进行解码,防止粘包拆包。
pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
// 把消息进行消息头、消息体的形式进行打包
pipeline.addLast(new LengthFieldPrepender(4));
// byte转strig的解码器
pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
// 自定义处理器
pipeline.addLast(new MyServerHandle());
}
}
public class MyServerHandle extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println(ctx.channel().remoteAddress() + ", " + msg);
ctx.channel().writeAndFlush("from server:" + UUID.randomUUID());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
客户端
public class MyClient {
public static void main(String[] args) {
// 客户端只有一个连接通道,所以只需要一个处理通道的读写线程池就可以
EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
try {
// 对应服务端的ServerBootstrap
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class)
.handler(new MyClientInitalizer());
ChannelFuture channelFuture = bootstrap.connect("localhost", 8899).sync();
channelFuture.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
eventLoopGroup.shutdownGracefully();
}
}
}
public class MyClientInitalizer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 0, 4, 0, 4));
pipeline.addLast(new LengthFieldPrepender(4));
pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
pipeline.addLast(new MyClientHandle());
}
}
public class MyClientHandle extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println(ctx.channel().remoteAddress());
System.out.println("client output:" + msg);
ctx.writeAndFlush("from client: " + LocalDateTime.now());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush("来自客户端的问候");
}
}
3. netty相关的问题
netty有很多种编码节码,组成了netty的丰富的功能,比如netty做http服务器,netty做websocket,都有对应的编码器封装。
1.粘包拆包:
tcp流,和发送的多条数据,有可能会粘在一起,或者只发送了一半。
解决思路:1.消息定长。2.加分隔符,比如回车符。3.消息头,消息体,消息头包含消息长度。4.更复杂的应用协议。
netty对粘包拆包提供很多解码器:LineBasedFrameDecoder,利用分隔符来处理粘包拆包。还有上面例子的LengthFieldBasedFrameDecoder,利用消息头消息体的形式。
2.系列化
java自带的系列化机制的缺点。
1.无法跨语言。
2.系列化后的字节数太大。
3.系列化性能低。
xml和json:
系列化后字节数大,性能低。
其他的系列化框架
google protobuf:跨多种语言,性能高,结构化数据。
使用步骤:编写prot文件,使用prot编译器,编译文件,生成java文件,用生成java类对对象进行编码节码。
thift:跟protobuf类似,比protobuf功能更丰富。
netty对各种系列化都有对应的编码解码类,直接使用就可以。
3. 零拷贝
其实就是,直接开辟堆外内存,省去了java堆到内核的一层拷贝。
网友评论