nettty

https://www.jianshu.com/p/e58674eb4c7a

1. Netty - 异步和事件驱动

1. Netty 能够帮助搭建允许系统能够扩展到支持150000名并发用户。

2. Netty 设计关键: 异步 + 事件驱动

1.1 Java网络编程(BIO)

典型的BIO服务端:

1. 一个主线程在某个port监听,等待客户端连接。

2. 当接收到客户端发起的连接时,创建一个新的线程去处理客户端请求。

3. 主线程重新回到port监听,等待下一个客户端连接。

缺点:

1. 每个新的客户端Socket都需要创建一个新的Thread处理,将会导致大量的线程处于休眠状态。

2. 每个线程都有调用栈的内存分配,连接数非常多时,耗费较多内存。

3. 连接数比较多时,创建大量线程,上下文切换所带来的开销较大。

代码:


 
 
  1. public void serve(int port) throws IOException {
  2. // 创建Socket
  3. ServerSocket serverSocket = new ServerSocket(port);
  4. // 等待客户端连接
  5. Socket clientSocket = serverSocket.accept();
  6. // 创建输入流
  7. BufferedReader in = new BufferedReader( new InputStreamReader(clientSocket.getInputStream()));
  8. PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
  9. String request, response;
  10. while((request = in.readLine()) != null) {
  11. if( "Done".equals(request)) {
  12. break;
  13. }
  14. response = processRequest(request);
  15. out.println(response);
  16. }
  17. }

1.2 Java NIO

1. 使用Selector来实现Java的非阻塞I/O操作。将多个Socket的读写状态绑定到Selector上,允许在任何时间检查任意的读操作/写操作的完成状态。

2. 允许单个线程处理多个并发的连接。

1.3 Netty的核心组件

Netty的主要构件块:

1. Channel

2. 回调

3. Future

4. 事件和ChannelHandler

1.3.1 Channel

Channel是传入(入站)或者传出(出站)数据的载体(如一个文件、一个Socket或一个硬件设备)。可以被打开或者被关闭,连接或断开连接。

1.3.2 回调

回调只是:先写一段代码,该段代码在将来某个适当的时候会被执行。Netty大量使用了回调,比如:某ChannelHandler中的channelActive()方法则是一个回调,表示连接建立时,请执行该段回调代码。

1.3.3 Future

异步操作占位符。在操作完成时,提供结果的访问。

JDK提供的Future和ChannelFuture对比:

1. JDK提供的Future需要手动检查对应的操作是否完成,或一直阻塞直到它完成

2. ChannelFuture能够注册Listener监听器,监听器的回调函数operationComplete()能异步的在操作完成时被调用。

代码:


 
 
  1. public static void connect() {
  2. Channel channel = CHANNEL_FROM_SOMEWHERE;
  3. ChannelFuture future = channel.connect( new InetSocketAddress( "127.0.0.1", 9080));
  4. future.addListener( new ChannelFutureListener() {
  5. @Override
  6. public void operationComplete(ChannelFuture future) throws Exception {
  7. if(future.isSuccess()) {
  8. ByteBuf buf = Unpooled.copiedBuffer( "hello", Charset.defaultCharset());
  9. ChannelFuture wf = future.channel().writeAndFlush(buf);
  10. // ...
  11. } else {
  12. // 失败后可尝试重连/切换链路
  13. future.cause().printStackTrace();
  14. }
  15. }
  16. })
  17. }

1.3.4 事件和ChannelHandler

1. 事件:发生某种事件触发适当的动作。比如入站触发事件: 链路激活(channelActive)/数据可读(channelRead)/发生异常(exceptionCaught)/...

2. Channelhandler:一组为了响应特定事件而被执行的回调函数。如:ChannelInboundHanderAdapter.java是一个入站事件

1.3.5 Channel和EventLoop关系:

Channel和EventLoop都是Netty核心概念,而且有一些约定俗成的规定,能帮助编程和理解:

1. 单个Channel只会映射到单个EventLoop

2. 单个EventLoop可以处理多个Channel(1:n关系)

3. 一个EventLoop在其生命周期内只能绑定到一个线程上4. 由于单个Channel在其生命周期中只会有一个I/O线程,所以ChannelPipeline中多个ChannelHandler无需关心同步互斥问题

2. 第一款Netty应用程序

1. ChannelHandler用于构建应用业务逻辑。往往封装了为响应特定事件而编写的回调函数

2. 本节主要讲解一个超级简单的Netty应用程序,回显服务: 客户端建立连接后,发送一个或多个消息。服务端收到后,将消息返回。

2.3 编写Echo服务器

Netty服务端至少需要两个部分: 一个ChannelHandler + 引导(Bootstrap)

2.3.1 ChannelHandler和业务逻辑

继承ChannelInboundHandlerAdapter类,感兴趣的入站方法:

1. channelRead() - 对于每个传入的消息都要调用

2. channelReadComplete() - 当前批量读取中的最后一条数据

3. exceptionCaught() - 读取操作期间,有异常抛出时调用

代码:


 
 
  1. @ChannelHandler.Sharable
  2. public class EchoServerHandler extends ChannelInboundHandlerAdapter{
  3. /**
  4. * 每次传入的消息都要调用
  5. */
  6. @Override
  7. public void channelRead(ChannelHandlerContext ctx, Object msg) {
  8. ByteBuf in = (ByteBuf) msg;
  9. System.out.println(
  10. "Server received: " + in.toString(CharsetUtil.UTF_8));
  11. ctx.write(in);
  12. }
  13. /**
  14. * 读完当前批量中的最后一条数据后,触发channelReadComplete(...)方法
  15. */
  16. @Override
  17. public void channelReadComplete(ChannelHandlerContext ctx)
  18. throws Exception {
  19. ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)
  20. .addListener(ChannelFutureListener.CLOSE);
  21. }
  22. /**
  23. * 异常捕获
  24. */
  25. @Override
  26. public void exceptionCaught(ChannelHandlerContext ctx,
  27. Throwable cause) {
  28. cause.printStackTrace();
  29. ctx.close();
  30. }
  31. }

解释:

1. channelRead和channelReadComplete理解:当批量消息后最后一条数据被channelRead(...)后触发channelReadComplete事件。

2. ctx.write(...)只是将消息暂时存放在ChannelOutboundBuffer中,等待flush(...)操作

3. @Sharable注解:本质是声明该ChannelHandler全局单例。可被多个Channel安全的共享。标注了@Sharable注解的ChannelHandler请注意不能有对应的状态

4. 完整代码地址

2.3.2 引导服务器

1. 引导服务器主要打开Netty的Channel。并分配对应的EventLoop和ChannelPipeline。

2. 一个Channel只有一个ChannelPipeline。ChannelPipeline是由一组ChannelHandler组成的责任链。

代码:


 
 
  1. EventLoopGroup group = new NioEventLoopGroup();
  2. try {
  3. ServerBootstrap b = new ServerBootstrap();
  4. b.group(group)
  5. .channel(NioServerSocketChannel.class)
  6. .localAddress( new InetSocketAddress(port))
  7. .childHandler( new ChannelInitializer<SocketChannel>() {
  8. @Override
  9. public void initChannel(SocketChannel ch) throws Exception {
  10. ch.pipeline().addLast( new EchoServerHandler());
  11. }
  12. });
  13. } finally {
  14. group.shutdownGracefully().sync();
  15. }

2.4 编写Echo客户端

客户端将会:

1. 建立连接

2. 发送消息

3. 关闭连接

2.4.1 ChannelHandler客户端逻辑

1. Java是通过GC可达性分析来实现垃圾回收。对于Netty传输中的ByteBuf,使用的是引用计数算法。也就是说:如果你使用了Netty,需要你亲自考虑是否需要手动释放对象。判断方法,后文将会给出

2. 扩展SimpleChannelInboundHandler类处理任务的Handler,无需手动释放对象。SimpleChannelInboundHandler.java中方法channelRead()中会负责释放引用。

3. 客户端发送消息条数和服务端接收的消息条数是不对应的。除非处理了TCP的粘包黏包。

代码:


 
 
  1. // SimpleChannelInboundHandler<T>中channelRead方法负责释放对象msg引用
  2. public abstract class SimpleChannelInboundHandler<I> ...{
  3. public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
  4. boolean release = true;
  5. try {
  6. // ...
  7. } finally {
  8. if (autoRelease && release) {
  9. // 减少对象msg引用计数
  10. ReferenceCountUtil.release(msg);
  11. }
  12. }
  13. }
  14. }

问:ChannelHandler中何时需要主动释放引用?

1. 扩展的类不是: SimpleChannelInboundHandler,且该对象msg不会传给下一个ChannelHandler

2. 扩展的类不是: SimpleChannelInboundHandler,且该对象msg不会被ctx.write(...)

2.4.2 引导客户端

给出引导客户端关键代码,完整代码请参考地址

代码:


 
 
  1. EventLoopGroup group = new NioEventLoopGroup();
  2. try {
  3. Bootstrap b = new Bootstrap();
  4. b.group(group)
  5. .channel(NioSocketChannel.class)
  6. .remoteAddress( new InetSocketAddress(host, port))
  7. .handler( new ChannelInitializer<SocketChannel>() {
  8. @Override
  9. public void initChannel(SocketChannel ch)
  10. throws Exception {
  11. ch.pipeline().addLast(
  12. new EchoClientHandler());
  13. }
  14. });
  15. // 下面两行代码可以删除
  16. ChannelFuture f = b.connect().sync();
  17. f.channel().closeFuture().sync();
  18. } finally {
  19. group.shutdownGracefully().sync();
  20. }

附录

1.完整代码地址

2.netty-in-action下载地址

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值