传统的BIO编程
传统的server/client模型,服务器端提供绑定的端口号和ip地址,客户端通过连接操作向服务器监听的地址发起连接请求,通过三次握手进行连接,如果连接成功,双方可以通过socket进行通信。
一请求一应答通信模型:BIO由独立的Acceptor线程负责监听客户端的连接请求,接收到请求后为每个客户创建一个新的线程进行链路处理,处理完成后返回应答给客户端,线程销毁。
缺点在于:线程数多了之后,系统的性能下降,可能会发生线程堆栈溢出以及创建新线程失败的问题。
server.accept方法:没有客户端接入就阻塞在这。
伪异步IO编程
通过线程池来控制客户端的请求连入。
可以设置线程池的最大线程数和任务队列的大小。
资源占用是可控的。
缺点:当调用outputstream的write方法写输出流,它会被阻塞,直到所有要写的字节全部写入完成,或发生异常。如果接收方处理数据的速度过慢,那么发送方的TCP的窗口大小就会不断变小,最终变成0,导致write操作被阻塞。
NIO编程
NIO提供socketchannel和serversocketchannel两种套接字通道。
这两种通道都支持阻塞和非阻塞两种模式。
NIO在java代码的层面实现高效IO,他能在不使用本机代码就可以利用低级优化,不需要使用JNI来操作操作系统的资源来调用本机代码。
1、缓冲区buffer:
buffer是一个对象,包含一些要写入或者要读出的数据。
可以将数据直接写入或者将数据直接读到stream对象里。
NIO库中,所有数据都是用缓冲区处理的。
缓冲区实质上是一个数组,还提供了对数据结构化的访问以及维护读写位置等信息。
最常用的缓冲区是Bytebuffer,提供一组功能用于操作byte数组。
不同的buffer只是处理的数据类型不一样。
2、通道channel
通道是双向的,流只有一个方向。
channel分为用于网络读写的selectablechannel和文件操作的filechannel。
serversocketchannel和socketchannel都是selectablechannel的子类。
3、多路复用器selector
多路复用器是用来选择已经就绪的任务的。
会不断轮询注册在他上面的channel(一个selector可以注册同时轮询多个channel),如果channel上发生读和写的事件,那么这个channel就处于准备就绪状态,会被selector轮询出来,然后通过selectionkey能得到就绪的channel的集合,可以进行后续的IO操作。
底层使用了epoll函数。