1.CS模型
两个进程间的交互。
服务端:提供位置信息(绑定的IP地址+监听端口)。
客户端:通过连接操作像服务端监听的地址发起请求。通过三次握手建立连接,成功后就可以通过socket进行通信了。
2.同步阻塞模型
ServerSocket:负责绑定IP
Socket:发起连接操作
连接成功后,双方通过输入输出流进行同步阻塞式通信。
3.BIO通信模型(一请求一应答模型)
服务端:一个独立的acceptor线程负责监听客户端的连接。每接收到一个客户端的连接请求,就为其创建一个新的线程进行链路处理,处理完毕通过输出流返回应答给客户端,线程销毁。
缺点:缺乏弹性伸缩能力。当客户端并发量大的时候,服务端线程数和客户端并发访问数1:1。(一个线程只能处理一个客户端连接)
线程是java虚拟机的宝贵资源,线程数膨胀后,系统性能下降,继而会发生线程堆栈溢出,创建新线程失败,最终宕机,不能提供服务。
4.线程池调优
有新的客户端接入时,将客户端socket封装位task(实现Runnable),扔到后端的线程池中进行处理。线程池中设置消息队列进行缓冲,避免了每个请求接入都创建一个新的线程,资源可控。
优点:线程池和消息队列都是有界的,无论多大并发,都不会导致线程个数膨胀或者队列过长内存溢出。
5.本质问题
read方法:对socket输入流进行读取时,会一直阻塞直到“有数据可读”,“可用数据已读取完”,“空指针或者IO一场”。当对方“发送请求或者应答消息比较缓慢”,或者“网络传输较慢”,读取输入流的一方的通信线程将被长时间阻塞(发送方发送需要60s,读取方读取的IO线程会同步阻塞60s,此间其他介入消息只能在消息队列中排队)。
write方法:OutputStream的write方法调用来写输出流时,也会阻塞,直到所有要发送的字节全部写入完毕,或发生异常。当消息接收方处理缓慢时,将不能及时从TCP缓冲区读取数据,会导致发送方的TCP window size不断减小,直到为0,双方处于keep Alive状态,消息发送方将不能再向TCP缓冲区写入消息,write操作会被阻塞直到window size大于0或者IO异常。
线程池问题:如果所有可用的线程都被阻塞了,后续的IO消息都将进入阻塞队列。队列满了以后,后续入队列的操作将被阻塞。因为前端只有一个Acceptor线程接收客户端接入,它被阻塞在线程池的同步阻塞队列之后,新的客户端请求消息会被拒绝,客户端会发生大量的连接超时。当所有的连接都超时,调用者会认为系统已经崩溃,无法接收新的请求消息。