NIO-Selector 网络编程

这篇具有很好参考价值的文章主要介绍了NIO-Selector 网络编程。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

一、阻塞 & 非阻塞

1、阻塞

2、非阻塞

二、selector

1、连接和读取

2、处理客户端断开

3、处理消息的边界

4、ByteBuffer大小分配

三、多线程优化

四、NIO vs BIO

1、stream vs channnel

2、IO模型

阻塞IO

非阻塞IO

多路复用

异步IO模型


一、阻塞 & 非阻塞

1、阻塞

服务器端的代码

NIO-Selector 网络编程,netty,网络,java,netty,NIO

然后创建客户端,直接连服务器端的代码就可以了

NIO-Selector 网络编程,netty,网络,java,netty,NIO

运行发现服务端线程执行到accept这个方法后就停止运行了,这个方法是阻塞的,要等连接建立完成之后才能继续运行;建立完连接后走到read方法又会阻塞等待读入数据,读到数据之后才能恢复运行。

当有多个客户端的时候,服务端在执行到一个客户端的堵塞方法的时候会一直等待,其他客户端就不会被处理。 

2、非阻塞

我们可以把ServerSocoketChannel和SocketChannel改成非阻塞模式,这样如果没有连接直接返回null,没有读取到数据就返回0,不会去堵塞等待了。

NIO-Selector 网络编程,netty,网络,java,netty,NIO

但是这样也会有缺点,因为线程是一直循环检查有没有链接和数据的,如果一直没有连接和数据来,线程依然会不断循环,太过繁忙了,所以这种非阻塞模式在开发中也不常用。

二、selector

为了解决上面非堵塞的不断循环问题,我们就引入selector,接下里看看selector的使用

1、连接和读取

用的时候我们要先调用静态open方法创建Selector对象,然后来管理这些channel,channel调用register方法注册到selector,返回值是个SelectionKey对象, SelectionKey就是将来事件发生后,通过它可以知道事件是哪个channel的事件,然后调用interstOps设置对哪个事件感兴趣

channel事件有4中类型:

  • accept-有连接请求的时候触发
  • connect-客户端连接建立后触发
  • read-数据可读时
  • write-可写事件

然后监听的时候用select()方法,没有时间发生的时候线程堵塞,有事件才执行,然后遍历集合调用selectedKey方法处理事件。

NIO-Selector 网络编程,netty,网络,java,netty,NIO

如果我们遍历出来不对事件处理,那么下次遍历的时候还会有,也就是说如果有未处理事件,那么是不会阻塞的,如果不想处理的话,我们可以用cannel()方法来取消处理

socket处理的时候要想不去不断循环读取,那么也要交给selector管理,所以这个socketChannel也要调用register方法注册到selector上,还有感兴趣关注的事件

这里要把监听到的事件类型分开处理

NIO-Selector 网络编程,netty,网络,java,netty,NIO

这个代码在运行的时候缺空指针了,为什么呢?

selector的集合和selectedKeys集合中每次监听事件中的内容执行完是不会自己删除的,所以我们第一次执行完第二次遍历到有元素执行,建立连接的时候却没有需要建立的返回null,这个时候就抛出空指针了,我们每次执行完一个事件就得手动从容器删除

NIO-Selector 网络编程,netty,网络,java,netty,NIO

NIO-Selector 网络编程,netty,网络,java,netty,NIO

2、处理客户端断开

我们客户端和服务器连接之后,如果客户端退出会抛出异常,如果我们直接try catch去抓的话会循环,因为客户端关闭会引发read事假,这个事假一直不处理就会一直执行,所以我们必须在catch里面去把key调用cancel()方法取消处理事件

NIO-Selector 网络编程,netty,网络,java,netty,NIO

但是如果是正常断开呢,还是会循环,因为正常断开执行不到catch语句不会cancel,就会一直循环处理close这个read事件,所以我们要用read的返回值来区分是不是close指令,是就取消处理掉

NIO-Selector 网络编程,netty,网络,java,netty,NIO

3、处理消息的边界

看看下面这种边界问题:

NIO-Selector 网络编程,netty,网络,java,netty,NIO

我们用分割符的方法来解决边界的问题,遇到分隔符就说明读取结束了,我们就分割成一个消息

如果我们实际的内容超过了byteBuffer,一次没有读取完不会报错,第二个会把剩下的部分发过来会当成一个完整的消息处理,所以当空间不够的时候,我们需要扩容,而且我们不能把byteBuffer做为局部变量,要当共享的第一个和第二次共用一个byteBuffer。扩容我们是重新搞个更大的buffer2,然后第一次复制过去,第二次的直接读过去

NIO-Selector 网络编程,netty,网络,java,netty,NIO

1、我们先修正ByteBuffer不能为局部变量,但是我们不能直接放最外层,因为如果放最外层就变成多个socketChannel公用一个ByteBuffer,这样就乱了,应该每个channel都有自己的byteBuffer,这里就要用到attachment附件,我们可以在注册的时候把buffer当成附件(Object)一起注册为SelectionKey里面。后面要用的时候直接用key调用attachment方法就能获取到buffer

NIO-Selector 网络编程,netty,网络,java,netty,NIO

 2、扩容的时候就是比较如果压缩后的长度和限制最大的长度相等,说明这个消息超过了要扩容,压缩就是每次读取完一个分割符后都要进行的,这个压缩方法就是把因为读取的去除

NIO-Selector 网络编程,netty,网络,java,netty,NIO

4、ByteBuffer大小分配

每个channel都需要记录可能被分割的消息,因为byteBuffer不能被多个channel共同使用,因此需要每个channel维护一个独立的byteBuffer

ByteBuffer不能太大,太大的话很多连接就需要很大内存了,因此需要设计大小可变的byteBuffer。一种思路是首先分配一个小buffer,比如4k,如果不够再扩容为8k,用新的替换掉旧的,优点是消息连续容易处理,缺点是数据拷贝耗性能。另一个思路是多个数组组成buffer,一个数组不够,把多出来的内容写入新的数组,与前面的区别是消息存储不连续解析复杂,优点是避免了拷贝引起的性能损耗。

三、多线程优化

现在的处理都是单线程的,没有充分利用多核cpu,

我们可以向下图这样优化,分为boss好worker,boss负责建立连接,而worker负责读写操作,如果连接了,可以直接去worker读,worker里面有一个selector,线程数可以很多很多,不可能一直建立很多worker,所以一个worker可以管理多个channel,多个thread,相当于把任务分担了。

NIO-Selector 网络编程,netty,网络,java,netty,NIO

NIO-Selector 网络编程,netty,网络,java,netty,NIO

问题: 

执行我们现在是worker-0不断循环执行select方法和boss的register可能会有问题,因为他们是同一个selector管理,又是不同线程的,如果上面执行的selector执行select方法会阻塞,所以下面register就不能执行。

NIO-Selector 网络编程,netty,网络,java,netty,NIO

我们可以把上面的worker.registery方法移动到sc.register上一行,这样就有可能会先执行到register,再执行worker.registery()里面的select方法,register不会阻塞直接执行完这样就不会有问题了,因为他们是两个线程,所以可能是先执行的register,执行完又会停留到select方法上阻塞,这个时候如果来了个新的客户端,所以肯定阻塞又出现了那个问题

解决:

我们要想一个办法,参考netty的解决办法,让他们在同一个线程内执行,这样就可以管理执行顺序了。

我们在搞个队列,然后注册的方法里像队列中添加一个注册的任务

NIO-Selector 网络编程,netty,网络,java,netty,NIO NIO-Selector 网络编程,netty,网络,java,netty,NIO

这样我们就能保证是worker-0在执行任务的时候,就会去注册了,就能保证在worker-0来执行这两个事件了,实现两个线程的事件用一个线程执行,这样就可以控制顺序。最后要注意,添加完事件之后要唤醒selector因为那边的selector是一直阻塞状态的

回顾下整体流程:

我们先接收连接,调用worker进行register去初始化,如果是第一次就会把selector线程都创建好,如果是第二次,都会向队列加入任务,这个worker-0的run进来是select就是阻塞住如果没有的话,然后队列加入完任务就会唤醒这个阻塞的worker执行任务,继续他执行完又select没任务阻塞了。

四、NIO vs BIO

1、stream vs channnel

  • stream不会自动缓冲数据,channel会利用系统提供的发送缓冲区,接收缓冲区(更底层)
  • stream仅支持阻塞API,channel同时支持阻塞、非阻塞API,网络channel可配合selector实现多路复用
  • 二者均为全双工,即读写可以同时进行

2、IO模型

当调用channel的read方法或stream的read后,会由用户态切换至操作系统内核态来完成真正的数据读取,而读取又分为两个阶段:等待数据阶段复制阶段

阻塞IO

当调用read切换内核态后,如果没有数据过来,就会等待数据,等数据到了之后处理好了,复制数据完成之后才切换回用户态,这个整个过程用户线程是阻塞的

NIO-Selector 网络编程,netty,网络,java,netty,NIO

非阻塞IO

用户线程会一直循环切换内核态看看有没有数据,没有就立即返回,然后继续转化,用户线程始终在运行,这就算非阻塞IO,但是正在有数据复制数据的时候还是阻塞的(这种切换太频繁了,可能会影响系统性能)

NIO-Selector 网络编程,netty,网络,java,netty,NIO

多路复用

多路复用一上来不是调用read,而是用select等待事件(这个是阻塞的),如果有内核态会告诉他,这个时候再切换过去复制数据(复制的时候还是需要阻塞) 

NIO-Selector 网络编程,netty,网络,java,netty,NIO

他们都是阻塞,那比阻塞的优势在哪里呢?看看下面的图就知道了,阻塞IO的时候,如果当read在阻塞等待的时候,其他还有线程要执行别的操作,他必须等待这个read阻塞完成全部完成才执行;而多路复用selector可以检测多个事件,什么事件都可以触发唤醒他运行,也就是他阻塞的时候等的是一批事件不是一个事件。

NIO-Selector 网络编程,netty,网络,java,netty,NIO

异步IO模型

  • 同步:线程自己去获取结果(一个线程)
  • 异步:线程自己不去获取结果,而是由其他线程送结果(至少两个线程)

我们先分析一下上面三种是同步还是异步

  • 阻塞IO就是同步的,是用户线程自己发起read,也是自己阻塞等待接受accept的,所以是同步的,所以也叫同步阻塞IO
  • 非阻塞IO也是同步的,他也是线程自己发送自己接受,只不过他是不断循环去发送,同步非阻塞
  • 多路复用也是同步的,他也是自己发送select之类的等待阻塞,有事件了唤醒自己去执行,还是自己发自己阻塞自己做,也是同步的

异步阻塞是怎么样的呢?

他是这样的,客户端线程read后,直接返回一个回调函数和参数,然后会异步启动线程2去等待数据和复制数据,复制完后用回调方法通知结果。 所以也这种就是非阻塞的,请求完直接返回了,所以也叫异步非阻塞

NIO-Selector 网络编程,netty,网络,java,netty,NIO文章来源地址https://www.toymoban.com/news/detail-521342.html

到了这里,关于NIO-Selector 网络编程的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Netty专题】【网络编程】从OSI、TCP/IP网络模型开始到BIO、NIO(Netty前置知识)

    我是有点怕网络编程的,总有点【谈网色变】的感觉。为了让自己不再【谈网色变】,所以我想过系统学习一下,然后再做个笔记这样,加深一下理解。但是真要系统学习,其实还是要花费不少时间的,所以这里也只是简单的,尽可能地覆盖一下,梳理一些我认为比较迫切需

    2024年02月06日
    浏览(62)
  • java网络编程——NIO架构

    目录 1.什么是NIO 2.NIO结构 3.基于NIO下的聊天系统实现 4.Netty NIO:java non-blocking IO,同步非阻塞IO。 BIO是阻塞IO,即每一个事件都需要分配一个进程给他,如果客户端没有连接上,则一直阻塞等待。 而NIO,异步 I/O 是一种没有阻塞地读写数据的方法:该架构下我们可以注册对特定

    2023年04月26日
    浏览(56)
  • 快速入门java网络编程基础------Nio

    哔哩哔哩黑马程序员 netty实战视频 NIO(New I/O)是Java中提供的一种基于通道和缓冲区的I/O(Input/Output)模型。它是相对于传统的IO(InputStream和OutputStream)模型而言的新型I/O模型。NIO的主要特点包括: 1.通道与缓冲区: 2.NIO引入了通道(Channel)和缓冲区(Buffer)的概念。通道

    2024年01月20日
    浏览(55)
  • Java网络编程-深入理解BIO、NIO

    BIO BIO 为 Blocked-IO(阻塞 IO),在 JDK1.4 之前建立网络连接时,只能使用 BIO 使用 BIO 时,服务端会对客户端的每个请求都建立一个线程进行处理,客户端向服务端发送请求后,先咨询服务端是否有线程响应,如果没有就会等待或者被拒绝 BIO 基本使用代码: 服务端: 客户端:

    2024年02月04日
    浏览(40)
  • BIO、NIO、IO多路复用模型详细介绍&Java NIO 网络编程

    上文介绍了网络编程的基础知识,并基于 Java 编写了 BIO 的网络编程。我们知道 BIO 模型是存在巨大问题的,比如 C10K 问题,其本质就是因其阻塞原因,导致如果想要承受更多的请求就必须有足够多的线程,但是足够多的线程会带来内存占用问题、CPU上下文切换带来的性能问题

    2024年02月14日
    浏览(48)
  • Java网络编程----通过实现简易聊天工具来聊聊NIO

    前文我们说过了BIO,今天我们聊聊NIO。 NIO 是什么?NIO官方解释它为 New lO ,由于其特性我们也称之为,Non-Blocking IO。这是jdk1.4之后新增的一套IO标准。 为什么要用NIO呢? 我们再简单回顾下BIO: 阻塞式IO,原理很简单,其实就是多个端点与服务端进行通信时,每个客户端有一个

    2024年02月05日
    浏览(59)
  • Netty实战专栏 | Java网络编程深入解析

    ✅作者简介:大家好,我是Leo,热爱Java后端开发者,一个想要与大家共同进步的男人😉😉 🍎个人主页:Leo的博客 💞当前专栏: Netty实战专栏 ✨特色专栏: MySQL学习 🥭本文内容:Netty实战专栏 | Java网络编程深入解析 🖥️个人小站 :个人博客,欢迎大家访问 📚个人知识

    2024年02月06日
    浏览(54)
  • NIO基础 - 网络编程

    non-blocking io 非阻塞 IO 1.1 Channel Buffer channel 有一点类似于 stream,它就是读写数据的 双向通道 ,可以从 channel 将数据读入 buffer,也可以将 buffer 的数据写入 channel,而之前的 stream 要么是输入,要么是输出,channel 比 stream 更为底层 常见的 Channel 有 FileChannel DatagramChannel SocketCh

    2024年02月02日
    浏览(40)
  • 10.NIO 网络编程应用实例-群聊系统

    需求:进一步理解 NIO 非阻塞网络编程机制,实现多人群聊 编写一个 NIO 群聊系统,实现客户端与客户端的通信需求(非阻塞) 服务器端:可以监测用户上线,离线,并实现消息转发功能 客户端:通过 channel 可以无阻塞发送消息给其它所有客户端用户,同时可以接受其它客户端用

    2024年02月15日
    浏览(43)
  • 使用Netty实现Socket网络编程

    ** ** Netty支持多种网络通信模型,包括传统的阻塞I/O、非阻塞I/O、多路复用I/O和异步I/O。其中,非阻塞I/O和多路复用I/O是Netty的核心特性。 非阻塞I/O :Netty通过使用Java的NIO(New I/O)库,实现了非阻塞的I/O操作。这意味着当一个操作正在进行时,不会阻塞线程,线程可以继续处

    2024年01月16日
    浏览(48)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包