NIO-Selector 网络编程

news/2024/11/29 23:53:47/

目录

一、阻塞 & 非阻塞

1、阻塞

2、非阻塞

二、selector

1、连接和读取

2、处理客户端断开

3、处理消息的边界

4、ByteBuffer大小分配

三、多线程优化

四、NIO vs BIO

1、stream vs channnel

2、IO模型

阻塞IO

非阻塞IO

多路复用

异步IO模型


一、阻塞 & 非阻塞

1、阻塞

服务器端的代码

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

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

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

2、非阻塞

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

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

二、selector

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

1、连接和读取

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

channel事件有4中类型:

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

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

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

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

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

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

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

2、处理客户端断开

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

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

3、处理消息的边界

看看下面这种边界问题:

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

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

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

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

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,相当于把任务分担了。

问题: 

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

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

解决:

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

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

 

这样我们就能保证是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切换内核态后,如果没有数据过来,就会等待数据,等数据到了之后处理好了,复制数据完成之后才切换回用户态,这个整个过程用户线程是阻塞的

非阻塞IO

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

多路复用

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

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

异步IO模型

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

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

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

异步阻塞是怎么样的呢?

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


http://www.ppmy.cn/news/774190.html

相关文章

[黑科技] 使用Word和Excel自制题库自判断答题系统

这篇文章是LZY老师告诉我的一个方法,如果你需要做题库,并且喜欢电子答题的方法,这篇文章或许会对你有所帮助。反正李老师班级的平均成绩高出其他班级的14分,这就是它的好处,希望这篇文章对我今后的学生有所帮助吧&…

GTC 2017现场直击:以人工智能的名义搞一场黑科技的盛会!

【CSDN记者美国硅谷报道】说巧不巧,在GTC 2017 英伟达首席执行官黄仁勋开始Keynote演讲的前一天,英伟达公布了截至4月30日的2018财年第一财季财报。报告显示,英伟达公司的业绩好于预期,该季度营收为19.37亿美元,去年同…

python黑科技:Python大佬用20行代码带你打造一个微信聊天机器人,真神了~

如何用20行Python代码打造一个微信群聊助手? 1.安装python环境2.安装python的itchat库3.安装itchat库4.Linux5.申请图灵机器人API和key6.编写python程序7.执行程序运行8.大致原理简介 今天要教大家一个黑科技,20行代码实现自己定制的微信群聊助手&#x…

来聊聊WWDC 苹果大会上的那些黑科技

原文链接 来聊聊WWDC苹果大会上的那些黑科技 发布时间:2017-06-09 11:39:23 93人关注 6人参与 当高考碰到WWDC 每年的高考都是6月7,8号,每年的wwdc也是六月那么几天,似乎骄阳似火的6月才是收(收)获(货)的季节。或许苹果的内涵是&a…

[黑科技] WPS通过VB宏函数实现自编号功能

这篇文章主要是作为李老师《算法设计与分析》助教课程中,与她交流,学到的一些基础知识。它主要是讲述Word通过宏函数设置一些操作,比如在Word全文中替换一些符号;再如对Word上角表进行编号,如果删除中间某个值&#xf…

这可能是冬奥会藏得最深的黑科技了

鱼羊 明敏 发自 凹非寺量子位 | 公众号 QbitAI 大家好,我们又来蹭冬奥会热度了(手动狗头)。 毕竟啊,这届冬奥不仅赛事精彩,背后频频曝出的黑科技也让人应接不暇填满了选题小黑板。 从开幕式上的“步步生雪花”&#xf…

世界经论坛狂飚黑科技 直击2017大连达沃斯

来自20多个国家的2000多名各行业专家会集大连,参加200多场论坛,这些论坛的场里场外成了狂飚未来黑科技赛场。这就是我对我正在采访的2017达沃斯全球经济论坛深刻感受。 啊,我好久沒有闻道海风的腥味,我贪婪地呼吸着这让人神怡清醒…

IBM 人工智能黑科技

打开IBM官网,意外发现IBM Research Lab发布了今年的未来5年将发生的五项创新 - The Power of Thinking Big: IBM Researchs "5 in 5"。五项创新均与目前热门的人工智能、纳米科技、IoT物联网、医疗大数据、环境污染检测等息息相关! With AI, …