JavaNIO底层原理-【NIO】—ServerSocketChannel的缺陷-《Java笔记》

admin 2025-10-19 01:44:48 编程 来源:ZONE.CI 全球网 0 阅读模式

Java【NIO】— ServerSocketChannel 的缺陷 - 图1

阻塞模式

先看服务端方法:

  1. public static void main(String[] args) throws Exception {
  2. ByteBuffer buffer = ByteBuffer.allocate(100);
  3. ServerSocketChannel serverSocket = ServerSocketChannel.open()
  4. serverSocket.bind(new InetSocketAddress(8080));
  5. List<SocketChannel> channels = new ArrayList<>();
  6. while(true) {
  7. SocketChannel sc = serverSocket.accept();
  8. channels.add(sc);
  9. for (SocketChannel asc :channels) {
  10. asc.read(buffer);
  11. buffer.flip();
  12. ByteBufferUtil.debugAll(buffer);
  13. buffer.clear();
  14. }
  15. }
  16. }
  • 首先新建一个 ServerSocketChannel 类,同时绑定 8080 端口
  • 然后 while(true)循环调用 accept()来建立连接,同时将该 SocketChannel 加入到 List 集合中,该集合用来装所有与服务端建立连接的 SocketChannel
  • 最后接受客户端发送来的数据,打印出来

现在来运行下(这里就不写客户端程序了,就用 MAC 的 iTerm 来模拟即可)。需要开 2 个客户端。先打开 client-01,然后发送一条 “hi,i am client-01”【NIO】— ServerSocketChannel 的缺陷 - 图2服务器运行结果:【NIO】— ServerSocketChannel 的缺陷 - 图3服务端准确无误打印出 client-01 发送过来的消息(hi,i am client-01)。这个时候再发一条消息:“hi,i am client-11”,会惊奇地发现,服务端竟然不输出客户端发来的消息。这个时候再启动 client-02,神奇的事情发生了,服务端把 client-01 发送的消息(hi,i am client-11)给打印出来了:【NIO】— ServerSocketChannel 的缺陷 - 图4为什么会出现这种神奇的现象?主要原因就是该 ServerSocketChannel 是阻塞模式,相关方法都会导致线程的阻塞,当 client-01 建立连接,第一次发送消息时,服务端正常打印消息(hi,i am client-01),这时服务端又运行到 accept(),注意这个方法是阻塞方法,如果没有客户端来建立连接,它会一直阻塞在这里,哪怕 client-01 再次发送消息(hi,i am client-11),服务端也不会打印。这时 client-02 与服务端建立连接,服务端就不会阻塞,打印 client-01 第二次发来的消息(hi,i am client-11)。所以,阻塞模式存在如下缺陷

  • 单线程情况下,阻塞方法都会导致线程暂停
    • ServerSocketChannel.accept() 会在没有连接建立时让线程暂停,即使有客户端向服务端发送消息,服务单也接收不到直到有新客户端连接服务端,不再阻塞在accept()方法上。
    • SocketChannel.read() 会在通道中没有数据可读时让线程暂停,即使之后有新客户端向服务端发起连接请求也接受不了,直到读取完毕,不再阻塞在read()方法上

所以在单线程情况,服务端几乎不可能正常工作。那多线程呢?多线程情况下,如果连接数过多,必然会导致 OOM,然后线程的上下文切换也会导致性能低下。

非阻塞模式

上面的阻塞模式几乎导致整个服务端是可能使用的,是可以使用非阻塞模式来避免的。如下

  1. public static void main(String[] args) throws Exception {
  2. ByteBuffer buffer = ByteBuffer.allocate(100);
  3. ServerSocketChannel serverSocket = ServerSocketChannel.open();
  4. serverSocket.bind(new InetSocketAddress(8080));
  5. List<SocketChannel> channels = new ArrayList<>();
  6. while(true) {
  7. // 非阻塞模式
  8. serverSocket.configureBlocking(false);
  9. SocketChannel sc = serverSocket.accept();
  10. if (sc != null){
  11. channels.add(sc);
  12. }
  13. for (SocketChannel asc :channels) {
  14. asc.configureBlocking(false);
  15. int size = asc.read(buffer);
  16. if (size > 0) {
  17. buffer.flip();
  18. ByteBufferUtil.debugAll(buffer);
  19. buffer.clear();
  20. }
  21. }
  22. }
  23. }
  • 通过 ServerSocketChannel.configureBlocking(false) 将 serverSocket 设置为非阻塞模式,这样 serverSocket 在调用 accept()方法时就不会阻塞了,如果没有连接,则会返回 null
  • 通过 SocketChannel..configureBlocking(false) 将 asc 设置为非阻塞模式,这 asc 在调用 read() 方法就不会阻塞了,如果没有可读数据,它则会返回 -1。

非阻塞模式虽然不会影响业务的使用,但由于在 while(true) 循环里面,CPU 会一直处理运行状态,占用和浪费 CPU 资源。所以,采用这种 while(true) 循环的暴力方式根本就不适合业务使用,对于 SocketChannel 而言,希望他只担任一个通道,传传数据的角色即可,不需再有额外的角色了,故而不能放任他们,需要对其进行统一管理,既要有管理器,有连接来了,就告诉你该建立连接了,有要读的数据,我就告诉你可以读数据了,这样 SocketChannel 是不是就很爽了。在 NIO 中,这个管理器称之为 Selector。

以太坊cppgolang区别 编程

以太坊cppgolang区别

以太坊是一种去中心化的开源平台,它采用智能合约技术,旨在构建和运行不受干扰的分布式应用程序。作为目前最受欢迎的区块链平台之一,以太坊提供了多种编程语言的支持,其
progolang 编程

progolang

Go语言(Golang)是由Google开发的一门静态类型编程语言。作为一名专业的Golang开发者,我深知这门语言的优势和特点。在本文中,我将介绍Golang
golangn个发送者 编程

golangn个发送者

Golang是一种开源的编程语言,由Google团队开发,旨在提高程序的并发性和简化软件开发过程。在Go语言中,有时需要向多个接收者发送信息。本文将介绍如何在G
golang技能图谱 编程

golang技能图谱

从互联网行业的快速发展到人工智能技术的日益成熟,各种编程语言也应运而生。而在这众多的编程语言中,Golang(即Go)作为一门强大且高效的开发语言备受关注。Go
评论:0   参与:  6