我们会通过Netty实现一个Echo服务器和客户端,客户端向服务器发送数据,服务器原样返回。
下图展示了这个Echo服务器的样子。

开始编写Echo Server:
所有的Netty 服务器都需要以下2个部分:

  • 至少一个ChannelHandler - 实现了服务器处理接收到的数据
  • Bootstrap - 这是能配置服务器的启动代码

ChannelHandler和业务逻辑
因为Echo服务器会对输入的信息提供响应,它需要实现接口ChannelInboundHandler,这个接口为输入数据事件(inbound events)定义了处理函数。我们这个服务器只需要ChannelInboundHandler接口里面的一小部分方法,因此,只要继承ChannelInboundHandlerAdapter就够了,它提供了ChannelInboundHandler的一个默认实现。

我们感兴趣的是如下方法:

  • channelRead() - 只要有数据来就会触发
  • channelReadComplete() - 通知handler,对channelRead()的最后一个调用是当前batch中的最后一个消息
  • exceptionCaught() - 在读操作期间抛出了异常就会触发

下面是Echo服务器的ChannelHandler实现类:

/*** @Sharable 表明这个ChannelHandler能被安全的被多个channels共享*/
@Sharable
public class EchoServerHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {ByteBuf in = (ByteBuf) msg;System.out.println("Server received: " + in.toString(CharsetUtil.UTF_8));ctx.write(in);//将收到的数据写入ByteBuf中(后面会发送给发送者)而没有flush}@Overridepublic void channelReadComplete(ChannelHandlerContext ctx) throws Exception {ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).//flush buffer中的消息到远程节点然后关闭channeladdListener(ChannelFutureListener.CLOSE);}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {cause.printStackTrace();ctx.close();//closes the channel}
}

如果一个异常没有被捕获会发生什么?每一个Channel都有一个与之相关的ChannelPipeline,ChannelPipeline持有一连串的ChannelHandler实例。一个hanlder会把handler方法的调用传递给(handler链中的)下一个handler,因此如果exceptionCaught()没有在链中的某个地方实现,接收到的异常就会传送到ChannelPipeline的末端,然后会被记录(log)。因此,你的应用应该提供至少一个实现了exceptionCaught()的ChannelHandler。

记住下面几点:

  • ChannelHandlers能被不同类型的事件调用
  • 应用程序实现或继承ChannelHandlers来介入事件的生命周期同时提供自定义的应用业务逻辑
  • ChannelHandlers让你的业务逻辑代码与网络编程代码解耦

启动(Bootstrapping)服务器
启动服务器会涉及到以下两点:

  • 绑定服务器监听的端口然后接受(accept)连接请求
  • 配置channel来通知一个EchoServerHandler实例输入的信息

下面给出EchoServer类的实现:

package com.netty.ch2;import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;import java.net.InetSocketAddress;public class EchoServer {private final int port;public EchoServer(int port){this.port = port;}public static void main(String[] args) throws Exception {if(args.length != 1){args = new String[]{"8000"};}int port = Integer.parseInt(args[0]);new EchoServer(port).start();}public void start() throws Exception{final EchoServerHandler serverHandler = new EchoServerHandler();EventLoopGroup group = new NioEventLoopGroup();try{ServerBootstrap b = new ServerBootstrap();b.group(group).channel(NioServerSocketChannel.class)//表示使用NIO transport Channel.localAddress(new InetSocketAddress(port))//绑定端口号//当一个新连接进来,一个新的child channel会被创建//ChannelInitializer会将EchoServerHandler的一个实例增加到pipeline.childHandler(new ChannelInitializer<SocketChannel>() {//增加一个EchoServerHandler到Channel的pipelineprotected void initChannel(SocketChannel ch) throws Exception {//EchoServerHandler是@Sharable的,因此我们可以一直使用同一个ch.pipeline().addLast(serverHandler);}});ChannelFuture f = b.bind().sync();//异步地绑定服务器,调用sync()将异步变成同步,同步地等待绑定完成f.channel().closeFuture().sync();//得到channel的closeFuture同时阻塞当前的线程直到closeFuture完成}finally {//关闭EventLoopGroup,释放所有的资源group.shutdownGracefully().sync();}}
}

回顾一下实现服务器的重要步骤:

  • EchoServerHandler实现了业务逻辑
  • main()方法启动了服务器

启动服务器的步骤是:

  • 创建一个ServerBootstrap实例来启动和绑定服务器
  • 创建和分配一个NioEventLoopGroup实例来处理各种事件(如:新连接进入和读写数据等)
  • 指定服务器绑定的本地InetSocketAddress
  • 用EchoServerHandler实例来实例化每一个新的Channel
  • 调用ServerBootstrap.bind()方法来绑定服务器

到此为止服务器已经被初始化了然后可以开始提供服务了。下面我们来实现客户端的代码

编写Echo客户端代码

Echo客户端会:

  • 连接服务器
  • 发送消息
  • 发送的每一条消息,等待相应的回复
  • 关闭连接
    客户端也需要实现业务逻辑和启动(bootstrapping)代码

通过ChannelHandlers实现客户端业务逻辑
客户端通过ChannelInboundHandler来处理数据。可以通过继承SimpleChannelInboundHandler来实现下面需要的方法:

  • channelActive() - 当与服务端的连接建立成功后被调用
  • channelRead0() - 收到来自服务器的消息时调用
  • exceptionCaught() - 如果处理的过程中抛出异常被调用
package com.netty.ch2;import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;@ChannelHandler.Sharable
public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf>{@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {//当知悉channel被激活后 发送一条信息ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks!", CharsetUtil.UTF_8));}protected void channelRead0(ChannelHandlerContext ctx, ByteBuf in) throws Exception {System.out.println("Client received: " + in.toString(CharsetUtil.UTF_8));}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {cause.printStackTrace();ctx.close();}
}

首先,重写了channelActive(),当连接建立后会调用这个方法。
接下来重写了channelRead0()方法,当收到的数据后会回调这个方法。注意可能以块(chunks)的形式接收服务器发送来的数据。如果服务器发送了5字节的数据,但是无法保证所有的5个字节会被一次接收。甚至这么小的数据(才5字节)都可能导致这个方法被调用两次,第一次一个ByteBuf(Netty的字节容器)装载了3字节的数据,第二次装载了2字节的数据。因为面向流的协议,比如TCP,能保证这些字节以它们被(服务器)发送的顺序接收。

SimpleChannelInboundHandler vs. ChannelInboundHandler
在客户端(客户端继承SimpleChannelInboundHandler),当channelRead0()方法完成时,你已经取得了要接收的消息,当这个方法返回时,SimpleChannelInboundHandler会释放持有消息的ByteBuf所引用的相关内存;而在服务端,你仍然需要通过异步的write()方法将受到的消息返回给发送者,这个write()方法在channelRead()方法返回前可能还没有将消息写完,此时不应该释放这个消息所占的资源。

启动客户端

package com.netty.ch2;import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;import java.net.InetSocketAddress;/*** Created by greyfoss on 2017/9/1.*/
public class EchoClient {private final String host;private final int port;public EchoClient(String host, int port) {this.host = host;this.port = port;}public void start() throws InterruptedException {EventLoopGroup group = new NioEventLoopGroup();try{Bootstrap b = new Bootstrap();b.group(group)//指定EventLoopGroup来处理客户端消息.channel(NioSocketChannel.class).remoteAddress(new InetSocketAddress(host,port))//当Channel创建时将EchoClientHandler增加到pipeline.handler(new ChannelInitializer<SocketChannel>() {protected void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast(new EchoClientHandler());}});ChannelFuture f = b.connect().sync();f.channel().closeFuture().sync();}finally {group.shutdownGracefully().sync();}}public static void main(String[] args) throws InterruptedException {if(args.length != 2){args = new String []{"localhost","8000"};}String host = args[0];int port = Integer.parseInt(args[1]);new EchoClient(host,port).start();}
}

像服务端一样,使用NIO transport。
回顾一下创建客户端的要点:

  • 创建一个Bootstrap实例来初始化客户端
  • 分配NioEventLoopGroup实例来处理事件(包括创建新连接,处理读写数据)
  • 为connnection创建一个InetSocketAddress实例来连接服务器
  • 当连接建立好后安装一个EchoClientHandler到pipeline
  • 接下来调用Bootstrap.connect()来连接到远程的服务器

Netty in action—第一个Netty应用相关推荐

  1. Netty介绍 与第一个Netty实例

    官网:https://netty.io/ 以下内容大部分来自Netty官网内容 一.现有问题 现在我们使用通用应用程序或库来彼此通信.例如,我们经常使用HTTP客户端库从web服务器检索信息,并通过w ...

  2. Netty In Action中文版

    第一章:Netty介绍 本章介绍 Netty介绍 为什么要使用non-blocking IO(NIO) 阻塞IO(blocking IO)和非阻塞IO(non-blocking IO)对比 Java ...

  3. 《Netty In Action》第二章:第一个Netty程序

    2019独角兽企业重金招聘Python工程师标准>>> 源码地址: GitHub 2.3 编写一个应答服务器 写一个Netty服务器主要由两部分组成: 配置服务器功能,如线程.端口 ...

  4. Netty In Action中文版 - 第十二章:SPDY

    Netty In Action中文版 - 第十二章:SPDY 本章我将不会直接翻译Netty In Action书中的原文,感觉原书中本章讲的很多废话,我翻译起来也吃力.所以,本章内容我会根据其他资料 ...

  5. 《Netty IN ACTION》中文版《Netty实战》翻译手记——不负好时光

    不负好时光--<Netty in Action>中文版<Netty实战>翻译手记 引子 "书中自有黄金屋,书中自有颜如玉",这句话从小我老爸就给我讲,当然那 ...

  6. 《Netty 实战》Netty In Action中文版 第2章——你的第一款Netty应用程序(一)

    第2章 你的第一款Netty应用程序 本章主要内容 设置开发环境 编写Echo服务器和客户端 构建并测试应用程序 在本章中,我们将展示如何构建一个基于Netty的客户端和服务器.应用程序很简单:客户端 ...

  7. netty 远程主机强迫关闭了一个现有的连接_死磕netty系列《一、netty基础概念》...

    1. Channel Channel代表了netty对网络连接的抽象,Channel是线程安全的,它提供了一些重要信息,比如当前网络连接的状态,远程的主机连接地址和本地的连接地址, 我们可以通过 Ch ...

  8. 第一个Netty应用

    本文来做一个Netty的入门程序,即Netty的hello word入门级程序 文章目录 概述 导入Netty依赖 服务端 创建服务端 自定义服务端 ChannelHandler 处理消息 客户端 创 ...

  9. Netty in Action (十九) 第九章节 单元测试

    本章内容包括: 1)单元测试 2)EmbeddedChannel的说明 3)使用EmbeddedChannel测试ChannelHandler 对于一个Netty应用来说,ChannelHandler ...

  10. 1、张龙netty学习 第一个netty服务端

    张龙netty学习 第一个netty服务端 public class TestServer {public static void main(String[] args) throws Excepti ...

最新文章

  1. Nginx多站点设置
  2. org.apache.tomcat.util.bcel.classfile.ClassFormatException: Invalid byte tag in constant pool: 60
  3. CodeForces 679B Bear and Tower of Cubes
  4. python list()函数 (从可迭代对象返回初始化的新列表)
  5. python 点滴记录1:python没有Tkinter模块
  6. SpringBoot项目打war包部署Tomcat教程
  7. 手机局域网html,手机遥控电脑开机神器!局域网唤醒App
  8. 学php记不住函数,为什么都是记不住PHP 的函数名
  9. 山东自考c语言程序设计停考了吗,山东自考教育类停考专业遗留问题的通知
  10. mac磨皮插件_Adobe Pr 黑科技插件合集,一键安装,Mac+Win
  11. matlab2c使用c++实现matlab函数系列教程-atan函数
  12. easyMule如何导入未完成任务
  13. Excel数据分析—折线图
  14. 阿里优酷视频分类方法???咋理解啊?
  15. [jruby] warbler 配置问题说明
  16. 一线明星纷纷失业,数据告诉你今年的演员有多难
  17. win7没有权限工作组计算机,win7没有权限访问工作组计算机的解决方法
  18. EXTJS动态载入组件定义文件并初始化组件,动态构造界面的方法.
  19. [英语阅读]2010年内衣新宠:抗皱文胸
  20. 6. 大尾数法或小尾数法

热门文章

  1. 数据之路 - Python爬虫 - BeautifulSoup库
  2. 《实用机器学习》中的一些拓展问题
  3. Azure database
  4. shell脚本--部署应用到tomcat并启动tomcat
  5. 刚注册博客了,发发随笔
  6. 使用JSON数据报错和方法
  7. jquery 初步(四)内容过滤器
  8. 【转】BW的星型数据模型
  9. SAAS及PAAS的概念
  10. CMake中include指令介绍