JAVA AIO 服务器与客户端实现示例(代码2)

AIO用于文件处理还是比较快乐的,但用AIO来写网络消息处理服务器端与客户端是比较麻烦的事情,当然这只是我个人意见,主要是有几点原因:

一是AIO需要操作系统支持,还好Windows与Linux(模拟)都支持;

二是AIO同时使用递归调用和异步调用容易把程序员搞晕,代码容易出错;

三是CompletionHandler会使用单独的线程跑,容易出现多线程问题,频繁线程上下文切换比较消耗资源;

四是异步写要创建队列来缓存需要写入的数据,否则肯定会遇到WritePendingException。


相对来说,NIO比较清楚直白,容易控制。


另外,笔者使用多线程模拟多个客户场景失败,代码在run方法中调用AsynchronousSocketChannel.connect()没返回,没连接上服务器,不知道为何,请大侠指教,最后只好使用多个进程模拟多个客户端,写个类似下面代码的bat文件,同时运行多个。

1
2
3
4
5
java -classpath .\ com.stevex.app.aio.Client 1
 
java -classpath .\ com.stevex.app.aio.Client 1
 
pause
服务器代码:

  1. package aio;  
  2.   
  3. /** 
  4.  * Created by Administrator on 2016/10/28. 
  5.  */  
  6. import java.io.IOException;  
  7. import java.net.InetSocketAddress;  
  8. import java.net.StandardSocketOptions;  
  9. import java.nio.ByteBuffer;  
  10. import java.nio.CharBuffer;  
  11. import java.nio.channels.AsynchronousChannelGroup;  
  12. import java.nio.channels.AsynchronousServerSocketChannel;  
  13. import java.nio.channels.AsynchronousSocketChannel;  
  14. import java.nio.channels.CompletionHandler;  
  15. //import java.nio.channels.WritePendingException;  
  16. import java.nio.charset.CharacterCodingException;  
  17. import java.nio.charset.Charset;  
  18. import java.util.LinkedList;  
  19. import java.util.Queue;  
  20. import java.util.concurrent.Executors;  
  21.   
  22. public class XiaoNa {  
  23.     private final AsynchronousServerSocketChannel server;  
  24.     //写队列,因为当前一个异步写调用还没完成之前,调用异步写会抛WritePendingException  
  25.     //所以需要一个写队列来缓存要写入的数据,这是AIO比较坑的地方  
  26.     private final Queue<ByteBuffer> queue = new LinkedList<ByteBuffer>();  
  27.     private boolean writing = false;  
  28.   
  29.     public static void main(String[] args) throws IOException{  
  30.         XiaoNa xiaona = new XiaoNa();  
  31.         xiaona.listen();  
  32.     }  
  33.   
  34.     public XiaoNa() throws IOException{  
  35.         //设置线程数为CPU核数  
  36.         AsynchronousChannelGroup channelGroup = AsynchronousChannelGroup.withFixedThreadPool(Runtime.getRuntime().availableProcessors(), Executors.defaultThreadFactory());  
  37.         server = AsynchronousServerSocketChannel.open(channelGroup);  
  38.         //重用端口  
  39.         server.setOption(StandardSocketOptions.SO_REUSEADDR, true);  
  40.         //绑定端口并设置连接请求队列长度  
  41.         server.bind(new InetSocketAddress(8383), 80);  
  42.     }  
  43.   
  44.     public void listen() {  
  45.         System.out.println(Thread.currentThread().getName() + ": run in listen method" );  
  46.         //开始接受第一个连接请求  
  47.         server.accept(nullnew CompletionHandler<AsynchronousSocketChannel, Object>(){  
  48.             @Override  
  49.             public void completed(AsynchronousSocketChannel channel,  
  50.                                   Object attachment) {  
  51.                 System.out.println(Thread.currentThread().getName() + ": run in accept completed method" );  
  52.   
  53.                 //先安排处理下一个连接请求,异步非阻塞调用,所以不用担心挂住了  
  54.                 //这里传入this是个地雷,小心多线程  
  55.                 server.accept(nullthis);  
  56.                 //处理连接读写  
  57.                 handle(channel);  
  58.             }  
  59.   
  60.             private void handle(final AsynchronousSocketChannel channel) {  
  61.                 System.out.println(Thread.currentThread().getName() + ": run in handle method" );  
  62.                 //每个AsynchronousSocketChannel,分配一个缓冲区  
  63.                 final ByteBuffer readBuffer = ByteBuffer.allocateDirect(1024);  
  64.                 readBuffer.clear();  
  65.                 channel.read(readBuffer, nullnew CompletionHandler<Integer, Object>(){  
  66.   
  67.                     @Override  
  68.                     public void completed(Integer count, Object attachment) {  
  69.                         System.out.println(Thread.currentThread().getName() + ": run in read completed method" );  
  70.   
  71.                         if(count > 0){  
  72.                             try{  
  73.                                 readBuffer.flip();  
  74.                                 //CharBuffer charBuffer = CharsetHelper.decode(readBuffer);  
  75.                                 CharBuffer charBuffer = Charset.forName("UTF-8").newDecoder().decode(readBuffer);  
  76.                                 String question = charBuffer.toString();  
  77.                                 String answer = Helper.getAnswer(question);  
  78.                                 /*//写入也是异步调用,也可以使用传入CompletionHandler对象的方式来处理写入结果 
  79.                                 //channel.write(CharsetHelper.encode(CharBuffer.wrap(answer))); 
  80.                                 try{ 
  81.                                     channel.write(Charset.forName("UTF-8").newEncoder().encode(CharBuffer.wrap(answer))); 
  82.                                 } 
  83.                                 //Unchecked exception thrown when an attempt is made to write to an asynchronous socket channel and a previous write has not completed. 
  84.                                 //看来操作系统也不可靠 
  85.                                 catch(WritePendingException wpe){ 
  86.                                     //休息一秒再重试,如果失败就不管了 
  87.                                     Helper.sleep(1); 
  88.                                     channel.write(Charset.forName("UTF-8").newEncoder().encode(CharBuffer.wrap(answer))); 
  89.                                 }*/  
  90.                                 writeStringMessage(channel, answer);  
  91.   
  92.                                 readBuffer.clear();  
  93.                             }  
  94.                             catch(IOException e){  
  95.                                 e.printStackTrace();  
  96.                             }  
  97.                         }  
  98.                         else{  
  99.                             try {  
  100.                                 //如果客户端关闭socket,那么服务器也需要关闭,否则浪费CPU  
  101.                                 channel.close();  
  102.                             } catch (IOException e) {  
  103.                                 e.printStackTrace();  
  104.                             }  
  105.                         }  
  106.   
  107.                         //异步调用OS处理下个读取请求  
  108.                         //这里传入this是个地雷,小心多线程  
  109.                         channel.read(readBuffer, nullthis);  
  110.                     }  
  111.   
  112.                     /** 
  113.                      * 服务器读失败处理 
  114.                      * @param exc 
  115.                      * @param attachment 
  116.                      */  
  117.                     @Override  
  118.                     public void failed(Throwable exc, Object attachment) {  
  119.                         System.out.println("server read failed: " + exc);  
  120.                         if(channel != null){  
  121.                             try {  
  122.                                 channel.close();  
  123.                             } catch (IOException e) {  
  124.                                 e.printStackTrace();  
  125.                             }  
  126.                         }  
  127.                     }  
  128.   
  129.                 });  
  130.             }  
  131.   
  132.             /** 
  133.              * 服务器接受连接失败处理 
  134.              * @param exc 
  135.              * @param attachment 
  136.              */  
  137.             @Override  
  138.             public void failed(Throwable exc, Object attachment) {  
  139.                 System.out.println("server accept failed: " + exc);  
  140.             }  
  141.   
  142.         });  
  143.     }  
  144.   
  145.     /** 
  146.      * Enqueues a write of the buffer to the channel. 
  147.      * The call is asynchronous so the buffer is not safe to modify after 
  148.      * passing the buffer here. 
  149.      * 
  150.      * @param buffer the buffer to send to the channel 
  151.      */  
  152.     private void writeMessage(final AsynchronousSocketChannel channel, final ByteBuffer buffer) {  
  153.         boolean threadShouldWrite = false;  
  154.   
  155.         synchronized(queue) {  
  156.             queue.add(buffer);  
  157.             // Currently no thread writing, make this thread dispatch a write  
  158.             if (!writing) {  
  159.                 writing = true;  
  160.                 threadShouldWrite = true;  
  161.             }  
  162.         }  
  163.   
  164.         if (threadShouldWrite) {  
  165.             writeFromQueue(channel);  
  166.         }  
  167.     }  
  168.   
  169.     private void writeFromQueue(final AsynchronousSocketChannel channel) {  
  170.         ByteBuffer buffer;  
  171.   
  172.         synchronized (queue) {  
  173.             buffer = queue.poll();  
  174.             if (buffer == null) {  
  175.                 writing = false;  
  176.             }  
  177.         }  
  178.   
  179.         // No new data in buffer to write  
  180.         if (writing) {  
  181.             writeBuffer(channel, buffer);  
  182.         }  
  183.     }  
  184.   
  185.     private void writeBuffer(final AsynchronousSocketChannel channel, ByteBuffer buffer) {  
  186.         channel.write(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {  
  187.             @Override  
  188.             public void completed(Integer result, ByteBuffer buffer) {  
  189.                 if (buffer.hasRemaining()) {  
  190.                     channel.write(buffer, buffer, this);  
  191.                 } else {  
  192.                     // Go back and check if there is new data to write  
  193.                     writeFromQueue(channel);  
  194.                 }  
  195.             }  
  196.   
  197.             @Override  
  198.             public void failed(Throwable exc, ByteBuffer attachment) {  
  199.                 System.out.println("server write failed: " + exc);  
  200.             }  
  201.         });  
  202.     }  
  203.   
  204.     /** 
  205.      * Sends a message 
  206.      * @param msg 
  207.      * @throws CharacterCodingException 
  208.      */  
  209.     private void writeStringMessage(final AsynchronousSocketChannel channel, String msg) throws CharacterCodingException {  
  210.         writeMessage(channel, Charset.forName("UTF-8").newEncoder().encode(CharBuffer.wrap(msg)));  
  211.     }  
  212. }  
package aio;

/**
 * Created by Administrator on 2016/10/28.
 */
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.StandardSocketOptions;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
//import java.nio.channels.WritePendingException;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.Executors;

public class XiaoNa {
    private final AsynchronousServerSocketChannel server;
    //写队列,因为当前一个异步写调用还没完成之前,调用异步写会抛WritePendingException
    //所以需要一个写队列来缓存要写入的数据,这是AIO比较坑的地方
    private final Queue<ByteBuffer> queue = new LinkedList<ByteBuffer>();
    private boolean writing = false;

    public static void main(String[] args) throws IOException{
        XiaoNa xiaona = new XiaoNa();
        xiaona.listen();
    }

    public XiaoNa() throws IOException{
        //设置线程数为CPU核数
        AsynchronousChannelGroup channelGroup = AsynchronousChannelGroup.withFixedThreadPool(Runtime.getRuntime().availableProcessors(), Executors.defaultThreadFactory());
        server = AsynchronousServerSocketChannel.open(channelGroup);
        //重用端口
        server.setOption(StandardSocketOptions.SO_REUSEADDR, true);
        //绑定端口并设置连接请求队列长度
        server.bind(new InetSocketAddress(8383), 80);
    }

    public void listen() {
        System.out.println(Thread.currentThread().getName() + ": run in listen method" );
        //开始接受第一个连接请求
        server.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>(){
            @Override
            public void completed(AsynchronousSocketChannel channel,
                                  Object attachment) {
                System.out.println(Thread.currentThread().getName() + ": run in accept completed method" );

                //先安排处理下一个连接请求,异步非阻塞调用,所以不用担心挂住了
                //这里传入this是个地雷,小心多线程
                server.accept(null, this);
                //处理连接读写
                handle(channel);
            }

            private void handle(final AsynchronousSocketChannel channel) {
                System.out.println(Thread.currentThread().getName() + ": run in handle method" );
                //每个AsynchronousSocketChannel,分配一个缓冲区
                final ByteBuffer readBuffer = ByteBuffer.allocateDirect(1024);
                readBuffer.clear();
                channel.read(readBuffer, null, new CompletionHandler<Integer, Object>(){

                    @Override
                    public void completed(Integer count, Object attachment) {
                        System.out.println(Thread.currentThread().getName() + ": run in read completed method" );

                        if(count > 0){
                            try{
                                readBuffer.flip();
                                //CharBuffer charBuffer = CharsetHelper.decode(readBuffer);
                                CharBuffer charBuffer = Charset.forName("UTF-8").newDecoder().decode(readBuffer);
                                String question = charBuffer.toString();
                                String answer = Helper.getAnswer(question);
                                /*//写入也是异步调用,也可以使用传入CompletionHandler对象的方式来处理写入结果
                                //channel.write(CharsetHelper.encode(CharBuffer.wrap(answer)));
                                try{
                                    channel.write(Charset.forName("UTF-8").newEncoder().encode(CharBuffer.wrap(answer)));
                                }
                                //Unchecked exception thrown when an attempt is made to write to an asynchronous socket channel and a previous write has not completed.
                                //看来操作系统也不可靠
                                catch(WritePendingException wpe){
                                    //休息一秒再重试,如果失败就不管了
                                    Helper.sleep(1);
                                    channel.write(Charset.forName("UTF-8").newEncoder().encode(CharBuffer.wrap(answer)));
                                }*/
                                writeStringMessage(channel, answer);

                                readBuffer.clear();
                            }
                            catch(IOException e){
                                e.printStackTrace();
                            }
                        }
                        else{
                            try {
                                //如果客户端关闭socket,那么服务器也需要关闭,否则浪费CPU
                                channel.close();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }

                        //异步调用OS处理下个读取请求
                        //这里传入this是个地雷,小心多线程
                        channel.read(readBuffer, null, this);
                    }

                    /**
                     * 服务器读失败处理
                     * @param exc
                     * @param attachment
                     */
                    @Override
                    public void failed(Throwable exc, Object attachment) {
                        System.out.println("server read failed: " + exc);
                        if(channel != null){
                            try {
                                channel.close();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                    }

                });
            }

            /**
             * 服务器接受连接失败处理
             * @param exc
             * @param attachment
             */
            @Override
            public void failed(Throwable exc, Object attachment) {
                System.out.println("server accept failed: " + exc);
            }

        });
    }

    /**
     * Enqueues a write of the buffer to the channel.
     * The call is asynchronous so the buffer is not safe to modify after
     * passing the buffer here.
     *
     * @param buffer the buffer to send to the channel
     */
    private void writeMessage(final AsynchronousSocketChannel channel, final ByteBuffer buffer) {
        boolean threadShouldWrite = false;

        synchronized(queue) {
            queue.add(buffer);
            // Currently no thread writing, make this thread dispatch a write
            if (!writing) {
                writing = true;
                threadShouldWrite = true;
            }
        }

        if (threadShouldWrite) {
            writeFromQueue(channel);
        }
    }

    private void writeFromQueue(final AsynchronousSocketChannel channel) {
        ByteBuffer buffer;

        synchronized (queue) {
            buffer = queue.poll();
            if (buffer == null) {
                writing = false;
            }
        }

        // No new data in buffer to write
        if (writing) {
            writeBuffer(channel, buffer);
        }
    }

    private void writeBuffer(final AsynchronousSocketChannel channel, ByteBuffer buffer) {
        channel.write(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
            @Override
            public void completed(Integer result, ByteBuffer buffer) {
                if (buffer.hasRemaining()) {
                    channel.write(buffer, buffer, this);
                } else {
                    // Go back and check if there is new data to write
                    writeFromQueue(channel);
                }
            }

            @Override
            public void failed(Throwable exc, ByteBuffer attachment) {
                System.out.println("server write failed: " + exc);
            }
        });
    }

    /**
     * Sends a message
     * @param msg
     * @throws CharacterCodingException
     */
    private void writeStringMessage(final AsynchronousSocketChannel channel, String msg) throws CharacterCodingException {
        writeMessage(channel, Charset.forName("UTF-8").newEncoder().encode(CharBuffer.wrap(msg)));
    }
}

客户端代码:

  1. package aio;  
  2.   
  3. /** 
  4.  * Created by Administrator on 2016/10/28. 
  5.  */  
  6. import java.io.IOException;  
  7. import java.net.InetSocketAddress;  
  8. import java.net.StandardSocketOptions;  
  9. import java.nio.ByteBuffer;  
  10. import java.nio.CharBuffer;  
  11. import java.nio.channels.AsynchronousChannelGroup;  
  12. import java.nio.channels.AsynchronousSocketChannel;  
  13. import java.nio.channels.CompletionHandler;  
  14. import java.nio.charset.CharacterCodingException;  
  15. import java.nio.charset.Charset;  
  16. import java.util.LinkedList;  
  17. import java.util.Queue;  
  18. import java.util.concurrent.CountDownLatch;  
  19. import java.util.concurrent.Executors;  
  20.   
  21. public class Client implements Runnable{  
  22.     private AsynchronousSocketChannel channel;  
  23.     private Helper helper;  
  24.     private CountDownLatch latch;  
  25.     private final Queue<ByteBuffer> queue = new LinkedList<ByteBuffer>();  
  26.     private boolean writing = false;  
  27.   
  28.     public Client(AsynchronousChannelGroup channelGroup, CountDownLatch latch) throws IOException, InterruptedException{  
  29.         this.latch = latch;  
  30.         helper = new Helper();  
  31.         initChannel(channelGroup);  
  32.     }  
  33.   
  34.     private void initChannel(AsynchronousChannelGroup channelGroup) throws IOException {  
  35.         //在默认channel group下创建一个socket channel  
  36.         channel = AsynchronousSocketChannel.open(channelGroup);  
  37.         //设置Socket选项  
  38.         channel.setOption(StandardSocketOptions.TCP_NODELAY, true);  
  39.         channel.setOption(StandardSocketOptions.SO_KEEPALIVE, true);  
  40.         channel.setOption(StandardSocketOptions.SO_REUSEADDR, true);  
  41.     }  
  42.   
  43.     public static void main(String[] args) throws IOException, InterruptedException {  
  44.         int sleepTime = Integer.parseInt(args[0]);  
  45.         Helper.sleep(sleepTime);  
  46.   
  47.         AsynchronousChannelGroup channelGroup = AsynchronousChannelGroup.withFixedThreadPool(Runtime.getRuntime().availableProcessors(), Executors.defaultThreadFactory());  
  48.         //只能跑一个线程,第二个线程connect会挂住,暂时不明原因  
  49.         final int THREAD_NUM = 1;  
  50.         CountDownLatch latch = new CountDownLatch(THREAD_NUM);  
  51.   
  52.         //创建个多线程模拟多个客户端,模拟失败,无效  
  53.         //只能通过命令行同时运行多个进程来模拟多个客户端  
  54.         for(int i=0; i<THREAD_NUM; i++){  
  55.             Client c = new Client(channelGroup, latch);  
  56.             Thread t = new Thread(c);  
  57.             System.out.println(t.getName() + "---start");  
  58.             t.start();  
  59.             //让主线程等待子线程处理再退出, 这对于异步调用无效  
  60.             //t.join();  
  61.         }  
  62.   
  63.         latch.await();  
  64.   
  65.         if(channelGroup !=null){  
  66.             channelGroup.shutdown();  
  67.         }  
  68.     }  
  69.   
  70.     @Override  
  71.     public void run() {  
  72.         System.out.println(Thread.currentThread().getName() + "---run");  
  73.   
  74.         //连接服务器  
  75.         channel.connect(new InetSocketAddress("localhost"8383), nullnew CompletionHandler<Void, Void>(){  
  76.             final ByteBuffer readBuffer = ByteBuffer.allocateDirect(1024);  
  77.   
  78.             @Override  
  79.             public void completed(Void result, Void attachment) {  
  80.                 //连接成功后, 异步调用OS向服务器写一条消息  
  81.                 try {  
  82.                     //channel.write(CharsetHelper.encode(CharBuffer.wrap(helper.getWord())));  
  83.                     writeStringMessage(helper.getWord());  
  84.                 } catch (CharacterCodingException e) {  
  85.                     e.printStackTrace();  
  86.                 }  
  87.   
  88.                 //helper.sleep();//等待写异步调用完成  
  89.                 readBuffer.clear();  
  90.                 //异步调用OS读取服务器发送的消息  
  91.                 channel.read(readBuffer, nullnew CompletionHandler<Integer, Object>(){  
  92.   
  93.                     @Override  
  94.                     public void completed(Integer result, Object attachment) {  
  95.                         try{  
  96.                             //异步读取完成后处理  
  97.                             if(result > 0){  
  98.                                 readBuffer.flip();  
  99.                                 CharBuffer charBuffer = CharsetHelper.decode(readBuffer);  
  100.                                 String answer = charBuffer.toString();  
  101.                                 System.out.println(Thread.currentThread().getName() + "---" + answer);  
  102.                                 readBuffer.clear();  
  103.   
  104.                                 String word = helper.getWord();  
  105.                                 if(word != null){  
  106.                                     //异步写  
  107.                                     //channel.write(CharsetHelper.encode(CharBuffer.wrap(word)));  
  108.                                     writeStringMessage(word);  
  109.                                     //helper.sleep();//等待异步操作  
  110.                                     channel.read(readBuffer, nullthis);  
  111.                                 }  
  112.                                 else{  
  113.                                     //不想发消息了,主动关闭channel  
  114.                                     shutdown();  
  115.                                 }  
  116.                             }  
  117.                             else{  
  118.                                 //对方已经关闭channel,自己被动关闭,避免空循环  
  119.                                 shutdown();  
  120.                             }  
  121.                         }  
  122.                         catch(Exception e){  
  123.                             e.printStackTrace();  
  124.                         }  
  125.                     }  
  126.   
  127.                     /** 
  128.                      * 读取失败处理 
  129.                      * @param exc 
  130.                      * @param attachment 
  131.                      */  
  132.                     @Override  
  133.                     public void failed(Throwable exc, Object attachment) {  
  134.                         System.out.println("client read failed: " + exc);  
  135.                         try {  
  136.                             shutdown();  
  137.                         } catch (IOException e) {  
  138.                             e.printStackTrace();  
  139.                         }  
  140.                     }  
  141.   
  142.                 });  
  143.             }  
  144.   
  145.             /** 
  146.              * 连接失败处理 
  147.              * @param exc 
  148.              * @param attachment 
  149.              */  
  150.             @Override  
  151.             public void failed(Throwable exc, Void attachment) {  
  152.                 System.out.println("client connect to server failed: " + exc);  
  153.   
  154.                 try {  
  155.                     shutdown();  
  156.                 } catch (IOException e) {  
  157.                     e.printStackTrace();  
  158.                 }  
  159.             }  
  160.         });  
  161.     }  
  162.   
  163.     private void shutdown() throws IOException {  
  164.         if(channel != null){  
  165.             channel.close();  
  166.         }  
  167.   
  168.         latch.countDown();  
  169.     }  
  170.   
  171.     /** 
  172.      * Enqueues a write of the buffer to the channel. 
  173.      * The call is asynchronous so the buffer is not safe to modify after 
  174.      * passing the buffer here. 
  175.      * 
  176.      * @param buffer the buffer to send to the channel 
  177.      */  
  178.     private void writeMessage(final ByteBuffer buffer) {  
  179.         boolean threadShouldWrite = false;  
  180.   
  181.         synchronized(queue) {  
  182.             queue.add(buffer);  
  183.             // Currently no thread writing, make this thread dispatch a write  
  184.             if (!writing) {  
  185.                 writing = true;  
  186.                 threadShouldWrite = true;  
  187.             }  
  188.         }  
  189.   
  190.         if (threadShouldWrite) {  
  191.             writeFromQueue();  
  192.         }  
  193.     }  
  194.   
  195.     private void writeFromQueue() {  
  196.         ByteBuffer buffer;  
  197.   
  198.         synchronized (queue) {  
  199.             buffer = queue.poll();  
  200.             if (buffer == null) {  
  201.                 writing = false;  
  202.             }  
  203.         }  
  204.   
  205.         // No new data in buffer to write  
  206.         if (writing) {  
  207.             writeBuffer(buffer);  
  208.         }  
  209.     }  
  210.   
  211.     private void writeBuffer(ByteBuffer buffer) {  
  212.         channel.write(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {  
  213.             @Override  
  214.             public void completed(Integer result, ByteBuffer buffer) {  
  215.                 if (buffer.hasRemaining()) {  
  216.                     channel.write(buffer, buffer, this);  
  217.                 } else {  
  218.                     // Go back and check if there is new data to write  
  219.                     writeFromQueue();  
  220.                 }  
  221.             }  
  222.   
  223.             @Override  
  224.             public void failed(Throwable exc, ByteBuffer attachment) {  
  225.             }  
  226.         });  
  227.     }  
  228.   
  229.     /** 
  230.      * Sends a message 
  231.      * @param msg 
  232.      * @throws CharacterCodingException 
  233.      */  
  234.     public void writeStringMessage(String msg) throws CharacterCodingException {  
  235.         writeMessage(Charset.forName("UTF-8").newEncoder().encode(CharBuffer.wrap(msg)));  
  236.     }  
  237. }  
package aio;

/**
 * Created by Administrator on 2016/10/28.
 */
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.StandardSocketOptions;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.util.LinkedList;
import java.util.Queue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;

public class Client implements Runnable{
    private AsynchronousSocketChannel channel;
    private Helper helper;
    private CountDownLatch latch;
    private final Queue<ByteBuffer> queue = new LinkedList<ByteBuffer>();
    private boolean writing = false;

    public Client(AsynchronousChannelGroup channelGroup, CountDownLatch latch) throws IOException, InterruptedException{
        this.latch = latch;
        helper = new Helper();
        initChannel(channelGroup);
    }

    private void initChannel(AsynchronousChannelGroup channelGroup) throws IOException {
        //在默认channel group下创建一个socket channel
        channel = AsynchronousSocketChannel.open(channelGroup);
        //设置Socket选项
        channel.setOption(StandardSocketOptions.TCP_NODELAY, true);
        channel.setOption(StandardSocketOptions.SO_KEEPALIVE, true);
        channel.setOption(StandardSocketOptions.SO_REUSEADDR, true);
    }

    public static void main(String[] args) throws IOException, InterruptedException {
        int sleepTime = Integer.parseInt(args[0]);
        Helper.sleep(sleepTime);

        AsynchronousChannelGroup channelGroup = AsynchronousChannelGroup.withFixedThreadPool(Runtime.getRuntime().availableProcessors(), Executors.defaultThreadFactory());
        //只能跑一个线程,第二个线程connect会挂住,暂时不明原因
        final int THREAD_NUM = 1;
        CountDownLatch latch = new CountDownLatch(THREAD_NUM);

        //创建个多线程模拟多个客户端,模拟失败,无效
        //只能通过命令行同时运行多个进程来模拟多个客户端
        for(int i=0; i<THREAD_NUM; i++){
            Client c = new Client(channelGroup, latch);
            Thread t = new Thread(c);
            System.out.println(t.getName() + "---start");
            t.start();
            //让主线程等待子线程处理再退出, 这对于异步调用无效
            //t.join();
        }

        latch.await();

        if(channelGroup !=null){
            channelGroup.shutdown();
        }
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "---run");

        //连接服务器
        channel.connect(new InetSocketAddress("localhost", 8383), null, new CompletionHandler<Void, Void>(){
            final ByteBuffer readBuffer = ByteBuffer.allocateDirect(1024);

            @Override
            public void completed(Void result, Void attachment) {
                //连接成功后, 异步调用OS向服务器写一条消息
                try {
                    //channel.write(CharsetHelper.encode(CharBuffer.wrap(helper.getWord())));
                    writeStringMessage(helper.getWord());
                } catch (CharacterCodingException e) {
                    e.printStackTrace();
                }

                //helper.sleep();//等待写异步调用完成
                readBuffer.clear();
                //异步调用OS读取服务器发送的消息
                channel.read(readBuffer, null, new CompletionHandler<Integer, Object>(){

                    @Override
                    public void completed(Integer result, Object attachment) {
                        try{
                            //异步读取完成后处理
                            if(result > 0){
                                readBuffer.flip();
                                CharBuffer charBuffer = CharsetHelper.decode(readBuffer);
                                String answer = charBuffer.toString();
                                System.out.println(Thread.currentThread().getName() + "---" + answer);
                                readBuffer.clear();

                                String word = helper.getWord();
                                if(word != null){
                                    //异步写
                                    //channel.write(CharsetHelper.encode(CharBuffer.wrap(word)));
                                    writeStringMessage(word);
                                    //helper.sleep();//等待异步操作
                                    channel.read(readBuffer, null, this);
                                }
                                else{
                                    //不想发消息了,主动关闭channel
                                    shutdown();
                                }
                            }
                            else{
                                //对方已经关闭channel,自己被动关闭,避免空循环
                                shutdown();
                            }
                        }
                        catch(Exception e){
                            e.printStackTrace();
                        }
                    }

                    /**
                     * 读取失败处理
                     * @param exc
                     * @param attachment
                     */
                    @Override
                    public void failed(Throwable exc, Object attachment) {
                        System.out.println("client read failed: " + exc);
                        try {
                            shutdown();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }

                });
            }

            /**
             * 连接失败处理
             * @param exc
             * @param attachment
             */
            @Override
            public void failed(Throwable exc, Void attachment) {
                System.out.println("client connect to server failed: " + exc);

                try {
                    shutdown();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    private void shutdown() throws IOException {
        if(channel != null){
            channel.close();
        }

        latch.countDown();
    }

    /**
     * Enqueues a write of the buffer to the channel.
     * The call is asynchronous so the buffer is not safe to modify after
     * passing the buffer here.
     *
     * @param buffer the buffer to send to the channel
     */
    private void writeMessage(final ByteBuffer buffer) {
        boolean threadShouldWrite = false;

        synchronized(queue) {
            queue.add(buffer);
            // Currently no thread writing, make this thread dispatch a write
            if (!writing) {
                writing = true;
                threadShouldWrite = true;
            }
        }

        if (threadShouldWrite) {
            writeFromQueue();
        }
    }

    private void writeFromQueue() {
        ByteBuffer buffer;

        synchronized (queue) {
            buffer = queue.poll();
            if (buffer == null) {
                writing = false;
            }
        }

        // No new data in buffer to write
        if (writing) {
            writeBuffer(buffer);
        }
    }

    private void writeBuffer(ByteBuffer buffer) {
        channel.write(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() {
            @Override
            public void completed(Integer result, ByteBuffer buffer) {
                if (buffer.hasRemaining()) {
                    channel.write(buffer, buffer, this);
                } else {
                    // Go back and check if there is new data to write
                    writeFromQueue();
                }
            }

            @Override
            public void failed(Throwable exc, ByteBuffer attachment) {
            }
        });
    }

    /**
     * Sends a message
     * @param msg
     * @throws CharacterCodingException
     */
    public void writeStringMessage(String msg) throws CharacterCodingException {
        writeMessage(Charset.forName("UTF-8").newEncoder().encode(CharBuffer.wrap(msg)));
    }
}


工具类:

  1. package aio;  
  2.   
  3. /** 
  4.  * Created by Administrator on 2016/10/28. 
  5.  */  
  6. import java.util.Random;  
  7. import java.util.concurrent.ArrayBlockingQueue;  
  8. import java.util.concurrent.BlockingQueue;  
  9. import java.util.concurrent.TimeUnit;  
  10.   
  11. public class Helper {  
  12.     private static BlockingQueue<String> words;  
  13.     private static Random random;  
  14.   
  15.     public Helper() throws InterruptedException{  
  16.         words = new ArrayBlockingQueue<String>(5);  
  17.         words.put("hi");  
  18.         words.put("who");  
  19.         words.put("what");  
  20.         words.put("where");  
  21.         words.put("bye");  
  22.   
  23.         random = new Random();  
  24.     }  
  25.   
  26.     public String getWord(){  
  27.         return words.poll();  
  28.     }  
  29.   
  30.     public void sleep() {  
  31.         try {  
  32.             TimeUnit.SECONDS.sleep(random.nextInt(3));  
  33.         } catch (InterruptedException e) {  
  34.             e.printStackTrace();  
  35.         }  
  36.     }  
  37.   
  38.     public static void sleep(long l) {  
  39.         try {  
  40.             TimeUnit.SECONDS.sleep(l);  
  41.         } catch (InterruptedException e) {  
  42.             e.printStackTrace();  
  43.         }  
  44.     }  
  45.   
  46.     public static String getAnswer(String question){  
  47.         String answer = null;  
  48.   
  49.         switch(question){  
  50.             case "who":  
  51.                 answer = "我是小娜\n";  
  52.                 break;  
  53.             case "what":  
  54.                 answer = "我是来帮你解闷的\n";  
  55.                 break;  
  56.             case "where":  
  57.                 answer = "我来自外太空\n";  
  58.                 break;  
  59.             case "hi":  
  60.                 answer = "hello\n";  
  61.                 break;  
  62.             case "bye":  
  63.                 answer = "88\n";  
  64.                 break;  
  65.             default:  
  66.                 answer = "请输入 who, 或者what, 或者where";  
  67.         }  
  68.   
  69.         return answer;  
  70.     }  
  71. }  
package aio;

/**
 * Created by Administrator on 2016/10/28.
 */
import java.util.Random;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;

public class Helper {
    private static BlockingQueue<String> words;
    private static Random random;

    public Helper() throws InterruptedException{
        words = new ArrayBlockingQueue<String>(5);
        words.put("hi");
        words.put("who");
        words.put("what");
        words.put("where");
        words.put("bye");

        random = new Random();
    }

    public String getWord(){
        return words.poll();
    }

    public void sleep() {
        try {
            TimeUnit.SECONDS.sleep(random.nextInt(3));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void sleep(long l) {
        try {
            TimeUnit.SECONDS.sleep(l);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static String getAnswer(String question){
        String answer = null;

        switch(question){
            case "who":
                answer = "我是小娜\n";
                break;
            case "what":
                answer = "我是来帮你解闷的\n";
                break;
            case "where":
                answer = "我来自外太空\n";
                break;
            case "hi":
                answer = "hello\n";
                break;
            case "bye":
                answer = "88\n";
                break;
            default:
                answer = "请输入 who, 或者what, 或者where";
        }

        return answer;
    }
}

  1. package aio;  
  2.   
  3. /** 
  4.  * Created by Administrator on 2016/10/28. 
  5.  */  
  6.   
  7. import java.nio.ByteBuffer;  
  8. import java.nio.CharBuffer;  
  9. import java.nio.charset.CharacterCodingException;  
  10. import java.nio.charset.Charset;  
  11. import java.nio.charset.CharsetDecoder;  
  12. import java.nio.charset.CharsetEncoder;  
  13.   
  14. public class CharsetHelper {  
  15.     private static final String UTF_8 = "UTF-8";  
  16.     private static CharsetEncoder encoder = Charset.forName(UTF_8).newEncoder();  
  17.     private static CharsetDecoder decoder = Charset.forName(UTF_8).newDecoder();  
  18.   
  19.     public static ByteBuffer encode(CharBuffer in) throws CharacterCodingException{  
  20.         return encoder.encode(in);  
  21.     }  
  22.   
  23.     public static CharBuffer decode(ByteBuffer in) throws CharacterCodingException{  
  24.         return decoder.decode(in);  
  25.     }  
  26. }  
package aio;

/**
 * Created by Administrator on 2016/10/28.
 */

import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CharsetEncoder;

public class CharsetHelper {
    private static final String UTF_8 = "UTF-8";
    private static CharsetEncoder encoder = Charset.forName(UTF_8).newEncoder();
    private static CharsetDecoder decoder = Charset.forName(UTF_8).newDecoder();

    public static ByteBuffer encode(CharBuffer in) throws CharacterCodingException{
        return encoder.encode(in);
    }

    public static CharBuffer decode(ByteBuffer in) throws CharacterCodingException{
        return decoder.decode(in);
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值