看了挺多关于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)。