NIO网络编程

服务端
需要不断循环从selector里拿key

客户端

selectkey需要获取后立马从select里删除,不然会循环拿这个selectkey

阻塞IO模型,非阻塞IO模型,IO多路复用模型,信号IO模型,异步IO模型

阻塞模型:进程发起IO系统调用后,进程被阻塞,转到内核空间处理,整个IO处理完毕后返回进程。操作成功则进程获取到数据。

1、典型应用:阻塞socket、Java BIO;

2、特点:

进程阻塞挂起不消耗CPU资源,及时响应每个操作;
实现难度低、开发应用较容易;
适用并发量小的网络应用开发;
不适用并发量大的应用:因为一个请求IO会阻塞进程,所以,得为每请求分配一个处理进程(线程)以及时响应,系统开销大。

非阻塞模型:进程发起IO系统调用后,如果内核缓冲区没有数据,需要到IO设备中读取,进程返回一个错误而不会被阻塞;进程发起IO系统调用后,如果内核缓冲区有数据,内核就会把数据返回进程。

对于上面的阻塞IO模型来说,内核数据没准备好需要进程阻塞的时候,就返回一个错误,以使得进程不被阻塞。搜索Java知音公众号,回复“后端面试”,送你一份Java面试题宝典

1、典型应用:socket是非阻塞的方式(设置为NONBLOCK)

2、特点:

进程轮询(重复)调用,消耗CPU的资源;
实现难度低、开发应用相对阻塞IO模式较难;
适用并发量较小、且不需要及时响应的网络应用开发;

IO多路复用:多个的进程的IO可以注册到一个复用器(select)上,然后用一个进程调用该select, select会监听所有注册进来的IO;

如果select没有监听的IO在内核缓冲区都没有可读数据,select调用进程会被阻塞;而当任一IO在内核缓冲区中有可数据时,select调用就会返回;

而后select调用进程可以自己或通知另外的进程(注册进程)来再次发起读取IO,读取内核中准备好的数据。

可以看到,多个进程注册IO后,只有另一个select调用进程被阻塞。

1、典型应用:select、poll、epoll三种方案,nginx都可以选择使用这三个方案;Java NIO;

2、特点:

专一进程解决多个进程IO的阻塞问题,性能好;Reactor模式;
实现、开发应用难度较大;
适用高并发服务应用开发:一个进程(线程)响应多个请求;
3、select、poll、epoll

Linux中IO复用的实现方式主要有select、poll和epoll:
Select:注册IO、阻塞扫描,监听的IO最大连接数不能多于FD_SIZE;
Poll:原理和Select相似,没有数量限制,但IO数量大扫描线性性能下降;
Epoll :事件驱动不阻塞,mmap实现内核与用户空间的消息传递,数量很大,Linux2.6后内核支持;

信号驱动IO模型:当进程发起一个IO操作,会向内核注册一个信号处理函数,然后进程返回不阻塞;当内核数据就绪时会发送一个信号给进程,进程便在信号处理函数中调用IO读取数据。

异步IO:当进程发起一个IO操作,进程返回(不阻塞),但也不能返回果结;内核把整个IO处理完后,会通知进程结果。如果IO操作成功则进程直接获取到数据。

1、典型应用:JAVA7 AIO、高性能服务器应用

2、特点:

不阻塞,数据一步到位;Proactor模式;
需要操作系统的底层支持,LINUX 2.5 版本内核首现,2.6 版本产品的内核标准特性;
实现、开发应用难度大;
非常适合高性能高并发应用;

特点:回调机制,实现、开发应用难度大;

网络断开处理
  • try catch住 selectorKey的获取channel方法以及channel的操作,因为这些操作会在断开连接的时候抛错。断开连接不可避免,所以需要在catch住异常的时候把key从selector中剔除,需要调用key.cancel()
  • 如果是客户端调用close方法正常断开,那么需要在调用read的时候,根据read返回结果判断是否等于-1,来决定是否需要把key从seletor中删除

粘包处理&&消息边界

  1. 自定义分割符,比如\n
  2. 客户端服务端商量好大小
  3. 定义一种结构TLV(type length value)/LTV(length type value):http1.1使用TLV,http2.0使用LTV
优缺点:
  1. 自定义分隔符实现简单,缺点每一个字符都要去判断是否是分隔符,效率低下,如果发送的数据比较长,需要考虑扩容
  2. 客户端与服务的对其大小,缺点是浪费带宽,客户端发送数据不足也得补齐
  3. TLV/LTV 优点是节省带宽,需要多少读多少,缺点是服务器需要提前分配byteBuffer,如果内容过大,影响server吞吐量
扩容的处理

读取不到消息的边界,就需要扩容扩容时需要注意把之前读取的byteBuffer数据放到扩容后的byteBuffer里,可以通过attach 属性将byteBuffer放入socketChanner,这里有个复制问题,后面用零拷贝解决
image

byteBuffer大小分配
  • 将老的数据复制到新的byteBuffer里,需要拷贝,实现简单
  • 将放不下的数据放入新的数组,避免拷贝,实现复杂

多路复用(考点)

参考:https://zhuanlan.zhihu.com/p/115220699
总结就是多路复用实现是用linux提供的selector,poll,epoll,通过文件描述符进行相关事件的注册,然后阻塞等待某些事件的发生或等待超时。

posted @ 2022-05-04 16:46  自律のalive  阅读(41)  评论(0编辑  收藏  举报