BlockingIO +thread-per-connection的网络服务器设计方案
1、 前言
在 java1.4引入 NIO之前,网络服务器的典型实现方案是采用阻塞 IO+多线程模型,后来出现了非阻塞 IO( NIO),常用的实现方案则变成 NIO+Reactor模式,还有 NIO+proactor模式。本文主要是介绍阻塞 IO+多线程模型,虽然该方案有很多缺点,但此处还对此进行介绍的原因是为后面进一步介绍基于 NIO的方案做准备,毕竟研究一种新的方案时,只有知道它出现之前的老的方案的缺点后,才能对新的方案理解的更深。
2、 TCP通信过程
面向连接的 TCP 协议可以在多个通信端点之间可靠的进行数据传递,这个过程涉及到两个角色,一个是被动( passive )角色(服务器),一个是主动 (active) 角色(客户端)。服务器在一个端点被动的等待其他端点来连接,而客户端则主动的去连接服务端。以下是交互过程:
3、BlockingIO + thread-per-connection设计方案
3.1 通信体系示例
上面是一个典型的网络服务器通信体系。一个 web 服务器会同时被多个客户端并发的访问,每次访问中,客户端与服务器的通信过程分为以下几个步骤:
- 用户在客户端的浏览器中打开一个 URL,浏览器发送请求给服务器
- 服务器收到请求后,解析请求
- 服务器对请求进行处理
- 服务器将结果发给客户端
3.2 BlockingIO + thread-per-connection 架构方案
- Thread-per-connection:顾名思义,为每个连接分配一个线程。多个客户端并发的向服务端发起请求,同步的 Acceptor通过 TCP三次握手后,每接受一个客户端的 connection,就为该 connection分配一个线程,然后由该线程处理该客户端的请求。这样就实现了多个线程并发处理多个客户端请求的目的。
- BlockingIO:为什么是阻塞?如果客户端没有 connect请求,则服务端监听客户端连接的线程会一致等待连接,阻塞在 accept中;连接成功后,如果客户端一直没有发起请求,则负责处理该客户端请求的线程会一直阻塞在等待读取请求数据中;服务端向客户端回写数据的时候,也可能会被阻塞直到响应数据发送完毕。
使用该方案有以下缺点:
- 由于服务端需要为每个已连接的客户端分配一个线程,所以服务端线程的数量与已连接的客户端的数量是成线性正比关系的,譬如有 1000个客户端并发访问,而且是长连接,则服务端会启动 1000个线程。而每个线程是需要分配一定大小的堆栈空间的,所以在高并发的情况下,就会导致服务器资源耗尽。当然也可以采用线程池来处理,以控制线程的数量,但在高并发的情况下,当线程池的线程都分配完后,就无法再响应后面的客户端请求了,所以可伸缩性比较差。
- 多个线程之间的上下文切换也是浪费 CPU时间的,尤其是存在大量空闲连接的情况下,切换线程上下文完全是没有必要的
- 由于存在多线程,所以在共享资源的访问上就要求开发人员做好同步控制,增加了实现的复杂度。
4、代码示例
4.1 java Io的几个关键概念
- ServerSocket:服务端监听套接字,创建时需要指定端口号,譬如: new ServerSocket(9090),表示建立了一个监听 9090端口的套接字对象。该对象通过 accept方法监听客户端的连接请求,会阻塞直到建立一个连接,并返回已连接的套接字。
- Socket:客户端需要建立该套接字与服务端进行通信,创建时需要指定服务器的地址和端口,譬如: new Socket(InetAddress.getByName("localhost"), 9090)。利用该对象的方法 getOutputStream()和 getInputStream()可以分别获得向 Socket读写数据的输入/输出流
- 阻塞读 : 数据在不超过指定的长度的时候有多少读多少,没有数据就会一直等待
- 阻塞写:会一致阻塞,直到将所有数据写完
4.2 code
注:所有代码只用来作为原理的进一步阐述,不能用于生产环境
模拟以下场景:客户端向服务端发送 nice to meet you 信息,然后服务端响应客户端,发送 Nice to meet you too 。
服务端代码如下(采用线程池) package iothreadpool; import java.net.ServerSocket; import java.net.Socket; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * 服务端 * @author jason * */ public class IoServer implements Runnable{ ExecutorService threadPool = Executors.newCachedThreadPool(); @Override public void run() { try{ //建立服务端监听套接字,绑定的端口号是9090 ServerSocket serverSocket = new ServerSocket(9090); while(true){ //监听客户端的的连接请求,会一直阻塞直到建立一个连接,并返回已连接的套接字 Socket socket = serverSocket.accept(); //为每一个连接从线程池分配一个线程 threadPool.execute(new Handler(socket)); } }catch(Exception e){ e.printStackTrace(); } } //处理线程 class Handler implements Runnable{ private Socket socket; Handler(Socket s){ this.socket = s; } @Override public void run() { try{ byte[] input = new byte[1024]; //读取客户端的请求数据,如果没有数据,会一直阻塞 socket.getInputStream().read(input); //数据处理 System.out.println(new String(input)); //发送响应 socket.getOutputStream().write("nice to meet you too".getBytes()); }catch(Exception e){ e.printStackTrace(); } } } //启动服务端 public static void main(String[] args){ new Thread(new IoServer()).start(); } }
客户端代码如下: package io; import java.io.IOException; import java.net.InetAddress; import java.net.Socket; import java.net.UnknownHostException; public class IoClient{ public static void main(String[] args) throws UnknownHostException, IOException{ byte[] input = new byte[1024]; //连接服务端 Socket socket = new Socket(InetAddress.getByName("localhost"), 9090); //发送请求 socket.getOutputStream().write("nice to meet you".getBytes()); //读取响应数据 socket.getInputStream().read(input); System.out.println(new String(input)); //关闭连接 socket.close(); } }
5、总结
本文对 BlockingIO + thread-per-connection 的网路服务器设计方案进行了描述,并分析了该方案的缺点(注:如果客户端的连接数不是很大,则采用该方案是合适的),在下一篇 blog 中将分析 NIO+reactor 模式的网路服务器设计方案
本文为原创,转载请注明出处
相关推荐
stm32f1系列标准库+rtthread 3.1.3+finsh中断方式实现 已测试通过, 相关说明都在工程的doc目录下的readme.txt中
正点原子stm32H750北极星+RT-Thread_4.1.1工程源码, 文章链接:http://t.csdnimg.cn/pHbz5
+---5-教学设计-成品模板 | +---6-说课稿-成品模板 | +---7-人才培养方案-成品模板 | +-- -8-课程标准 | \---9-教学能力大赛工作总结 +++++++++++++ ++++++++++++++++++ 历年收集的课程思政、课程建设、教材教法...
STM32F+ RT-Thread工程源码实验 经验证可以连续稳定运行,希望物有所值,贡献出来
+-------+---------------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------+---------------------+------+-----+---------+----------------+ | id | ...
阿波罗开发板F429+RT-thread的touchGFX模板工程
+---2005-2019国社科中标标书(51份) | \---申请书 | +---活页 +---国社科中期评估 | +---国社科开题 | +---国社科标书各部分写作套路 | | | \---2023年填写模板 | + ---国社科申报技巧 | +---国社科结题 ...
stm32f4系列+RT-Thread studio+ADC+timer实现adc多通道采集,适合初学stm32+RTOS(RT-Thread)的学生以及研究人员
基于i.MX RT1050+RT-Thread云接入demo
我的世界1.12.2官方forge服务器,已经配置好的。联机教程:http://www.mcbbs.net/thread-831469-1-1.html 希望你能成功开我的世界服务器。
使用STM32标准库和RT-thread系统移植freemodbus主机程序,完美运行,keil工程
硬件平台stm32f103vet6,操作系统rt-thread,加入lwip,注意只做了服务器测试程序,只是测试程序!!!!
基于stm32f103单片机移值RT-Thread Nano实现FinSH,工程采用STM32的标准库。
硬件平台stm32f103vet6,操作系统rt-thread,利用w25x16做的一个文件系统,是一个测试程序。。
基于STM32 RT-Thread操作系统的波形发生器 DAC芯片AD5676R进行8通道波形输出 ec11编码器进行人机交互 u8g2库驱动OLED 供大家参考,希望对各位有所帮助
硬件平台stm32f103vet6,操作系统rt-thread,应用在finsh shell测试程序,只是测试程序!!!
if (EXCEPTION_BREAKPOINT == per->ExceptionCode) { // 判断断点地址是否为WriteFile()API地址 if (g_pfWriteFile == per->ExceptionAddress) { WriteProcessMemory(g_cpdi.hProcess, g_...
1、移植并修改了 FreeModbus1.5 及 RT-Thread1.2.2 至 STM32 ,新增主机功能 2、开发平台支持Eclipse、Keil、IAR 4、支持 Modbus RTU 5、Modbus主机 支持所有常用功能(寄存器、线圈、离散输入) 6、目前的Modbus...
资源整理防止下次找不到,Nano 是 Master 的精简版,去掉了一些组件和各种开发板的 BSP,保留了 OS 的核心功能.
ST77903为sitronix 小体积IC,主要应该在穿戴,接口是QSPI,因为内部不带RAM,所以QSPI必须连续传输,在HOST必须使用一个专门线程,来完成LCD的刷屏,DEMO内容包括3个手表表盘,UI框架是LVGL,系统用的RT-THREAD