转自:轻样知生 - JAVA网络通信之NIO - Tylili
本篇将展现JAVA网络通信中NIO的部分,和上一篇不同,本篇所用的Socket是通过SocketChannel的方式,这是NIO与传统IO最主要的区别。
一、基于缓冲的SocketChannel
和传统的IO基于流的方式不同,NIO采用基于缓冲的方式。二者的最重要的区别就是基于流的方式是阻塞的,而基于缓冲的方式却不需要阻塞。关于阻塞与非阻塞,最直观的的便是当我们通过IO的方式进行通信的时候,当前线程必须等待返回请求响应并处理完该响应之后才能进行发送下一次请求。但NIO却并非如此,当通过SocketChannel发送请求之后,当前线程无需等待便可发送下一次请求。
二、基于事件的Selector
Selector是JAVA NIO中的新概念,它通常需要跟Channel配合使用,用于注册Channel并监听Channel的事件。在JAVA的网络通信中,Channel的事件有下列四种:
-
Connect, 连接就绪事件,当SocketChannel成功连接到服务器时,该事件触发。
-
Accept,接收就绪事件,当ServerSocketChannel成功接收到客户端的请求,并表示可以接收数据时,该事件触发。
-
Read,读就绪事件,当SocketChannel或ServerSocketChannel表示可以读取通道中数据时,该事件触发。
-
Write,写就绪事件,当SocketChannel或ServerSocketChannel表示可以往通道中写数据时,该事件触发。
当Channel在Selector注册之后,会生成一个对应的SelectionKey,该SelectionKey会立即返回,也可以通过Selector的selectedKeys()方法获取一个就绪状态的SelectionKey集。之后便可通过SelectionKey来控制请求会话。具体代码如下:
客户端:
import java.io.IOException;
import java.net.InetAddress;
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.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class SocketClient {
private Selector selector;
private SocketChannel socketCli;
private String host;
private int port;
public SocketClient(String host, int port) throws Exception {
socketCli = SocketChannel.open();
socketCli.configureBlocking(false); // 非阻塞模式
selector = Selector.open();
this.host = host;
this.port = port;
}
public void send(String msg) throws IOException {
/**
* 在请求连接之前注册监听Connect事件,否则将无法在Selector中获取到SelectorKey,
* 更无法获取SocketChannel的Connect事件
*/
socketCli.register(selector, SelectionKey.OP_CONNECT);
socketCli.connect(new InetSocketAddress(InetAddress.getByName(host), port)); // 请求连接
while (selector.select(100) > 0) { // 轮询校验通道状态是否就绪
Set<SelectionKey> selectionKeys = selector.selectedKeys(); // 获取就绪状态的SelectorKey集合
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
iterator.remove();
if (selectionKey.isValid()) { // 是否有效
if (selectionKey.isConnectable()) { // 是否连接就绪
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
if (socketChannel.finishConnect()) {
socketChannel.register(selector, SelectionKey.OP_WRITE); // 注册写就绪事件
}
}
if (selectionKey.isWritable()) { // 是否写就绪
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
byte[] bytes = msg.getBytes();
ByteBuffer byteBuffer = ByteBuffer.allocate(bytes.length);
byteBuffer.put(bytes); // 将字节写入缓冲
byteBuffer.flip(); // 切换到读模式
while (byteBuffer.hasRemaining()) {
socketChannel.write(byteBuffer); // 将缓冲写入通道
}
socketChannel.register(selector, SelectionKey.OP_READ); // 注册读就绪事件
}
if (selectionKey.isReadable()) { // 是否读就绪
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
ByteBuffer byteBufferRead = ByteBuffer.allocate(1024);
int readBytes = socketChannel.read(byteBufferRead); // 将通道中的数据读入缓冲
if (readBytes > 0) {
byteBufferRead.flip(); // 切换到读模式
byte[] bytesRead = new byte[byteBufferRead.remaining()];
byteBufferRead.get(bytesRead); // 将缓冲数据读入到字节数组中
System.out.println("from SERVER : " + new String(bytesRead, "UTF-8"));
}
}
}
}
}
}
public void close() {
if (socketCli != null && socketCli.isOpen()) {
try {
socketCli.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(5);
for (int i = 1; i <= 1000; i ) {
final int index = i;
executor.submit(new Runnable() {
@Override
public void run() {
try {
SocketClient cli = new SocketClient("127.0.0.1", 8088);
cli.send("Hello world, No." + String.format("%04d", index));
cli.close();
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
}
服务器端:
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.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
public class SocketServer {
private Selector selector;
private ServerSocketChannel socketSvr;
private boolean flag = true;
public SocketServer(int port) {
try {
System.out.println("Server start...");
socketSvr = ServerSocketChannel.open();
socketSvr.socket().setReuseAddress(true);
socketSvr.socket().bind(new InetSocketAddress(port), 1024); // 绑定端口
socketSvr.configureBlocking(false); // 非阻塞模式
selector = Selector.open();
socketSvr.register(selector, SelectionKey.OP_ACCEPT); // 注册接收就绪
} catch (IOException e) {
e.printStackTrace();
try {
if (socketSvr != null && socketSvr.isOpen()) {
socketSvr.close();
}
} catch (IOException e1) {
e1.printStackTrace();
}
try {
if (selector != null && selector.isOpen()) {
selector.close();
}
} catch (IOException e1) {
e1.printStackTrace();
}
}
}
public void accept() {
try {
while (flag) {
selector.select(1000);
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
iterator.remove();
try {
if (selectionKey.isValid()) {
if (selectionKey.isAcceptable()) {
ServerSocketChannel socketSvrChannel = (ServerSocketChannel) selectionKey.channel();
SocketChannel socketChannel = socketSvrChannel.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
}
if (selectionKey.isReadable()) {
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
ByteBuffer byteBufferRead = ByteBuffer.allocate(1024);
int readBytes = 0;
readBytes = socketChannel.read(byteBufferRead);
if (readBytes > 0) {
byteBufferRead.flip();
byte[] bytesRead = new byte[byteBufferRead.remaining()];
byteBufferRead.get(bytesRead);
System.out.println("from CLI : " + new String(bytesRead, "UTF-8"));
socketChannel.register(selector, SelectionKey.OP_WRITE);
}
}
if (selectionKey.isWritable()) {
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
byte[] bytesWrite = "SESSION ENDING...".getBytes();
ByteBuffer byteBufferWrite = ByteBuffer.allocate(bytesWrite.length);
byteBufferWrite.put(bytesWrite);
byteBufferWrite.flip();
socketChannel.write(byteBufferWrite);
// 取消注册,防止停留在Writable状态导致重复写入消息
selectionKey.cancel();
}
}
} catch (IOException e) {
e.printStackTrace();
selectionKey.cancel();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new SocketServer(8088).accept();
}
}