使用NIO实现非阻塞式的网络通信

来源:互联网 发布:中老年服饰淘宝模特 编辑:程序博客网 时间:2024/05/17 23:56

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

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

0 0
原创粉丝点击