看了挺多关于NIO的东西,网上复制的代码也跑了几个,
但是多多少少都存在各种问题。
例子先写出来,具体的细节还有待分析
下面直接贴我改过的代码,源码网址找不到了。
服务端:
package com.nio.service; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.ClosedChannelException; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.util.Iterator; import java.util.Set; public class Server { private Selector selector; private ByteBuffer readBuffer = ByteBuffer.allocate(1024);//调整缓存的大小可以看到打印输出的变化 private ByteBuffer sendBuffer = ByteBuffer.allocate(1024);//调整缓存的大小可以看到打印输出的变化 /** * 启动服务 * * @throws IOException */ public void start() throws IOException { // 打开服务器套接字通道 ServerSocketChannel ssc = ServerSocketChannel.open(); // 服务器配置为非阻塞 ssc.configureBlocking(false); // 进行服务的绑定 ssc.bind(new InetSocketAddress("localhost", 8000)); // 通过open()方法找到Selector selector = Selector.open(); // 注册到selector,等待连接 ssc.register(selector, SelectionKey.OP_ACCEPT); while (!Thread.currentThread().isInterrupted()) { selector.select(); Set<SelectionKey> keys = selector.selectedKeys(); Iterator<SelectionKey> keyIterator = keys.iterator(); while (keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); if (!key.isValid()) { continue; } if (key.isAcceptable()) { accept(key); } else if (key.isReadable()) { read(key); } else if (key.isWritable()) { write(key); } keyIterator.remove(); //该事件已经处理,可以丢弃 } } } /** * 返回数据到客户端 * * @param key * @throws IOException * @throws ClosedChannelException */ private void write(SelectionKey key) throws IOException, ClosedChannelException { SocketChannel channel = (SocketChannel) key.channel(); // 清除发送缓存 sendBuffer.clear(); sendBuffer.put("服务端已成功获取信息".getBytes()); sendBuffer.flip(); channel.write(sendBuffer); // 从缓存中读取数据,并发送消息到客户端 channel.register(selector, SelectionKey.OP_READ); } /** * 读取客户端发送过来的数据 * * @param key * @throws IOException */ private void read(SelectionKey key) throws IOException { SocketChannel socketChannel = (SocketChannel) key.channel(); // 清除缓冲区内容 this.readBuffer.clear(); // this.readBuffer.flip(); // 将客户端的内容读取到缓存区中,并记录缓存区的长度 int numRead = socketChannel.read(this.readBuffer); // 将缓存转成String String str = new String(readBuffer.array(), 0, numRead); System.out.println("客户端请求的数据:" + str); socketChannel.register(selector, SelectionKey.OP_WRITE); } /** * 接受链接 * * @param key * @throws IOException */ private void accept(SelectionKey key) throws IOException { ServerSocketChannel ssc = (ServerSocketChannel) key.channel(); SocketChannel clientChannel = ssc.accept(); clientChannel.configureBlocking(false); clientChannel.register(selector, SelectionKey.OP_READ); System.out.println("a new client connected "+clientChannel.getRemoteAddress()); } public static void main(String[] args) throws IOException { System.out.println("服务开启..."); new Server().start(); } }
客户端:
package com.nio.client; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.SocketChannel; import java.util.Iterator; import java.util.Scanner; import java.util.Set; public class Client { ByteBuffer writeBuffer = ByteBuffer.allocate(1024); ByteBuffer readBuffer = ByteBuffer.allocate(1024); @SuppressWarnings("resource") public void start() throws IOException { // 打开socket通道 SocketChannel sc = SocketChannel.open(); //设置为非阻塞 sc.configureBlocking(false); //连接服务器地址和端口 sc.connect(new InetSocketAddress("localhost", 8000)); //打开选择器 Selector selector = Selector.open(); //注册连接服务器socket的动作 sc.register(selector, SelectionKey.OP_CONNECT); Scanner scanner = new Scanner(System.in); while (true) { //选择一组键,其相应的通道已为 I/O 操作准备就绪。 //此方法执行处于阻塞模式的选择操作。 selector.select(); //返回此选择器的已选择键集。 Set<SelectionKey> keys = selector.selectedKeys(); System.out.println("keys=" + keys.size()); Iterator<SelectionKey> keyIterator = keys.iterator(); while (keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); keyIterator.remove(); // 判断此通道上是否正在进行连接操作。 if (key.isConnectable()) { sc.finishConnect(); sc.register(selector, SelectionKey.OP_WRITE); System.out.println("server connected..."); break; } else if (key.isWritable()) { //写数据 System.out.print("请输入发送到服务端的数据:"); String message = scanner.nextLine(); // 清除缓存数据 writeBuffer.clear(); writeBuffer.put(message.getBytes()); writeBuffer.flip(); sc.write(writeBuffer); sc.register(selector, SelectionKey.OP_READ); } else if (key.isReadable()){//读取数据 System.out.print("服务端返回的数据:"); SocketChannel client = (SocketChannel) key.channel(); //将缓冲区清空以备下次读取 readBuffer.clear(); int num = client.read(readBuffer); System.out.println(new String(readBuffer.array(),0, num)); //注册读操作,下一次读取 sc.register(selector, SelectionKey.OP_WRITE); } } } } public static void main(String[] args) throws IOException { new Client().start(); } }
-------------------------------------------------------------------------------------------------------------------
之前拷贝过来的时候代码存在以下问题:
1,服务端的read(SelectionKey key)方法原先是这样的:
/** * 读取客户端发送过来的数据 * * @param key * @throws IOException */ private void read(SelectionKey key) throws IOException { SocketChannel socketChannel = (SocketChannel) key.channel(); this.readBuffer.clear(); this.readBuffer.flip(); // 将客户端的内容读取到缓存区中,并记录缓存区的长度 int numRead = socketChannel.read(this.readBuffer); // 将缓存转成String String str = new String(readBuffer.array(), 0, numRead); System.out.println("客户端请求的数据:" + str); socketChannel.register(selector, SelectionKey.OP_WRITE); }区别是我把下面这行注释了
this.readBuffer.flip();
flip()方法说明https://www.cnblogs.com/woshijpf/articles/3723364.html:
buffer中的flip方法涉及到bufer中的Capacity,Position和Limit三个概念。
其中Capacity在读写模式下都是固定的,就是我们分配的缓冲大小,Position类似于读写指针,
表示当前读(写)到什么位置,Limit在写模式下表示最多能写入多少数据,
此时和Capacity相同,在读模式下表示最多能读多少数据,此时和缓存中的实际数据大小相同。
在写模式下调用flip方法,那么limit就设置为了position当前的值(即当前写了多少数据),postion会被置为0,以表示读操作从缓存的头开始读。
也就是说调用flip之后,读写指针指到缓存头部,并且设置了最多只能读出之前写入的数据长度(而不是整个缓存的容量大小)。
flip源码:
public final Buffer flip() { limit = position; position = 0; mark = -1; return this; }所以,当执行flip()后,readBuffer的limit重置成0导致无法写入数据(position初始值为0)。