一个连接一个线程
1)传统的BIO编程
网络编程的基本模型是C/S模型,即两个进程间的通信;HTTP请求之后,进行域名解析、三次握手建立连接、连接成功之后,套接字进行通信;
传统的就是同步阻塞,双方是输入和输出流同步阻塞通信;
通常由一个独立的Acceptor线程负责监听客户端的连接,接收客户端请求之后为每个客户端建立新线程进行链路处理,通过输出流返回给客户端,然后再进行线程销毁;
缺:扩展性差,针对高并发情况线程数量急剧增加,性能会急剧下降,甚至出现崩溃。
2)伪异步I/O编程
线程池实现1个或多个线程处理N个客户端,底层还是同步阻塞I/O。
线程的新建和销毁的开销得到很大的控制,但是仍然是阻塞。
使用CachedThreadPool线程池,然后使用FixedThreadPool就有效控制了线程的最大数量,保证了系统有限的资源控制,实现N:M的伪异步I/O模型;
限制了线程数凉,如果发生大量并发请求,超出最大限制就只能等待,直到有空闲线程才能被复用。
一个请求一个线程
JDK1.4的java.nio.*包引入,提高速度。基于事件驱动思想来完成的,主要解决并发问题,
NIO提供的与传统BIO模型中的Socket和ServerSocket相对应的SocketChannel和ServerSocketChannel两种不同的套接字通道实现;
优点:更好的维护性,对于高负载、高并发应用,应使用NIO的非阻塞模式来开发。
1 )缓冲区
Buffer是一个对象,包含一些写入或读出数据;
所有数据都是缓冲处理,缓冲区实际是一个数组,提供了对数据结构化访问以及维护读写位置等信息。具体有ByteBuffer、CharBuffer、IntBuffer、Longbuffer\FloatBuffer、DoubleBuffer。
2)通道Channel
不同与流,是双向的,可以实现读写和同时读写。全双工。
Channel主要分为两大类:SelectableChannel:用户网络读写;FileChannel:用于文件操作;
3 )多路复用器Selector
提供选择以及就绪的任务的能力,不断轮询注册在其上的Channel,如果有发生读写,这个Channel就是就绪,被轮询处理,通过SelectionKey可以获取就绪Channel的集合,进行后续的I/O操作。
一个Selector可以轮询多个Channel。
创建NIO服务端主要步骤是:
1)打开ServerSocketChannel监听客户端连接;
2)绑定监听端口,设置连接为非阻塞模式;
3)创建Reactor线程,创建多路复用器并启动线程;
4)将ServerSocketChannel注册到Reactor线程中的Selector上,监听ACCEPT事件;
5) Selector轮询准备就绪的key;
6) Selector监听到新的客户端接入,处理新的接入请求,完成TCP的三次握手;
7)设置客户端链路为非阻塞模式;
8)新接入的客户端连接注册到Reactor线程的Selector上,监听读操作,读取客户端发送网络信息;
9)异步读取客户端消息到缓冲区;
10)对Buffer编解码,处理半包消息,将节码成功的消息封装为Task;
11)将应答消息编为Buffer,调用SocketChannel的write将消息异步发送给客户端;通过Buffer中的hasRemain()方法判断消息是否发送完成。
一个有效请求一个线程
与NIO不同,当进行读写操作时,只需要直接调用API的read或write方法即可,均为异步操作,读的流传入缓冲区,写操作也是将write方法传递的流写入缓冲区,读写异步的,完成后会主动调用回调函数。
三者比较
BIO:同步并阻塞,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,可以经过线程池改进,
适合于连接数目小且固定。
NIO:同步非阻塞,一个请求一个线程,客户端的连接请求都会注册到多路复用器上,多路复用器轮询到连接有IO请求时才启动一个线程进行处理;
适合连接数目多,连接比较短的架构,比聊天服务器,并发局限于应用中,编程比较复杂;
AIO:连接数目多且连接比较长的架构,比如相册服务器,充分调用OS参与并发操作,JDK1.7开始支持