通过一段简单的代码来了解一下java NIO编程。需求很简单,就是客户端发送一条消息至服务端,服务端在返回一条消息就行。
首先编写服务端代码。
public class NIOServer {
public static void main(String[] args) throws IOException {
//开启一个多路I/O复用器
Selector selector = Selector.open();
new Thread(() -> {
try {
//开启一个服务器socket,该socket的唯一作用是接收客户端连接
ServerSocketChannel socketChannel = ServerSocketChannel.open();
//将接收连接这个socket设置为阻塞模式
socketChannel.configureBlocking(true);
//绑定端口
socketChannel.bind(new InetSocketAddress(8000));
while (true) {
//因为设置成了阻塞模式,所以只有当连接进来时,代码才会往下走,不然会阻塞在这里
SocketChannel channel = socketChannel.accept();
//这个channel为服务端与客户端的连接,既然是用NIO,这个地方就设置成非阻塞模式
channel.configureBlocking(false);
//将新进来的连接注册到复用器上,并设置关注的事件为读事件
channel.register(selector, SelectionKey.OP_READ);
}
} catch (IOException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
//分配两个内存缓冲区,分别为接收和发送事件使用
ByteBuffer receiveBuf = ByteBuffer.allocate(1024);
ByteBuffer sendBuf = ByteBuffer.allocate(1024);
try {
while (true) {
//查询复用器上是否有事件发生
if (selector.select(1000) > 0) {
//获取发生事件的key
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iterator = keys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
//获取key的channel
SocketChannel channel = (SocketChannel) key.channel();
try {
if (key.isValid()) {
//如果该key关注的是读事件
if (key.isReadable()) {
//判断是否有数据可读,如果read为-1的话,就是客户端发送的断开连接的指令
int read = channel.read(receiveBuf);
if (read == -1) {
channel.close();
continue;
}
//读取客户端发送的消息
receiveBuf.flip();
System.out.println(Charset.defaultCharset().newDecoder().decode(receiveBuf).toString());
receiveBuf.clear();
//返回客户端的消息
sendBuf.put("this is server".getBytes());
sendBuf.flip();
channel.write(sendBuf);
sendBuf.clear();
}
//...通常这边还会有写事件或连接事件等等
} else {
channel.close();
}
} finally {
//处理完成后需要将该key从迭代器移除,不然下次会重复处理
iterator.remove();
}
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}).start();
}
}
其次编写客户端代码
public class NioClient {
public static void main(String[] args) throws IOException {
//开启一个客户端socket,并连接至服务端
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress(8000));
//往服务端写入数据
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
byteBuffer.put("this is client".getBytes());
byteBuffer.flip();
socketChannel.write(byteBuffer);
byteBuffer.clear();
//从客户端读取数据并展示
socketChannel.read(byteBuffer);
byteBuffer.flip();
System.out.println(Charset.defaultCharset().newDecoder().decode(byteBuffer).toString());
//关闭连接
socketChannel.close();
}
}
NIO编程的基础组件在上述代码中基本都展示了。配合注释应该很容易理解,我们主要要了解几个关键类的作用。
1、 Selector 多路选择复用器,一个Selector可以注册多个SocketChannel,每当SocketChannel发生我们感兴趣的事件的时候,Selector就可以将他们挑选出来。这样的话即便一万个连接中只有一个接收到了消息,我们也可以很轻易的找出来。
2、 ServerSocketChannel 这个类的主要作用就是接收客户端的连接,并生成SocketChannel
3、 SocketChannel 这个类就是负责服务端和客户端的通信了。每个连接都会生成一个SocketChannel,我们可以通过他来接收来自客户端的消息,并返回消息给客户端。
在了解的基本的java API之后,下一步就开始学习Netty的源代码。