目录
下一篇:尚硅谷Netty系列学习笔记九《NIO搭建简易聊天室》
前言:
结合之前的知识,深入理解下NIO非阻塞的网络编程原理,并写一个NIO的demo,demo包含了服务端和客户端
逻辑流程
1.创建NIO服务器通道ServerSocketChannel,监听端口8888,并设置为非阻塞模式,
2.创建Selector实例,并把ServerSocketChannel实例注册到Selector实例上,并监听连接事件
3.客户端连接,形成事件被Selector实例感知到后,通过ServerSocketChannel实例来建立连接,并给连接的客户端
创建一个SocketChannel。
4.把给客户端创建的的SocketChannel实例同样注册到Selector实例上,监听读事件
5.客户端发送消息,被服务器端的Selector感知到,通过获取SelectionKey来获取对应事件的SocketChannel,并把通道数据读到缓冲区,打印到控制台!
逻辑图
代码
服务器代码示例
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;
/**
* NioServer
*/
public class NioServer {
public static void main(String[] args) throws IOException {
//创建 ServerSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//绑定启动端口并启动
serverSocketChannel.bind(new InetSocketAddress(8888));
//设置为非阻塞
serverSocketChannel.configureBlocking(false);
//创建选择器
Selector selector = Selector.open();
//将 serverSocketChannel 注册到选择器上 关心的事件为 OP_ACCEPT(连接事件)
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true) {
//这离我们等待1秒,如果没有事件发生,就返回 。也可以使用 selectNow() : 没有事件立刻返回。
int select = selector.select(1000);
if (select == 0) {
System.out.println("没有事件发生");
continue;
}
//selectedKeys 返回关注事件的集合
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = keys.iterator();
//遍历
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
if (key.isAcceptable()) {
//发生 OP_ACCEPT(连接事件)
System.out.println("有一个客户端连接了");
//通过 serverSocketChannel创建 SocketChannel
SocketChannel socketChannel = serverSocketChannel.accept();
//设置为非阻塞,否则不能注册到选择器中
socketChannel.configureBlocking(false);
//注册到 选择器中,关心的事件为 OP_READ(读),另外注册时绑定了一个 ByteBuffer 后续可直接 key.attachment() 获取该缓冲区对象
socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
}else if (key.isReadable()) {
//发生 OP_READ (读事件)
SocketChannel channel = (SocketChannel) key.channel();
ByteBuffer byteBuffer = (ByteBuffer) key.attachment();
channel.read(byteBuffer);
System.out.println("客户端说:" + new String(byteBuffer.array()));
}
//手动从集合中删除 selectionKey
keyIterator.remove();
}
}
}
}
客户端代码示例
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
/**
* NioClient
*/
public class NioClient {
public static void main(String[] args) throws IOException {
//创建一个客户端的SocketChannel
SocketChannel socketChannel = SocketChannel.open();
//设置非阻塞
socketChannel.configureBlocking(false);
//创建一个 InetSocketAddress 用于连接
InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 8888);
//socketChannel.connect 连接到服务器,连接失败,会返回false,由于是非阻塞的,并且连接也需要时间,这里并不一定会立马连接成功
if (!socketChannel.connect(inetSocketAddress)) {
//finishConnect方法 检查连接是否成功,不成功可以先做别的事
while (!socketChannel.finishConnect()) {
System.out.println("连接需要时间,可以做其他的事情");
}
}
String str = "hello world";
//ByteBuffer.wrap 将字节数组包装到缓冲区中。字节数组多大,创建的缓冲区就多大,主要是使用方便!
ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes());
socketChannel.write(byteBuffer);
//让客户端停在这
System.in.read();
}
}
结语
先启动服务器,再启动客户端,控制台打印如下: