分别使用 BIO 和 NIO 的实现简易群聊系统
前言
下面是BIO和NIO的原理结构图,可以看出使用BIO时,每个客户端都会独占一个线程,而使用NIO时,一个Selector选择器独占一个线程,一个选择器下面可以连接多个客户端,然后Selector开始轮询下面的每一个客户端,这就提高了线程的复用,所以叫非阻塞IO
一、BIO
下面的代码已经完成了一个 BIO 服务端的编写,启动下面的代码后,可以用 cmd 的 telnet 命令来连接到服务端(telnet怎么安装可以自己bing一下哦)。
1 | telnet 127.0.0.1 6666 |
然后回车,就可以输入要发送的文字信息了,有些电脑可能回车以后还需要按一下 Ctrl+J 才能开始输入文字,这时,我们的cmd就变成了客户端,可以向服务端发送信息。
1、代码(含服务端、客户端)
1 | package pers.xuyijie.bio; |
2、演示
二、NIO
下面的代码分为 Server 和 Client,将idea设置成同时启动多个相同类(看我下面的截图),可以启动多个客户端,而且Server有转发功能,收到任意一个客户端发来的消息,都可以把消息转发给其他所有客户端,也就是说任意一个客户端发送的消息,其他客户端和服务端都可以看到,可以作为一个简易的群聊系统。
首先Edit Configurations
然后选择要启动多个的类,我这里是 NIOClient,把Allow multiple instance勾上,确定就可以了
1、代码
服务端
NIOServer.java,这个是服务端,只需要启动一个
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
package pers.xuyijie.nio.groupchat;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
/**
* @author 徐一杰
* @date 2022/8/23 20:10
* @description
*/
public class NIOServer {
private Selector selector;
private ServerSocketChannel serverSocketChannel;
private static final int PORT = 6667;
/**
* 构造器完成初始化工作
*/
public NIOServer(){
try {
//得到选择器
selector = Selector.open();
//开启通道
serverSocketChannel = ServerSocketChannel.open();
//绑定端口
serverSocketChannel.socket().bind(new InetSocketAddress(PORT));
//设置非阻塞
serverSocketChannel.configureBlocking(false);
//把通道注册到选择器
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
} catch (IOException e){
e.printStackTrace();
}
}
/**
* 监听
*/
public void listen(){
try {
//循环处理
while (true){
int count = selector.select(2000);
if (count > 0){
//有事件处理
//遍历得到selectionKey集合
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while (iterator.hasNext()){
SelectionKey selectionKey = iterator.next();
if (selectionKey.isAcceptable()){
SocketChannel socketChannel = serverSocketChannel.accept();
socketChannel.configureBlocking(false);
//将通道注册到选择器
socketChannel.register(selector, SelectionKey.OP_READ);
System.out.println(socketChannel.getRemoteAddress() + "上线了!");
}
if (selectionKey.isReadable()){
readData(selectionKey);
}
//从当前keys中删除,防止重复操作
iterator.remove();
}
}else {
System.out.println("正在监听...");
}
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
}
}
/**
* 读取客户端消息
* @param selectionKey
*/
public void readData(SelectionKey selectionKey) throws IOException {
//得到关联的SocketChannel
SocketChannel socketChannel = null;
try {
socketChannel = (SocketChannel) selectionKey.channel();
//创建buffer
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
int count = socketChannel.read(byteBuffer);
if (count > 0){
//将buffer中的数据转成字符串
String msg = new String(byteBuffer.array());
System.out.println("客户端:" + msg);
//向其他客户端转发消息
sendMsgToOtherClients(msg, socketChannel);
}
} catch (IOException e) {
System.out.println(socketChannel.getRemoteAddress() + "离线了!");
//取消注册
selectionKey.cancel();
//关闭通道
socketChannel.close();
}
}
/**
* 转发消息
* @param msg
* @param socketChannel
* @throws IOException
*/
public void sendMsgToOtherClients(String msg, SocketChannel socketChannel) throws IOException {
System.out.println("服务器转发消息");
//遍历所有注册到选择器上的SocketChannel,并排除自己
for (SelectionKey selectionKey : selector.keys()){
//通过key取出相应的通道
Channel channel = selectionKey.channel();
//排除自己
if (channel instanceof SocketChannel && channel != socketChannel){
SocketChannel socketChannel1 = (SocketChannel) channel;
//将msg存储到buffer
ByteBuffer byteBuffer = ByteBuffer.wrap(msg.getBytes());
//将buffer的数据写入通道
socketChannel1.write(byteBuffer);
}
}
}
public static void main(String[] args) {
//创建一个服务器对象
NIOServer nioServer = new NIOServer();
nioServer.listen();
}
}
1 | package pers.xuyijie.nio.groupchat; |
客户端
NIOClient.java,这时客户端,设置好以后,可以点击多次启动,启动多个
1 | package pers.xuyijie.nio.groupchat; |
2、演示
启动两个文件,先启动 NIOServer.java,再启动 NIOClient.java,NIOClient我启动了两个,因为服务器有转发功能,相当于一个群聊系统.
总结
以上是 JDK 的原生 BIO 和 NIO 的使用 demo,代码的意思在注释里面基本都有写,但是写的不是很详细,因为我也没有完全弄懂,这东西要慢慢自己断点调试来研究,接下来会写 NIO 的框架——Netty