1、NIO总览
2、用NIO实现一个简易聊天室
本例是使用Java NIO实现一个简易聊天室功能,全部代码见下文。
实现NIO的七个步骤已在代码中注释,这是重点,请细读代码。
2.1 服务端
服务端 -> NIOServer.java:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.Channel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;
/**
* @author Administrator
* @date 2021/4/9
* NIO服务器端
*/
public class NIOServer {
public static void main(String[] args) throws IOException {
new NIOServer().start();
}
/**
* 启动
*/
public void start() throws IOException {
// 1、创建Selector
Selector selector = Selector.open();
// 2、通过ServerSocketChannel创建channel通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 3、为channel通道绑定监听端口
serverSocketChannel.bind(new InetSocketAddress(8000));
// 4、设置channel为非阻塞模式
serverSocketChannel.configureBlocking(false);
// 5、将channel注册到selector上,监听连接事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("服务器已启动");
// 6、循环等待新接入的连接
for (; ; ) {
int readyChannels = selector.select();
if (readyChannels == 0) {
continue;
}
// 获取可用channel集合
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
// selectionKey实例
SelectionKey selectionKey = (SelectionKey) iterator.next();
// 重要:移除Set中大的当前selectionKey
iterator.remove();
// 7、根据就绪状态,调用对应的业务处理逻辑。分接入事件和可读事件
if (selectionKey.isAcceptable()) {
acceptHandler(serverSocketChannel, selector);
}
if (selectionKey.isReadable()) {
readHandler(selectionKey, selector);
}
}
}
}
/**
* 接入事件处理器
*/
private void acceptHandler(ServerSocketChannel serverSocketChannel, Selector selector) throws IOException {
// 创建socketChannel
SocketChannel socketChannel = serverSocketChannel.accept();
// 将socketChannel设置为非阻塞模式
socketChannel.configureBlocking(false);
// 将channel注册到selector上,监听可读事件
socketChannel.register(selector, SelectionKey.OP_READ);
// 回复客户端提示信息
socketChannel.write(Charset.forName("UTF-8").encode("你与聊天室其他人不是朋友关系,请注意隐私安全"));
}
/**
* 可读事件处理器
*/
private void readHandler(SelectionKey selectionKey, Selector selector) throws IOException {
// 要从selectionKey中获取到已经就绪的channel
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
// 创建buffer
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
// 循环读取客户端请求信息
String request = "";
while (socketChannel.read(byteBuffer) > 0) {
// 切换buffer为读模式
byteBuffer.flip();
// 读取buffer中的内容
request += Charset.forName("UTF-8").decode(byteBuffer);
}
// 将channel再次注册到selector上,监听它的可读事件
socketChannel.register(selector, SelectionKey.OP_READ);
// 将客户端发送的请求信息,广播给其他客户端
if (request.length() > 0) {
broadCast(selector, socketChannel, request);
}
}
/**
* 广播给其他客户端
*/
private void broadCast(Selector selector, SocketChannel sourceSocketChannel, String request) {
// 获取所有已接入的客户端channel
Set<SelectionKey> selectionKeySet = selector.keys();
// 循环向所有的channel广播信息
selectionKeySet.forEach(selectionKey -> {
Channel targetChannel = selectionKey.channel();
// 剔除发消息的客户端
if (targetChannel instanceof SocketChannel && targetChannel != sourceSocketChannel) {
try {
// 将消息发送到targetChannel客户端
((SocketChannel) targetChannel).write(Charset.forName("UTF-8").encode(request));
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
}
2.2 客户端
客户端 -> NIOClient.java:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Scanner;
/**
* @author Administrator
* @date 2021/4/9
* NIO客户端
*/
public class NIOClient {
/**
* 客户端启动方式:
* 1、直接启动此处的main方法
* 2、单独启动AClient、BClient、CClient中的main方法
*/
public static void main(String[] args) throws IOException {
new NIOClient().start("客户端");
}
public void start(String nickName) throws IOException {
// 连接服务器端
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 8000));
// 新开线程,专门负责来接收服务器端的响应数据
Selector selector = Selector.open();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
new Thread(new NIOClientHandler(selector)).start();
// 向服务器端发送数据
Scanner scanner = new Scanner(System.in);
while (scanner.hasNextLine()) {
String request = scanner.nextLine();
if (request != null && request.length() > 0) {
socketChannel.write(Charset.forName("UTF-8").encode(nickName + " : " + request));
}
}
}
}
客户端数据响应线程类 -> NIOClientHandler.java:
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;
/**
* @author Administrator
* @date 2021/4/9
*/
public class NIOClientHandler implements Runnable {
private Selector selector;
public NIOClientHandler(Selector selector) {
this.selector = selector;
}
@Override
public void run() {
try {
for (; ; ) {
int readyChannels = selector.select();
if (readyChannels == 0) {
continue;
}
// 获取可用的channel集合
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
// selectionKey实例
SelectionKey selectionKey = (SelectionKey) iterator.next();
// 移除Set中的当前selectionKey
iterator.remove();
// 根据就绪状态,调用对应的业务处理方法
if (selectionKey.isReadable()) {
readHandler(selectionKey, selector);
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* 可读事件处理器
*/
private void readHandler(SelectionKey selectionKey, Selector selector) throws IOException {
// 要从selectionKey中获取到已经就绪的channel
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
// 创建buffer
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
// 循环读取服务器端响应数据
String response = "";
while (socketChannel.read(byteBuffer) > 0) {
// 切换buffer为读模式
byteBuffer.flip();
// 读取buffer中的内容
response += Charset.forName("UTF-8").decode(byteBuffer);
}
// 将channel再次注册到selector上,监听可读事件
socketChannel.register(selector, SelectionKey.OP_READ);
// 将服务器端响应信息打印到本地
if (response.length() > 0) {
System.out.println(response);
}
}
}
单独写的3个客户端类
AClient.java:
import java.io.IOException;
/**
* @author Administrator
* @date 2021/4/9
*/
class AClient {
public static void main(String[] args) throws IOException {
new NIOClient().start("AClient");
}
}
BClient.java:
import java.io.IOException;
/**
* @author Administrator
* @date 2021/4/9
*/
class BAClient {
public static void main(String[] args) throws IOException {
new NIOClient().start("BClient");
}
}
CClient.java:
import java.io.IOException;
/**
* @author Administrator
* @date 2021/4/9
*/
class CClient {
public static void main(String[] args) throws IOException {
new NIOClient().start("CClient");
}
}
2.3 测试结果
测试结果:
微信公众号: TechU