引述
前文说了 BIO 无法克服上连接的问题,那么有什么模式可以支持长连接呢?
这里介绍下 NIO 模型,即同步非阻塞。每当我们去食堂点餐时,点好餐后还得要排队等厨师弄好取餐。在这个过程中,我们得一直在这条队伍里等待,不能上厕所也不能坐下休息。这里就相当于 BIO 模型中,当有一个连接建立后,就得要有一个线程去处理,直到连接处理完毕,并且这个线程在整个过程中都被占用。
于是有人提出了一种思路:我们下单后就给我们一个订单号,并且有个屏幕显示现在哪个订单已经做完了。于是,我们彻底被解放了,我们可以去做其他事情,甚至我们可以晚点取餐(做完手头上的事情再去取餐),且取餐的过程也几乎不用排队。
NIO 的思路也是这样,通过一个容器去记录下新建的连接,然后我们不断循环监听容器里的连接,当连接有数据传回时,我们让一个线程去处理。(处理完毕后就退出执行线程,但这时还并不是断开连接。)
代码
通过 Netty 来模拟 NIO 的过程。
服务端:
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
|
public class NIOServer extends Thread {
LinkedList<SocketChannel> clients = new LinkedList<>(); // 存放连接的容器
public static void main(Stringp[] args) throw IOException {
NIOSocket socket = new NIOSocket();
ServerSocketChannel ss = ServerSocketChannel.open(); // 服务端开启监听,接收客户端
ss.bind(new InetSocketAddress(8080)); // 监听 8080 端口
ss.configureBlocking(false); // 设置非阻塞模式,即每个连接都不会一直占用线程
socket.start(); // 开启这个线程,让他一直循环,判断有没有接收到连接发送的数据
while (true) {
// 接收客户端的连接
SocketChannel client = ss.accept(); // 则个阶段不会阻塞,若没有连接,则返回 null
if (client != null) {
client.configureBlocking(false); // 将客户端的连接也设置为非阻塞
clients.add(client); // 将此连接放入容器中
}
}
}
@Override
public void run() {
ByteBuffer buffer = ByteBuffer.allocateDirect(4096); // 设置读取缓冲区
int i = 0;
while (true) {
if (i == clients.size())
i = 0;
SocketChannel c = clients.get(i++);
try {
int num = c.read(buffer); // -1:未写,0:断开,n:已写
if (num > 0) {
buffer.flip();
byte[] aaa = new byte[buffer.limit()];
buffer.get(aaa);
String b = new String(aaa);
System.out.println(c.socket().getPort() + ":" + b);
buffer.clear();
} else if (num == 0) { // 改连接断开
c.close();
clients.remove(c);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
|
客户端
1
2
3
4
5
6
7
|
public class NIOClient {
public static void main(String[] args) throw IOException {
SocketChannel sc = SocketChannel.open(); // 开启客户端连接
sc.connect(new InetSocketAddress("localhost", 8080)); // 设置服务端地址
System.out.println("等待接收数据");
}
}
|
优势
NIO 的最大优势就是不让一个连接独占线程,通过对每个连接打个标记,当连接需要线程时就给它线程,不需要时就释放线程。并且也因为有标记的存在,即它支持的模式是长连接的模式,每次发送数据是都不需要三次握手连接。