编写服务端
ChannelHandler - 接受客户端数据
channelRead()
- 对每个传入的消息都要调用;
channelReadComplete()
- 通过
ChannelInboundHandler
最后一个对channel-Read()
的调用是当前批量读取中的最后一条消息;
exceptionCaught()
- 在读取操作期间,有异常抛出时会调用j
方式:
继承CahnelInboundHandlerAdapter
的方式
不捕获异常,会发生什么情况
每个Channel
拥有一个关联的ChannelPipeline
,如果没有实现,会放到Piepeline
的尾端,但是,必须有一个实现了捕获异常的Caught()
方法
- 针对不同事件,调用handler
- 使用责任链加上事件响应的方式处理
引导服务器启动
- 业务逻辑增加了之后,需要启动这个
Channel
监听请求 - 配置
Channel
,将入站信息通知给handler实例
创建传输的server
- 建立我们自己实现的
channelHandler
EchoServer对象 - 创建
EventLoopGroup
,用来处理channel
- 创建
boostrapServer
,用于建立套接字连接 - 设置
group
- 使用
NIOSocket
套接字 - 设置请求接受端口
- 设置子channel的channelHandler
- 初始化调用,设置新的channel -> 就是我们自我实现的
EchoServer
- 设置
future
, 使用异步开启服务器并且阻塞线程等待结果 - 获取
future
的closeFuture
,同样阻塞等待所有任务结束之后发送通知 - 最后,finally 方法关闭group,并且释放所有的资源
如果想要使用阻塞的IO要如何处理:
OioServerSocketChannel
和OioEventLoopGroup
示范代码:
EchoServer:我们实现的channelHandler
public class EchoServer extends ChannelInboundHandlerAdapter { /** * 读取事件 * @param ctx * @param msg * @throws Exception */ @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { ByteBuf in = (ByteBuf) msg; System.err.println("Server receive" + in.toString(CharsetUtil.UTF_8)); } /** * 最后一次读取完成之后,发送通知到channelHandler * @param ctx * @throws Exception */ @Override public void channelReadComplete(ChannelHandlerContext ctx) throws Exception { ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE); } /** * 读取的过程 * @param ctx * @param cause * @throws Exception */ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { cause.printStackTrace(); ctx.close(); } } 复制代码
EchoServerClinet:服务器客户端开启
public class EchoServerClient { private static final int PORT = 8080; public static void main(String[] args) throws InterruptedException { if(args.length > 1){ System.err.println("server "); } final EchoServer echoServer = new EchoServer(); // 创建事件驱动器 EventLoopGroup EventLoopGroup eventLoopGroup = new NioEventLoopGroup(); try{ // 创建server bootstrap ServerBootstrap serverBootstrap = new ServerBootstrap(); // 1. 指定使用NIO传输的渠道 // 2. 指定端口 // 3. 添加一个echoseverhandler 到子channel的chanelPipeline serverBootstrap.group(eventLoopGroup) .channel(NioServerSocketChannel.class) .localAddress(new InetSocketAddress(PORT)) .childHandler(new ChannelInitializer() { // 很关键,需要使用pipeline加入事件来帮我们处理channel protected void initChannel(Channel ch) throws Exception { ch.pipeline().addLast(echoServer); } }); // 阻塞当前线程,异步绑定服务器 ChannelFuture channelFuture = serverBootstrap.bind().sync(); // 获取当前channel的closeFuture,阻塞直到线程完成 channelFuture.channel().closeFuture().sync(); }finally { // 关闭group 释放资源 eventLoopGroup.shutdownGracefully().sync(); } } } 复制代码
编写客户端
客户端需要做的事情比较简单,只需要如下步骤:
- 建立一个与socket的连接
- 发送一条或者多条消息
- 对于每一条消息,等待服务器返回处理的数据
- 关闭连接
chanelHandler实现客户端
- 继承
SimpleChannelInboundHandler
- 实现方法
channelRead0()
从服务器收到消息之后,会调用这个方法 - 重写
channelActive()
,被通知channel活跃的时候发送通知 - 重写
exceptionCaught()
,处理异常
为什么客户端要使用
SimpleChannelInboundHandler
而不是服务端的ChannelInboundHandlerAdapter
SimpleChannelInboundHandler 在处理完传入消息之后,理论上一个请求就已经被处理完成,此时channel会该类默认会释放这条传入消息的内存引用
而
ChannelInboundHandlerAdapter
处理完消息之后,此时channelRead()
结果不一定返回,因为整个过程都是异步的问题又来了,什么时候会释放channelRead()处理之后的消息资源呢
答:当channelComplete()
writeAndFlush()
之后,该消息资源才会被真正释放
运行案例
- 启动EchoServerClient
- 启动EchoClientServer
总结
以上就是一个最为简单的netty Demo
后续的笔记将会深入到netty的几个关键点
- Channel
- CahnnelHandler
- Futrue
- BootstrapServer
- EventLoop
- EventLoopGroup