API | 阻塞 | 非阻塞 |
connect | tcp三次握手成功后返回。 | 立即返回,需要通过其他方式判断TCP连接建立成功还是失败。 |
send | 阻塞直到将待发送数据从用户空间全部放入内核发送缓冲区后返回。 | 立即返回,无论待发送数据是否成功放入内核发送缓冲区。 |
recv | 阻塞直到数据到达内核接收缓冲区,并将数据从内核复制到用户空间后返回。 | 立即返回,无论是否有数据到达内核接收缓冲区。 |
close | 略 | 略 |
1)同步/异步关注的是消息通知的机制,而阻塞/非阻塞关注的是程序(线程)等待消息通知时的状态;
2)
同步的情况下,是由处理消息者自己去等待消息是否被触发,而异步的情况下是由触发机制来通知处理消息者;
阻塞与非阻塞主要是程序(线程)等待消息通知时的状态角度来说的。
阻塞调用是指调用结果返回之前,当前线程会被挂起,一直处于等待消息通知不能够执行其他业务,
函数只有在得到结果之后才会返回。
非阻塞
指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回
。
IO模型 | 是否阻塞 | 是否同步 |
阻塞IO | 阻塞 | 同步 |
同步非阻塞IO | 非阻塞 | 同步 |
IO多路复用 | 阻塞 | 同步 |
AIO | 非阻塞 | 异步 |
2)服务端线程个数与客户端并发访问连接数是1:1的关系。
3)随着客户端并发访问量增大,服务端线程个数线性膨胀,系统性能急剧下降。
优化方式:
1)服务端通过线程池来处理多个客户端的接入请求,通过线程池约束服务端线程资源。形成“客户端并发连接M”与“服务端线程池最大线程数N”的比例关系(治标不治本)。
示例代码:
netty-study工程com.zhangyiwen.study.bio.multi_thread_server包。
Reactor模式思想:分而治之+事件驱动
1)分而治之
一个connection里完整的网络处理过程一般分为accept、read、decode、process、encode、send这几步。
Reactor模式将每个步骤映射为一个Task,服务端线程执行的最小逻辑单元不再是一次完整的网络请求,而是Task,且采用非阻塞方式执行。
2)事件驱动
每个Task对应一个特定网络事件。当Task准备就绪时,Reactor收到对应的网络事件通知,并将Task分发给绑定了对应网络事件的Handler执行。
Reactor模型图解:
1)single reactor thread
采用多个Reactor,每个Reactor在自己单独线程中执行,可以并行响应多个客户端的请求事件;
Netty采用类似这种模式,boss线程池就是多个mainReactor,worker线程池就是多个subReactor
2)客户端发起的连接操作是异步的,不需要像之前的客户端那样被同步阻塞;
3)JDK的Selector在Linux等主流操作系统上通过epoll实现,它没有连接句柄数的限制,使得一个Reactor线程可以同时处理成千上万个客户端连接,而且性能不会随客户端连接的增加而线性下降,适合做高性能高负载的网络服务器方案。
示例代码:
netty-study工程com.zhangyiwen.study.nio.reactor_demo包。
线程池使用比较方便。
模型缺点:
依赖线程的数量,设置太少,并发处理能力太弱,设置太多,线程切换频繁。
可以避免频繁的线程切换。
模型缺点:
如果官方的库没有提供这种功能,就只能自己去解析协议,没有线程池使用起来方便快捷。
3、连接复用
一般我们自定义的二进制协议,协议设计时都会带有一个唯一的序列号,Rsp通过这个序列号来找到对应哪个Req。
这样就可以复用一条链接进行多次请求发送和响应接收,无需使用线程池和连接池方式了。