天天看点

BIO和NIO学习笔记及比较BIO和NIO

BIO和NIO

1.BIO

1.1概念

BIO(blocking I/O):同步且阻塞。服务器实现模式为一个连接启动一个线程,当客户端有连接请求时服务器就为其启动一个线程去处理。

1.2单线程演示

1.编写服务器端代码,监听8080端口,同时接收客户端发来的信息,启动服务端。服务器正在等待连接。

BIO和NIO学习笔记及比较BIO和NIO

2.编写客户端代码,绑定本机的8080端口,建立socket连接,发送数据,启动客户端。

BIO和NIO学习笔记及比较BIO和NIO

3.这时返回服务器端发现控制台打印内容增加,服务器端收到了连接,并且接收到数据。此时服务端还没有结束,在等待下一次连接。

BIO和NIO学习笔记及比较BIO和NIO

1.3客户端消息扩展

​ 刚才演示了客户端直接发送信息给服务器,现在手动输入信息,看看服务器是怎样响应的。

1.只需要修改客户端代码即可

BIO和NIO学习笔记及比较BIO和NIO

2.观察服务器端,接收到了新的连接请求,但是一直在等待接收服务器发来的数据

BIO和NIO学习笔记及比较BIO和NIO

3.此时,我们在客户端输入数据,发现服务器端也有了响应。

BIO和NIO学习笔记及比较BIO和NIO

总结:通过测试使用BIO,在服务器端启动到客户端接收到数据的过程中,会有2次阻塞的过程。第一次是等待连接,第二次是等待数据。

1.4多线程演示

​ 通过单线程的演示,发现确实建立了连接,也接收到了客户端发来的信息,但是如果再等待数据时被阻塞了,是无法获取到第二个连接的,同时实际的生产环境客户连接不可能只有一个,下面使用多线程的方式,使服务器端接收多个客户请求。

1.修改服务器端代码,每次接收到连接后创建一个线程去处理。

BIO和NIO学习笔记及比较BIO和NIO

2.修改客户端写入的信息,启动多个客户端。

BIO和NIO学习笔记及比较BIO和NIO

3.发现服务器端接收到了每一个客户端的请求和信息。

BIO和NIO学习笔记及比较BIO和NIO

总结:这样做确实可以接收到多个客户端发来的信息,但是也存在一些问题。我们为每一个请求都创建一个线程是非常消耗资源的,对服务器压力过大。使用单线程不能解决实际的客户需要,使用多线程很大程度上又会给服务器带来过高的负荷。所以才有了NIO。

2.NIO

2.1概念

NIO(non-blocking I/O):同步非阻塞。服务器实现模式为一个请求一个线程,客户端发送的连接请求都会注册到多路复用器上,轮询到连接有I/O请求时才启动一个线程去处理。

2.2模拟NIO处理请求

2.2.1使等待连接和等待数据时不阻塞

1.BIO存在的问题是如果当前请求被阻塞了,下一个请求将发不出去,所以我们使其不进入阻塞状态。

BIO和NIO学习笔记及比较BIO和NIO

总结:可以看到服务器虽然在接收消息是不会阻塞,但是会重新建立起一个连接信息,如果用户没有输入完成的话,那么服务器端是接收不到客户端请求的,就会造成客户端请求的丢失。

2.2.2缓存socket,轮询检查数据是否准备好

1.实现一个list,轮询访问,数据准备好之后就进行访问

BIO和NIO学习笔记及比较BIO和NIO

2.启动客户端(和之前保持一致),服务器端就会接收到相关的信息。

BIO和NIO学习笔记及比较BIO和NIO

总结:使用轮询解决的话虽然不会出现丢失请求的现象,但是遍历List的时间复杂度是o(n),真实的线上环境,可能是百万千万级别的数据,如果也使用list的话,估计很快就崩了,肯定也是不合适的

2.3NIO实际的解决方案

在上述两种虽然使用了单线程去解决了多方请求的问题,但是并不够高效和高可用。在真实的NIO中,轮询的操作是由操作系统来完成的,有select()函数(Linux系统中为epoll()函数),主动去感知有数据的socket。

2.3.1 select()函数

select()函数:将多个请求备份在内核空间中,有内核来判断是否准备好数据,来避免轮询操作出现的上下文切换。需要注意select()是一个阻塞函数,如果没有查询到有数据请求的话,会一直阻塞;如果有一个或多个请求准备好的话,会将这些请求的位置返回给调用者,遍历查看请求的数据。

缺点:底层存储依赖bitmap,处理请求上限为1024;文件描述符是会有置位的,当置位发生变化时,需要重新赋值;内存拷贝在底层也是对服务器的一种压力;返回值后,还是会通过遍历去获取。

2.3.2 epoll()函数

epoll()函数是一种多路IO复用的函数。首先他的文件描述符共享在用户和内核之中,减少了拷贝对资源的占用。当epoll()函数发现某个请求存在数据时,会进行一个重排操作,将所有有数据的放在最前面的位置,然后返回。上层程序只需要遍历这些有数据的请求即可。

3.BIO、NIO使用场景

BIO适用于一些连接数较小且比较固定的架构,这种方式对服务器要求较高。

NIO适用于连接数目多且连接较短的架构,比如聊天服务器,编程较复杂,从JDK1.4之后开始支持。