作者:Grey

原文地址:Java IO学习笔记八:Netty入门

多路复用多线程方式还是有点麻烦,Netty帮我们做了封装,大大简化了编码的复杂度,接下来熟悉一下netty的基本使用。

Netty+最朴素的阻塞的方式来实现一版客户端和服务端通信的代码,然后再重构成Netty官方推荐的写法。

第一步,引入netty依赖包。

  1. <dependency>
  2. <groupId>io.netty</groupId>
  3. <artifactId>netty-all</artifactId>
  4. <version>4.1.65.Final</version>
  5. </dependency>

准备发送端

  1. import io.netty.buffer.ByteBuf;
  2. import io.netty.buffer.Unpooled;
  3. import io.netty.channel.ChannelFuture;
  4. import io.netty.channel.ChannelHandlerContext;
  5. import io.netty.channel.ChannelInboundHandlerAdapter;
  6. import io.netty.channel.ChannelPipeline;
  7. import io.netty.channel.nio.NioEventLoopGroup;
  8. import io.netty.channel.socket.nio.NioSocketChannel;
  9. import java.net.InetSocketAddress;
  10. import static java.nio.charset.StandardCharsets.UTF_8;
  11. /**
  12. * @author <a href="mailto:410486047@qq.com">Grey</a>
  13. * @since
  14. */
  15. public class NettyClientSync {
  16. public static void main(String[] args) throws Exception {
  17. NioEventLoopGroup thread = new NioEventLoopGroup(1);
  18. NioSocketChannel client = new NioSocketChannel();
  19. thread.register(client);
  20. ChannelPipeline p = client.pipeline();
  21. p.addLast(new MyInHandler());
  22. ChannelFuture connect = client.connect(new InetSocketAddress("192.168.205.138", 9090));
  23. ChannelFuture sync = connect.sync();
  24. ByteBuf buf = Unpooled.copiedBuffer("hello server".getBytes());
  25. ChannelFuture send = client.writeAndFlush(buf);
  26. send.sync();
  27. sync.channel().closeFuture().sync();
  28. System.out.println("client over....");
  29. }
  30. static class MyInHandler extends ChannelInboundHandlerAdapter {
  31. @Override
  32. public void channelRegistered(ChannelHandlerContext ctx) {
  33. System.out.println("client register...");
  34. }
  35. @Override
  36. public void channelActive(ChannelHandlerContext ctx) {
  37. System.out.println("client active...");
  38. }
  39. @Override
  40. public void channelRead(ChannelHandlerContext ctx, Object msg) {
  41. ByteBuf buf = (ByteBuf) msg;
  42. CharSequence str = buf.getCharSequence(0, buf.readableBytes(), UTF_8);
  43. System.out.println(str);
  44. ctx.writeAndFlush(buf);
  45. }
  46. }
  47. }

这个客户端主要就是给服务端(192.168.205.138:9090)发送数据, 启动一个服务端:

  1. [root@io ~]# nc -l 192.168.205.138 9090

然后启动客户端,服务端可以接收到客户端发来的数据:

  1. [root@io ~]# nc -l 192.168.205.138 9090
  2. hello server

这就是netty实现的一个客户端,再来看服务端的写法:

  1. import io.netty.channel.*;
  2. import io.netty.channel.nio.NioEventLoopGroup;
  3. import io.netty.channel.socket.SocketChannel;
  4. import io.netty.channel.socket.nio.NioServerSocketChannel;
  5. import java.net.InetSocketAddress;
  6. /**
  7. * @author <a href="mailto:410486047@qq.com">Grey</a>
  8. * @since
  9. */
  10. public class NettyServerSync {
  11. public static void main(String[] args) throws Exception {
  12. NioEventLoopGroup thread = new NioEventLoopGroup(1);
  13. NioServerSocketChannel server = new NioServerSocketChannel();
  14. thread.register(server);
  15. ChannelPipeline p = server.pipeline();
  16. p.addLast(new MyAcceptHandler(thread, new NettyClientSync.MyInHandler()));
  17. ChannelFuture bind = server.bind(new InetSocketAddress("192.168.205.1",9090));
  18. bind.sync().channel().closeFuture().sync();
  19. System.out.println("server close....");
  20. }
  21. static class MyAcceptHandler extends ChannelInboundHandlerAdapter {
  22. private final EventLoopGroup selector;
  23. private final ChannelHandler handler;
  24. public MyAcceptHandler(EventLoopGroup thread, ChannelHandler myInHandler) {
  25. this.selector = thread;
  26. this.handler = myInHandler;
  27. }
  28. @Override
  29. public void channelRegistered(ChannelHandlerContext ctx) {
  30. System.out.println("server registered...");
  31. }
  32. @Override
  33. public void channelRead(ChannelHandlerContext ctx, Object msg) {
  34. SocketChannel client = (SocketChannel) msg;
  35. ChannelPipeline p = client.pipeline();
  36. p.addLast(handler);
  37. selector.register(client);
  38. }
  39. }
  40. }

启动这个服务端,然后通过一个客户端来连接这个服务端,并且向这个服务端发送一些数据

  1. [root@io ~]# nc 192.168.205.1 9090
  2. hello
  3. hello

服务端可以感知到客户端连接并接收到客户端发来的数据

  1. client register...
  2. client active...
  3. hello

但是,这样的服务端如果再接收一个客户端连接,客户端继续发送一些数据进来,服务端就会报一个错误:

  1. An exceptionCaught() event was fired, and it reached at the tail of the pipeline. It usually means the last handler in the pipeline did not handle the exception.
  2. io.netty.channel.ChannelPipelineException: git.snippets.io.netty.NettyClientSync$MyInHandler is not a @Sharable handler, so can't be added or removed multiple times.

原因在这个博客里面说的比较清楚:Netty ChannelHandler使用报错

我们可以发现每当有新的数据可读时都会往这个channel的pipeline里加入handler,这里加的是childHander。值得注意的是,我们初始化的时候这个childHandler都是同一个实例,也就说会导致不同的channel用了同一个handler,这个从netty的设计角度来说是要避免的。因为netty的一大好处就是每一个channel都有自己绑定的eventloop和channelHandler,这样可以保证代码串行执行,不必考虑并发同步的问题。所以才会有checkMultiplicity这个方法来检查这个问题。那该怎么办呢?netty的这段代码:child.pipeline().addLast(childHandler)就是用了同一个handler啊,怎么才能为每一个channel创建不同的handler呢?

很简单,只要写个类继承ChannelInitializer就行了,ChannelInitializer这个类比较特殊,你可以把它想象成是很多channelhandler的集合体,而且这个类就是@Shareable的,继承了这个类之后你可以为每一个channel单独创建handler,甚至是多个handler。

解决方案也很简单,只需要在服务端传入的handler上加上@Sharable注解即可

  1. @ChannelHandler.Sharable
  2. static class MyInHandler extends ChannelInboundHandlerAdapter{
  3. ...
  4. }

但是对于每次服务端的Handler,如果都要加@Sharable,就会非常不好扩展,Netty里面提供了一个没有任何业务功能的并且标注为@Sharable的类:ChannelInitializer, 每个业务handler只需要重写其initChannel()方法即可,我们可以改造一下NettyClientSync和NettyServerSync的代码,并用Netty推荐的写法来修改。

客户端改成:

  1. import io.netty.bootstrap.Bootstrap;
  2. import io.netty.buffer.ByteBuf;
  3. import io.netty.buffer.Unpooled;
  4. import io.netty.channel.Channel;
  5. import io.netty.channel.ChannelFuture;
  6. import io.netty.channel.ChannelInitializer;
  7. import io.netty.channel.ChannelPipeline;
  8. import io.netty.channel.nio.NioEventLoopGroup;
  9. import io.netty.channel.socket.nio.NioSocketChannel;
  10. import java.net.InetSocketAddress;
  11. /**
  12. * @author <a href="mailto:410486047@qq.com">Grey</a>
  13. * @since
  14. */
  15. public class NettyClient {
  16. public static void main(String[] args) throws InterruptedException {
  17. NioEventLoopGroup group = new NioEventLoopGroup(1);
  18. Bootstrap bs = new Bootstrap();
  19. ChannelFuture fu = bs
  20. .group(group).channel(NioSocketChannel.class)
  21. .handler(new ChannelInitializer<NioSocketChannel>() {
  22. @Override
  23. protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
  24. ChannelPipeline pipeline = nioSocketChannel.pipeline();
  25. pipeline.addLast(new NettyClientSync.MyInHandler());
  26. }
  27. }).connect(new InetSocketAddress("192.168.205.138", 9090));
  28. Channel client = fu.channel();
  29. ByteBuf buf = Unpooled.copiedBuffer("Hello Server".getBytes());
  30. ChannelFuture future = client.writeAndFlush(buf);
  31. future.sync();
  32. }
  33. }

启动一个服务端,然后启动上述客户端代码,服务端可以收到信息

  1. [root@io ~]# nc -l 192.168.205.138 9090
  2. Hello Server

接下来改造服务端代码:

  1. import io.netty.bootstrap.ServerBootstrap;
  2. import io.netty.channel.ChannelFuture;
  3. import io.netty.channel.ChannelInitializer;
  4. import io.netty.channel.ChannelPipeline;
  5. import io.netty.channel.nio.NioEventLoopGroup;
  6. import io.netty.channel.socket.nio.NioServerSocketChannel;
  7. import io.netty.channel.socket.nio.NioSocketChannel;
  8. import java.net.InetSocketAddress;
  9. /**
  10. * @author <a href="mailto:410486047@qq.com">Grey</a>
  11. * @since
  12. */
  13. public class NettyServer {
  14. public static void main(String[] args) throws InterruptedException {
  15. NioEventLoopGroup group = new NioEventLoopGroup(1);
  16. ServerBootstrap bs = new ServerBootstrap();
  17. ChannelFuture bind = bs
  18. .group(group, group)
  19. .channel(NioServerSocketChannel.class)
  20. .childHandler(new ChannelInitializer<NioSocketChannel>() {
  21. @Override
  22. protected void initChannel(NioSocketChannel nioServerSocketChannel) throws Exception {
  23. ChannelPipeline pipeline = nioServerSocketChannel.pipeline();
  24. pipeline.addLast(new NettyClientSync.MyInHandler());
  25. }
  26. }).bind(new InetSocketAddress("192.168.205.1", 9090));
  27. bind.sync().channel().closeFuture().sync();
  28. }
  29. }

启动服务端代码,然后通过客户端连接服务端并发送一些数据:

  1. [root@io ~]# nc 192.168.205.1 9090
  2. sdfasdfas
  3. sdfasdfas

可以正常接收。

源码:Github

Java IO学习笔记八:Netty入门的更多相关文章

  1. Java IO学习笔记八

    BufferedReader和BufferedWriter 这两个类是高效率的提高文件的读取速度,它们为字符输入和输出提供了一个缓冲区,可以显著的调高写入和读取的速度,特别针对大量的磁盘文件读取的时候 ...

  2. Java IO学习笔记总结

    Java IO学习笔记总结 前言 前面的八篇文章详细的讲述了Java IO的操作方法,文章列表如下 基本的文件操作 字符流和字节流的操作 InputStreamReader和OutputStreamW ...

  3. Java IO学习笔记:概念与原理

    Java IO学习笔记:概念与原理   一.概念   Java中对文件的操作是以流的方式进行的.流是Java内存中的一组有序数据序列.Java将数据从源(文件.内存.键盘.网络)读入到内存 中,形成了 ...

  4. Java IO学习笔记三

    Java IO学习笔记三 在整个IO包中,实际上就是分为字节流和字符流,但是除了这两个流之外,还存在了一组字节流-字符流的转换类. OutputStreamWriter:是Writer的子类,将输出的 ...

  5. Java IO学习笔记二

    Java IO学习笔记二 流的概念 在程序中所有的数据都是以流的方式进行传输或保存的,程序需要数据的时候要使用输入流读取数据,而当程序需要将一些数据保存起来的时候,就要使用输出流完成. 程序中的输入输 ...

  6. Java IO学习笔记一

    Java IO学习笔记一 File File是文件和目录路径名的抽象表示形式,总的来说就是java创建删除文件目录的一个类库,但是作用不仅仅于此,详细见官方文档 构造函数 File(File pare ...

  7. Java IO学习笔记一:为什么带Buffer的比不带Buffer的快

    作者:Grey 原文地址:Java IO学习笔记一:为什么带Buffer的比不带Buffer的快 Java中为什么BufferedReader,BufferedWriter要比FileReader 和 ...

  8. Java IO学习笔记二:DirectByteBuffer与HeapByteBuffer

    作者:Grey 原文地址:Java IO学习笔记二:DirectByteBuffer与HeapByteBuffer ByteBuffer.allocate()与ByteBuffer.allocateD ...

  9. Java IO学习笔记三:MMAP与RandomAccessFile

    作者:Grey 原文地址:Java IO学习笔记三:MMAP与RandomAccessFile 关于RandomAccessFile 相较于前面提到的BufferedReader/Writer和Fil ...

随机推荐

  1. MySQL分区表最佳实践

    前言: 分区是一种表的设计模式,通俗地讲表分区是将一大表,根据条件分割成若干个小表.但是对于应用程序来讲,分区的表和没有分区的表是一样的.换句话来讲,分区对于应用是透明的,只是数据库对于数据的重新整理 ...

  2. [知识路书]beta设计和计划

    [知识路书]beta设计和计划 一.需求再分析 根据用户反馈,是否发现之前的需求分析有偏差?为什么会出现这种偏差?beta阶段你们是否能真的分析清楚用户需求?如何做到? 根据alpha阶段的推广情况以 ...

  3. composer 更新命令及常用命令

    composer 安装 官方地址:https://getcomposer.org/download/ 下载地址:https://getcomposer.org/Composer-Setup.exe 下 ...

  4. Jmeter(四十五) - 从入门到精通高级篇 - Jmeter之网页爬虫-上篇(详解教程)

    1.简介 上大学的时候,第一次听同学说网页爬虫,当时比较幼稚和懵懂,觉得就是几只电子虫子爬在网页上在抓取东西.后来又听说写代码可以实现网页爬虫,宏哥感觉高大上,后来工作又听说,有的公司做爬虫被抓的新闻 ...

  5. [bug] HDFS:hdfs missing blocks. The following files may be corrupted

    原因 HDFS数据块丢失,需要删除丢失块的元信息 bin/hadoop fsck / -delete 参考 https://blog.csdn.net/lixgjob/article/details/ ...

  6. 消息队列之 RabbitMQ【验证通过】

    消息队列之 RabbitMQ 预流 关注  22.9 2017.05.06 16:03* 字数 4884 阅读 284691评论 41喜欢 618赞赏 2 关于消息队列,从前年开始断断续续看了些资料, ...

  7. 在虚拟机中安装 Ubuntu

    https://www.cnblogs.com/huohu121/p/12250869.html 火狐python 博客园 首页 新随笔 联系 订阅 管理 随笔 - 54  文章 - 0  评论 - ...

  8. Mysql_二进制方式安装详解

    mysql 安装 1.安装方式 1.二进制安装 2.源码包安装 3.rpm包安装 1.二进制安装 1)上传或者下载包 [root@db01 ~]# rz #或者 [root@web01 ~]# wge ...

  9. 012.Kubernetes的configmap和secret配置

    使用configmap对多有的配置文件进行统一管理 一 configmap配置管理 1.1 检查mysql的配置 [root@docker-server1 storage]# kubectl get ...

  10. crontab 的简要介绍

    1.概述: crontab 用于周期性被执行的指令,该指令从标准设备输入指令,并将指令存放在crontab文件中,供之后读取和执行. 与crontab相关的文件一共有三个: /etc/crontab ...