实现这样一个程序:客户端读取键盘输入,并发送到服务器端,服务器端接收信息并打印。
首先先写一个阻塞式的程序:
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时可以正常运行:
但是再运行一遍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线程的情况下,程序也能正常运行: