博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
《Netty In Action》第二章:第一个Netty程序
阅读量:6839 次
发布时间:2019-06-26

本文共 9445 字,大约阅读时间需要 31 分钟。

hot3.png

源码地址: 

2.3 编写一个应答服务器

写一个Netty服务器主要由两部分组成:

  • 配置服务器功能,如线程、端口
  • 实现服务器处理程序,它包含业务逻辑,决定当有一个请求连接或接收数据时该做什么

2.3.1 启动服务器

通过创建ServerBootstrap对象来启动服务器,然后配置这个对象的相关选项,如端口、线程模式、事件循环,并且添加逻辑处理程序用来处理

业务逻辑(下面是个简单的应答服务器例子)
 

package nia.chapter2.echoserver;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;/** * 代码清单 2-2 EchoServer 类 * * @author Norman Maurer */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) {            System.err.println("Usage: " + EchoServer.class.getSimpleName() +                " 
" ); return; } //设置端口值(如果端口参数的格式不正确,则抛出一个NumberFormatException) int port = Integer.parseInt(args[0]); //调用服务器的 start()方法 new EchoServer(port).start(); } public void start() throws Exception { final EchoServerHandler serverHandler = new EchoServerHandler(); //(1) 创建EventLoopGroup EventLoopGroup group = new NioEventLoopGroup(); try { //(2) 创建ServerBootstrap ServerBootstrap b = new ServerBootstrap(); b.group(group) //(3) 指定所使用的 NIO 传输 Channel .channel(NioServerSocketChannel.class) //(4) 使用指定的端口设置套接字地址 .localAddress(new InetSocketAddress(port)) //(5) 添加一个EchoServerHandler到于Channel的 ChannelPipeline .childHandler(new ChannelInitializer
() { @Override public void initChannel(SocketChannel ch) throws Exception { //EchoServerHandler 被标注为@Shareable,所以我们可以总是使用同样的实例 //这里对于所有的客户端连接来说,都会使用同一个 EchoServerHandler,因为其被标注为@Sharable, //这将在后面的章节中讲到。 ch.pipeline().addLast(serverHandler); } }); //(6) 异步地绑定服务器;调用 sync()方法阻塞等待直到绑定完成 ChannelFuture f = b.bind().sync(); System.out.println(EchoServer.class.getName() + " started and listening for connections on " + f.channel().localAddress()); //(7) 获取 Channel 的CloseFuture,并且阻塞当前线程直到它完成 f.channel().closeFuture().sync(); } finally { //(8) 关闭 EventLoopGroup,释放所有的资源 group.shutdownGracefully().sync(); } }}

 从上面这个简单的服务器例子可以看出,一、启动服务器应先创建一个ServerBootstrap对象二、因为使用NIO,所以指定NioEventLoopGroup来接受和处理新连接,指定通道类型为NioServerSocketChannel三、设置InetSocketAddress让服务器监听某个端口已等待客户端连接。

接下来,四、调用childHandler放来指定连接后调用的ChannelHandler,这个方法传ChannelInitializer类型的参数,ChannelInitializer是个抽象类,所以需要实现initChannel方法,这个方法就是用来设置ChannelHandler。
五、最后绑定服务器等待直到绑定完成,调用sync()方法会阻塞直到服务器完成绑定,然后服务器等待通道关闭,因为使用sync(),所以关闭操作也会被阻塞。六、现在你可以关闭EventLoopGroup和释放所有资源,包括创建的线程。
这个例子中使用NIO,因为它是目前最常用的传输方式,你可能会使用NIO很长时间,但是你可以选择不同的传输实现。例如,这个例子使用OIO方式传输,你需要指定OioServerSocketChannel。Netty框架中实现了多重传输方式,将再后面讲述。

本小节重点内容:

  • 创建ServerBootstrap实例来引导绑定和启动服务器
  • 创建NioEventLoopGroup对象来处理事件,如接受新连接、接收数据、写数据等等
  • 指定InetSocketAddress,服务器监听此端口
  • 设置childHandler执行所有的连接请求
  • 都设置完毕了,最后调用ServerBootstrap.bind() 方法来绑定服务器

2.3.2 实现业务器业务逻辑

Netty使用futures和回调概念,它的设计允许你处理不同的事件类型,更详细的介绍将再后面章节讲述,但是我们可以接收数据。你的channelhandler必须继承ChannelInboundHandlerAdapter并且重写channelRead方法,这个方法在任何时候都会被调用来接收数据,在这个例子中接收的是字节。

下面是handler的实现,其实现的功能是将客户端发给服务器的数据返回给客户端:

package nia.chapter2.echoserver;import io.netty.buffer.ByteBuf;import io.netty.buffer.Unpooled;import io.netty.channel.ChannelFutureListener;import io.netty.channel.ChannelHandler.Sharable;import io.netty.channel.ChannelHandlerContext;import io.netty.channel.ChannelInboundHandlerAdapter;import io.netty.util.CharsetUtil;/** * 代码清单 2-1 EchoServerHandler * * @author Norman Maurer *///标示一个ChannelHandler可以被多个 Channel 安全地共享@Sharablepublic class EchoServerHandler extends ChannelInboundHandlerAdapter {    @Override    public void channelRead(ChannelHandlerContext ctx, Object msg) {        ByteBuf in = (ByteBuf) msg;        //将消息记录到控制台        System.out.println(                "Server received: " + in.toString(CharsetUtil.UTF_8));        //将接收到的消息写给发送者,而不冲刷出站消息        ctx.write(in);    }    @Override    public void channelReadComplete(ChannelHandlerContext ctx)            throws Exception {        //将未决消息冲刷到远程节点,并且关闭该 Channel        ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)                .addListener(ChannelFutureListener.CLOSE);    }    @Override    public void exceptionCaught(ChannelHandlerContext ctx,        Throwable cause) {        //打印异常栈跟踪        cause.printStackTrace();        //关闭该Channel        ctx.close();    }}

Netty使用多个Channel Handler来达到对事件处理的分离,因为可以很容的添加、更新、删除业务逻辑处理handler。Handler很简单,它的每个方法都可以被重写,它的所有的方法中只有channelRead方法是必须要重写的

2.3.3 捕获异常

重写ChannelHandler的exceptionCaught方法可以捕获服务器的异常,比如客户端连接服务器后强制关闭,服务器会抛出"客户端主机强制关闭错误",通过重写exceptionCaught方法就可以处理异常,比如发生异常后关闭ChannelHandlerContext。

2.4 编写应答程序的客户端

服务器写好了,现在来写一个客户端连接服务器。应答程序的客户端包括以下几步:

  • 连接服务器
  • 写数据到服务器
  • 等待接受服务器返回相同的数据
  • 关闭连接

2.4.1 引导客户端

引导客户端启动和引导服务器很类似,客户端需同时指定host和port来告诉客户端连接哪个服务器。看下面代码:

package nia.chapter2.echoclient;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;/** * 代码清单 2-4 客户端的主类 * * @author Norman Maurer */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 Exception {        EventLoopGroup group = new NioEventLoopGroup();        try {            //创建 Bootstrap            Bootstrap b = new Bootstrap();            //指定 EventLoopGroup 以处理客户端事件;需要适用于 NIO 的实现            b.group(group)                //适用于 NIO 传输的Channel 类型                .channel(NioSocketChannel.class)                //设置服务器的InetSocketAddress                .remoteAddress(new InetSocketAddress(host, port))                //在创建Channel时,向 ChannelPipeline中添加一个 EchoClientHandler实例                .handler(new ChannelInitializer
() { @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast( new EchoClientHandler()); } }); //连接到远程节点,阻塞等待直到连接完成 ChannelFuture f = b.connect().sync(); //阻塞,直到Channel 关闭 f.channel().closeFuture().sync(); } finally { //关闭线程池并且释放所有的资源 group.shutdownGracefully().sync(); } } public static void main(String[] args) throws Exception { if (args.length != 2) { System.err.println("Usage: " + EchoClient.class.getSimpleName() + "
" ); return; } final String host = args[0]; final int port = Integer.parseInt(args[1]); new EchoClient(host, port).start(); }}

创建启动一个客户端包含下面几步:

  • 创建Bootstrap对象用来引导启动客户端
  • 创建EventLoopGroup对象并设置到Bootstrap中,EventLoopGroup可以理解为是一个线程池,这个线程池用来处理连接、接受数据、发送数据
  • 创建InetSocketAddress并设置到Bootstrap中,InetSocketAddress是指定连接的服务器地址
  • 添加一个ChannelHandler,客户端成功连接服务器后就会被执行
  • 调用Bootstrap.connect()来连接服务器
  • 最后关闭EventLoopGroup来释放资源

2.4.2 实现客户端的业务逻辑

客户端的业务逻辑的实现依然很简单,更复杂的用法将在后面章节详细介绍。和编写服务器的ChannelHandler一样,在这里将自定义一个继承SimpleChannelInboundHandler的ChannelHandler来处理业务;通过重写父类的三个方法来处理感兴趣的事件:

  • channelActive():客户端连接服务器后被调用
  • channelRead0():从服务器接收到数据后调用
  • exceptionCaught():发生异常时被调用

实现代码如下

package nia.chapter2.echoclient;import io.netty.buffer.ByteBuf;import io.netty.buffer.Unpooled;import io.netty.channel.ChannelHandler.Sharable;import io.netty.channel.ChannelHandlerContext;import io.netty.channel.SimpleChannelInboundHandler;import io.netty.util.CharsetUtil;/** * 代码清单 2-3 客户端的 ChannelHandler * * @author Norman Maurer */@Sharable//标记该类的实例可以被多个 Channel 共享public class EchoClientHandler    extends SimpleChannelInboundHandler
{ @Override public void channelActive(ChannelHandlerContext ctx) { //当被通知 Channel是活跃的时候,发送一条消息 ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks!", CharsetUtil.UTF_8)); } @Override public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) { //记录已接收消息的转储 System.out.println( "Client received: " + in.toString(CharsetUtil.UTF_8)); } @Override //在发生异常时,记录错误并关闭Channel public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { cause.printStackTrace(); ctx.close(); }}

可能你会问为什么在这里使用的是SimpleChannelInboundHandler而不使用ChannelInboundHandlerAdapter?主要原因是ChannelInboundHandlerAdapter在处理完消息后需要负责释放资源。在这里将调用ByteBuf.release()来释放资源。SimpleChannelInboundHandler会在完成channelRead0后释放消息,这是通过Netty处理所有消息的ChannelHandler实现了ReferenceCounted接口达到的。

为什么在服务器中不使用SimpleChannelInboundHandler呢?因为服务器要返回相同的消息给客户端,在服务器执行完成写操作之前不能释放调用读取到的消息,因为写操作是异步的一旦写操作完成后,Netty中会自动释放消息

 

 

 

 

 

 

转载于:https://my.oschina.net/LucasZhu/blog/1616666

你可能感兴趣的文章
每日英语:In the Future, Who Will Need Teachers?
查看>>
Android UI学习 - TableLayout
查看>>
Revit平面视图控制
查看>>
mysql 开启 登陆 停止
查看>>
java学习笔记------ PrintStream
查看>>
5、条件、循环和其他语句
查看>>
asp 文件上传(ASPUpload组件上传)
查看>>
MVC - 18.缓存
查看>>
Loadrunner11之禁用/启用Action
查看>>
Largest Rectangular Area in a Histogram
查看>>
.NET设计模式(14):代理模式(Proxy Pattern)(转)
查看>>
信息安全书单之逆向工程(未完成)
查看>>
电脑环境变量里面的参数
查看>>
easyui easyui-filebox 显示中文
查看>>
FG面经Prepare: BST to Double LinkedList
查看>>
360开源的pika
查看>>
POJ 2503-Babelfish(map)
查看>>
提高sqlmap爆破效率
查看>>
HDOJ 5288 OO’s Sequence 水
查看>>
centos 扩容
查看>>