使用NIO实现非阻塞式的网络通信【转载】

  实现这样一个程序:客户端读取键盘输入,并发送到服务器端,服务器端接收信息并打印。 
  首先先写一个阻塞式的程序:

package nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Date;
import java.util.Scanner;

import org.junit.Test;

public class TestBlocking {

    @Test
    public void client() throws IOException {

        // 获取通道
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8888));

        // 分配1024字节大小的缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        Scanner scan = new Scanner(System.in);

        // 输入
        while (scan.hasNext()) {

            String str = scan.next();

            buffer.put((new Date().toString() + "\n" + str).getBytes());

            // 切换回读模式,实质是令limit=position;position=0。
            buffer.flip();
            // 写入通道
            socketChannel.write(buffer);
            // 清空缓冲区
            buffer.clear();
        }
    }

    @Test
    public void server() throws IOException {
        // 获取服务器端通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        // 绑定端口号
        serverSocketChannel.bind(new InetSocketAddress(8888));

        // 接收客户端通道
        SocketChannel socketChannel = serverSocketChannel.accept();

        // 分配缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        // 每次读取数据的长度
        int len;

        // 接收数据
        while ((len = socketChannel.read(buffer)) > 0) {

            buffer.flip();

            // 将每次读取的数据输出
            System.out.println(new String(buffer.array(), 0, len));

            buffer.clear();
        }
    }
}

 

  运行程序(注意先启动server,再启动client),发现当只有一个server一个client时可以正常运行: 
   
  image_1b8of1b9e15vmro7mu44e1uc113.png-22.9kB 
   
  但是再运行一遍client以添加一个client线程,会发现第二次添加的client线程发送的信息无法显示,这是因为,服务器端的线程一直阻塞在接收第一个客户端线程发送信息的位置,即63行,无法接收第二个客户端线程的连接请求。 
   
  现在,我们使用NIO来实现非阻塞式的程序,以解决上述问题:

package nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Date;
import java.util.Iterator;
import java.util.Scanner;

import org.junit.Test;

public class TestNonBlocking {

    @Test
    public void client() throws IOException {

        // 获取通道
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8888));

        // 分配1024字节大小的缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        Scanner scan = new Scanner(System.in);

        // 输入
        while (scan.hasNext()) {

            String str = scan.next();

            buffer.put((new Date().toString() + "--" + str).getBytes());

            // 切换回读模式,实质是令limit=position;position=0。
            buffer.flip();
            // 写入通道
            socketChannel.write(buffer);
            // 清空缓冲区
            buffer.clear();
        }
    }

    @Test
    public void server() throws IOException {
        // 获取服务器端通道
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

        // 切换该通道为非阻塞模式
        serverSocketChannel.configureBlocking(false);

        // 绑定端口号
        serverSocketChannel.bind(new InetSocketAddress(8888));

        // 获取选择器
        Selector selector = Selector.open();

        // 将通道注册到选择器上,并指定监听的事件类型为“接收”
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        // 轮询式地获取选择器上已经准备就绪的事件
        //selector.select()方法是阻塞的
        while (selector.select() > 0) {

            // 获取当前选择器中所有已经注册的“选择键”,即已就绪的监听事件
            Iterator<SelectionKey> it = selector.selectedKeys().iterator();

            while (it.hasNext()) {
                // 获取已经准备就绪的事件
                SelectionKey selectionKey = it.next();

                // 判断事件的类型
                // 接收类型
                if (selectionKey.isAcceptable()) {
                    // 接收客户端通道
                    SocketChannel socketChannel = serverSocketChannel.accept();

                    socketChannel.configureBlocking(false);

                    socketChannel.register(selector, selectionKey.OP_READ);
                } else if (selectionKey.isReadable()) {
                    // 获取当前选择器上“读就绪”状态的通道
                    SocketChannel socketChannel = (SocketChannel) selectionKey.channel();

                    ByteBuffer buffer = ByteBuffer.allocate(1024);

                    int len;

                    while ((len = socketChannel.read(buffer)) > 0) {
                        buffer.flip();
                        System.out.println(new String(buffer.array(), 0, len));
                        buffer.clear();
                    }
                }
                // 取消选择键 SelectionKey
                it.remove();
            }

        }
    }
}

  再次运行程序,发现即使在多个client线程的情况下,程序也能正常运行: 

   
  image_1b8of8ec5un4hrg1eis15csqlr1g.png-25.2kB

posted @ 2017-04-28 13:39  pilink  阅读(305)  评论(0编辑  收藏  举报