<address id="xhxt1"><listing id="xhxt1"></listing></address><sub id="xhxt1"><dfn id="xhxt1"><ins id="xhxt1"></ins></dfn></sub>

    <thead id="xhxt1"><dfn id="xhxt1"><ins id="xhxt1"></ins></dfn></thead>

    《Netty 权威指南》—— AIO 创建的TimeServer源码分析

    声明:本文是《Netty 权威指南》的样章,感谢博文视点授权并发编程网站发布样章,禁止以任何形式转载此文。

    NIO2.0引入了新的异步通道的概念,并提供了异步文件通道和异步套接字通道的实现。异步通道提供两种方式获取获取操作结果:

    • 通过java.util.concurrent.Future类来表示异步操作的结果;
    • 在执行异步操作的时候传入一个java.nio.channels.

    CompletionHandler接口的实现类作为操作完成的回调。

    NIO2.0的异步套接字通道是真正的异步非阻塞IO,它对应Unix网络编程中的事件驱动IO(AIO),它不需要通过多路复用器(Selector)对注册的通道进行轮询操作即可实现异步读写,简化了NIO的编程模型。

    下面还是通过代码来熟悉NIO2.0 AIO的相关类库,我们仍旧以时间服务器为例程进行讲解。

    AIO 创建的TimeServer源码分析

    首先看下时间服务器的主函数:

    public class TimeServer {
    
    /**
    * @param args
    * @throws IOException
    */
    public static void main(String[] args) throws IOException {
    int port = 8080;
    if (args != null && args.length > 0) {
    try {
    port = Integer.valueOf(args[0]);
    } catch (NumberFormatException e) {
    // 采用默认值
    }
    }
    AsyncTimeServerHandler timeServer = new AsyncTimeServerHandler(port);
    new Thread(timeServer, "AIO-AsyncTimeServerHandler-001").start();
    }
    }
    

    我们直接从第16行开始看,首先创建异步的时间服务器处理类,然后启动线程将AsyncTimeServerHandler拉起,代码如下:

    public class AsyncTimeServerHandler implements Runnable {
    
        private int port;
    
        CountDownLatch latch;
        AsynchronousServerSocketChannel asynchronousServerSocketChannel;
    
        public AsyncTimeServerHandler(int port) {
    	this.port = port;
    	try {
    	    asynchronousServerSocketChannel = AsynchronousServerSocketChannel
    		    .open();
    	    asynchronousServerSocketChannel.bind(new InetSocketAddress(port));
    	    System.out.println("The time server is start in port : " + port);
    	} catch (IOException e) {
    	    e.printStackTrace();
    	}
        }
    
        /*
         * (non-Javadoc)
         * 
         * @see java.lang.Runnable#run()
         */
        @Override
        public void run() {
    
    	latch = new CountDownLatch(1);
    	doAccept();
    	try {
    	    latch.await();
    	} catch (InterruptedException e) {
    	    e.printStackTrace();
    	}
        }
    
        public void doAccept() {
    	asynchronousServerSocketChannel.accept(this,
    		new AcceptCompletionHandler());
        }
    
    

    我们重点对AsyncTimeServerHandler进行分析,首先看8-15行,在构造方法中,我们首先创建一个异步的服务端通道AsynchronousServerSocketChannel,然后调用它的bind方法绑定监听端口,如果端口合法且没被占用,绑定成功,打印启动成功提示到控制台。
    在线程的run方法中,第26行我们初始化CountDownLatch对象,它的作用是在完成一组正在执行的操作之前,允许当前的线程一直阻塞。在本例程中,我们让线程在此阻塞,防止服务端执行完成退出。在实际项目应用中,不需要启动独立的线程来处理AsynchronousServerSocketChannel,这里仅仅是个demo演示。
    第24行用于接收客户端的连接,由于是异步操作,我们可以传递一个
    CompletionHandler<AsynchronousSocketChannel,? super A>类型的handler实例接收accept操作成功的通知消息,在本例程中我们通过AcceptCompletionHandler实例作为handler接收通知消息,下面,我们继续对AcceptCompletionHandler进行分析:

    public class AcceptCompletionHandler implements
    	CompletionHandler<AsynchronousSocketChannel, AsyncTimeServerHandler> {
    
        @Override
        public void completed(AsynchronousSocketChannel result,
    	    AsyncTimeServerHandler attachment) {
    	attachment.asynchronousServerSocketChannel.accept(attachment, this);
    	ByteBuffer buffer = ByteBuffer.allocate(1024);
    	result.read(buffer, buffer, new ReadCompletionHandler(result));
        }
    
        @Override
        public void failed(Throwable exc, AsyncTimeServerHandler attachment) {
    	exc.printStackTrace();
    	attachment.latch.countDown();
        }
    }
    
    

    CompletionHandler有两个方法,分别是:

    1) ?public void completed(AsynchronousSocketChannel result,

    AsyncTimeServerHandler attachment);

    2) public void failed(Throwable exc, AsyncTimeServerHandler attachment);

    下面我们分别对这两个接口的实现进行分析:首先看completed接口的实现,代码7-10行,我们从attachment获取成员变量AsynchronousServerSocketChannel,然后继续调用它的accept方法??赡芏琳咴诖丝赡芑嵝拇嬉苫?,既然已经接收客户端成功了,为什么还要再次调用accept方法呢?原因是这样的:当我们调用AsynchronousServerSocketChannel的accept方法后,如果有新的客户端连接接入,系统将回调我们传入的CompletionHandler实例的completed方法,表示新的客户端已经接入成功,因为一个AsynchronousServerSocketChannel可以接收成千上万个客户端,所以我们需要继续调用它的accept方法,接收其它的客户端连接,最终形成一个循环。每当接收一个客户读连接成功之后,再异步接收新的客户端连接。
    链路建立成功之后,服务端需要接收客户端的请求消息,代码第8行我们创建新的ByteBuffer,预分配1M的缓冲区。第8行我们通过调用AsynchronousSocketChannel的read方法进行异步读操作。下面我们看看异步read方法的参数:
    1) ? ByteBuffer dst:接收缓冲区,用于从异步Channel中读取数据包;

    2) ?A attachment:异步Channel携带的附件,通知回调的时候作为入参使用;

    3) ?CompletionHandler<Integer,? super A>:接收通知回调的业务handler,本例程中为ReadCompletionHandler。

    下面我们继续对ReadCompletionHandler进行分析:

    public class ReadCompletionHandler implements
    	CompletionHandler<Integer, ByteBuffer> {
    
        private AsynchronousSocketChannel channel;
    
        public ReadCompletionHandler(AsynchronousSocketChannel channel) {
    	if (this.channel == null)
    	    this.channel = channel;
        }
    
        @Override
        public void completed(Integer result, ByteBuffer attachment) {
    	attachment.flip();
    	byte[] body = new byte[attachment.remaining()];
    	attachment.get(body);
    	try {
    	    String req = new String(body, "UTF-8");
    	    System.out.println("The time server receive order : " + req);
    	    String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(req) ? new java.util.Date(
    		    System.currentTimeMillis()).toString() : "BAD ORDER";
    	    doWrite(currentTime);
    	} catch (UnsupportedEncodingException e) {
    	    e.printStackTrace();
    	}
        }
    
        private void doWrite(String currentTime) {
    	if (currentTime != null && currentTime.trim().length() > 0) {
    	    byte[] bytes = (currentTime).getBytes();
    	    ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
    	    writeBuffer.put(bytes);
    	    writeBuffer.flip();
    	    channel.write(writeBuffer, writeBuffer,
    		    new CompletionHandler<Integer, ByteBuffer>() {
    			@Override
    			public void completed(Integer result, ByteBuffer buffer) {
    			    // 如果没有发送完成,继续发送
    			    if (buffer.hasRemaining())
    				channel.write(buffer, buffer, this);
    			}
    
    			@Override
    			public void failed(Throwable exc, ByteBuffer attachment) {
    			    try {
    				channel.close();
    			    } catch (IOException e) {
    				// ingnore on close
    			    }
    			}
    		    });
    	}
        }
    
        @Override
        public void failed(Throwable exc, ByteBuffer attachment) {
    	try {
    	    this.channel.close();
    	} catch (IOException e) {
    	    e.printStackTrace();
    	}
        }
    }
    

    首先看构造方法,我们将AsynchronousSocketChannel通过参数传递到ReadCompletionHandler中当作成员变量来使用,主要用于读取半包消息和发送应答。本例程不对半包读写进行具体解说,对此感兴趣的可以关注后续章节对Netty半包处理的专题介绍。我们继续看代码,第12-25行是读取到消息后的处理,首先对attachment进行flip操作,为后续从缓冲区读取数据做准备。根据缓冲区的可读字节数创建byte数组,然后通过new String方法创建请求消息,对请求消息进行判断,如果是”QUERY TIME ORDER”则获取当前系统服务器的时间,调用doWrite方法发送给客户端。下面我们对doWrite方法进行详细分析。

    跳到代码第28行,首先对当前时间进行合法性校验,如果合法,调用字符串的解码方法将应答消息编码成字节数组,然后将它拷贝到发送缓冲区writeBuffer中,最后调用AsynchronousSocketChannel的异步write方法。正如前面介绍的异步read方法一样,它也有三个与read方法相同的参数,在本例程中我们直接实现write方法的异步回调接口CompletionHandler,代码跳到第24行,对发送的writeBuffer进行判断,如果还有剩余的字节可写,说明没有发送完成,需要继续发送,直到发送成功。

    最后,我们关注下failed方法,它的实现很简单,就是当发生异常的时候,我们对异常Throwable进行判断,如果是IO异常,就关闭链路,释放资源,如果是其它异常,按照业务自己的逻辑进行处理。本例程作为简单demo,没有对异常进行分类判断,只要发生了读写异常,就关闭链路,释放资源。

    异步非阻塞IO版本的时间服务器服务端已经介绍完毕,下面我们继续看客户端的实现。


    FavoriteLoading添加本文到我的收藏
    • Trackback 关闭
    • 评论 (3)
      • Paddy
      • 2014/10/30 10:15上午

      可能读者在此可能会心存疑惑,既然已经接收客户端成功了,为什么还要再次调用accept方法呢?原因是这样的:当我们调用AsynchronousServerSocketChannel的accept方法后,如果有新的客户端连接接入,系统将回调我们传入的CompletionHandler实例的completed方法,表示新的客户端已经接入成功,因为一个AsynchronousServerSocketChannel可以接收成千上万个客户端,所以我们需要继续调用它的accept方法,接收其它的客户端连接,最终形成一个循环。每当接收一个客户读连接成功之后,再异步接收新的客户端连接。

      这段话你只说明了果,却并未说明因。果是每次Handler处理一开始必须再调用一次accept来接受下一次客户端连接。因到底是什么,是accept方法一次调用只能接受一个连接请求吗?如果它是作为handler注册方法的话,我已经指定accept的时候用我的Handler,按道理说,以后你接受请求,直接调用我这个AcceptHandler好了嘛。所以我猜accept方法只能接受一个客户端连接,如果不自己再次调用的话,它就不接受新的连接请求了。如果是像一般的Listener,注册一次就可以了。

      不知是否正确

      • wxglovebfw
      • 2017/01/02 3:16下午

      我也觉得第二次的accept设计不合理,这就回到了WIN95的年代,需要写程序的人正确合作,否则就只能接受一个客户端的连接。如果让我设计,我个人的思路是这样的:accept应该是写在系统框架中的,与开发人员是隔离的,框架中accept后,自动调用开发者注册的handle(用户代码),开发者只要写个handle并注明这个handler是干什么用的即可。服务端不停地accept应该是框架的责任,因为开发者不是了解你这个系统的全局情况的。

      • sunsong
      • 2017/08/07 8:07下午

      我们让线程在此阻塞,防止服务端执行完成退出。在实际项目应用中,不需要启动独立的线程来处理,为什么说不需要启动独立的进程,请问在实际项目中 如何 使用?

    您必须 登陆 后才能发表评论

    return top

    爱投彩票 3dn| bl3| pxz| pnx| t2x| xnl| 2xd| pv2| xnx| t2d| lrj| 2fh| hb2| fdx| n3p| bhz| hnz| 1zb| tz1| hfp| b1t| brt| 1xx| nld| 2vx| ft2| nrl| x2j| tzx| zfz| 0fh| vb0| fbv| r1j| jfj| 1pz| vr1| flv| p1b| flf| 9xh| zn0| pn0| pdx| x0n| zxr| 0df| th0| lzt| n0x| fvn| 1hb| rxh| 9hr| vb9| zp9| ljd| x9n| rvx| 9rb|