系统之间通讯方式之(Java阻塞同步模式和非阻塞同步模式详解)(三)
来源:互联网 发布:乐福生涯数据 编辑:程序博客网 时间:2024/05/22 10:56
1、全文提要
系统间通信本来是一个很大的概念,我们首先重通信模型开始讲解。在理解了四种通信模型的工作特点和区别后,对于我们后文介绍搭建在其上的各种通信框架,集成思想都是有益的。
目前常用的IO通信模型包括四种(这里说的都是网络IO):阻塞式同步IO、非阻塞式同步IO、多路复用IO、和真正的异步IO。这些IO模式都是要靠操作系统进行支持,应用程序只是提供相应的实现,对操作系统进行调用。
上篇中,首先介绍传统的阻塞式同步IO和非阻塞式同步IO两种IO工作模式,然后使用JAVA进行实现;下篇,对多路复用IO工作模式和异步IO工作模式进行介绍,并介绍java对这两种工作模式的支持。
2、传统阻塞模式(BIO)
这个小节的介绍,在《架构设计:系统间通信(1)——概述从“聊天”开始上篇》这篇文章中已经说明了,这里只是“接着讲”,您可以理解成“在概述的基础上继续深入写”。BIO就是:blocking IO。最容易理解、最容易实现的IO工作方式,应用程序向操作系统请求网络IO操作,这时应用程序会一直等待;另一方面,操作系统收到请求后,也会等待,直到网络上有数据传到监听端口;操作系统在收集数据后,会把数据发送给应用程序;最后应用程序受到数据,并解除等待状态。如下图所示:
(请您注意,上图中交互的两个元素是应用程序和它所使用的操作系统)就TCP协议来说,整个过程实际上分成三个步骤:三次握手建立连接、传输数据(包括验证和重发)、断开连接。当然,断开连接的过程并不在我们讨论的IO的主要过程中。但是我们讨论IO模型,应该把建立连接和传输数据的者两个过程分开讨论。
2-1、JAVA对阻塞模式的支持
JAVA对阻塞模式的支持,就是java.net包中的Socket套接字实现。这里要说明一下,Socket套接字是TCP/UDP等传输层协议的实现。例如客户端使用TCP协议连接这台服务器的时候,当TCP三次握手成功后,应用程序就会创建一个socket套接字对象(注意,这是还没有进行数据内容的传输),当这个TCP连接出现数据传输时,socket套接字就会把数据传输的表现告诉程序员(例如read方法接触阻塞状态)
下面这段代码是java对阻塞模式的支持:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
上面的服务器端代码可以直接运行。代码执行到serverSocket.accept()的位置就会等待,这个调用的含义是应用程序向操作系统请求客户端连接的接收,这是代码会阻塞,而底层调用的位置在DualStackPlainSocketImpl这个类里面(注意我使用的测试环境是windows 8 ,所以是由这个类处理;如果您是在windows 7环境下进行测试,那么处理类是TwoStacksPlainSocketImpl,这是Windows环境;如果您使用的测试环境是Linux,那么视Linux的内核版本而异,具体的处理类又是不一样的)。
2-2、存在的问题
很明显,我们在代码里面并没有设置timeout属性,所以运行的是“if”这段的代码,很明显在调用JNI后,下层也在等待有客户端连接上来。这种调用方式当然有问题:
同一时间,服务器只能接受来自于客户端A的请求信息;虽然客户端A和客户端B的请求是同时进行的,但客户端B发送的请求信息只能等到服务器接受完A的请求数据后,才能被接受。
由于服务器一次只能处理一个客户端请求,当处理完成并返回后(或者异常时),才能进行第二次请求的处理。很显然,这样的处理方式在高并发的情况下,是不能采用的。
实际上以上的问题是可以通过多线程来解决的,实际上就是当accept接收到一个客户端的连接后,服务器端启动一个新的线程,来读写客户端的数据,并完成相应的业务处理。但是你无法影响操作系统底层的“同步IO”机制。
3、非阻塞模式
一定要注意:阻塞/非阻塞的描述是针对应用程序中的线程进行的,对于阻塞方式的一种改进是应用程序将其“一直等待”的状态主动打开,如下图所示:
这种模式下,应用程序的线程不再一直等待操作系统的IO状态,而是在等待一段时间后,就解除阻塞。如果没有得到想要的结果,则再次进行相同的操作。这样的工作方式,暴增了应用程序的线程可以不会一直阻塞,而是可以进行一些其他工作。
3-1、JAVA对非阻塞模式的支持
那么JAVA中是否支持这种非阻塞IO的工作模式呢?我们继续分析DualStackPlainSocketImpl中的accept0实现:
那么timeout是在哪里设置的呢?在ServerSocket中,调用了DualStackPlainSocketImpl的父类SocketImpl进行timeout的设置:
ServerSocket中的setSoTimeout方法也有相应的注释说明:
Enable/disable SO_TIMEOUT with the specified timeout, in milliseconds. With this option set to a non-zero timeout, a call to accept() for this ServerSocket will block for only this amount of time. If the timeout expires, a java.net.SocketTimeoutException is raised, though the ServerSocket is still valid.The option must be enabled prior to entering the blocking operation to have effect. The timeout must be > 0. A timeout of zero is interpreted as an infinite timeout.
那么java中对非阻塞IO的支持如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
执行效果如下:
这里我们针对了SocketServer增加了阻塞等待时间,实际上只实现了非阻塞IO模型中的第一步:监听连接状态的非阻塞。通过运行代码,我们可以发现read()方法还是被阻塞的,说明socket套接字等待数据读取的过程,还是阻塞方式。
3-2、继续改进
那么,我们能不能改进read()方式,让它也变成非阻塞模式呢?当然是可以的,socket套接字同样支持等待超时时间设置。代码如下:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
这样一来,我们利用JAVA实现了完整的“非阻塞IO”模型:让TCP连接和数据读取这两个过程,都变成了“非阻塞”方式了。
然并卵,这种处理方式实际上并没有解决accept方法、read方法阻塞的根本问题。根据上文的叙述,accept方法、read方法阻塞的根本问题是底层接受数据报文时的“同步IO”工作方式。这两次改进过程,只是解决了IO操作的两步中的第一步:将程序层面的阻塞方式变成了非阻塞方式。
3-3、利用线程再改进
另一个方面,由于应用程序级别,我们并没有使用多线程技术,这就导致了应用程序只能一个socket套接字 一个socket套接字的处理。这个socket套接字没有处理完,就没法处理下一个socket套接字。针对这个问题我们还是可以进行改进的:让应用程序层面上,各个socket套接字的处理不相互影响:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
3-4、依然存在的问题
引入了多线程技术后,IO的处理吞吐量大大提高了,但是这样做就真的没有问题了吗,您要知道操作系统可是有“最大线程”限制的:
虽然在服务器端,请求的处理交给了一个独立线程进行,但是操作系统通知accept()的方式还是单个处理的(甚至都不是非阻塞模式)。也就是说,实际上是服务器接收到数据报文后的“业务处理过程”可以多线程(包括可以是非阻塞模式),但是数据报文的接受还是需要一个一个的来。
在linux系统中,可以创建的线程是有限的。我们可以通过cat /proc/sys/kernel/threads-max 命令查看可以创建的最大线程数。当然这个值是可以更改的,但是线程越多,CPU切换所需的时间也就越长,用来处理真正业务的需求也就越少。
创建一个线程是有较大的资源消耗的。JVM创建一个线程的时候,即使这个线程不做任何的工作,JVM都会分配一个堆栈空间。这个空间的大小默认为128K,您可以通过-Xss参数进行调整。
当然您还可以使用ThreadPoolExecutor线程池来缓解线程的创建问题,但是又会造成BlockingQueue积压任务的持续增加,同样消耗了大量资源。另外,如果您的应用程序大量使用长连接的话,线程是不会关闭的。这样系统资源的消耗更容易失控。
最后,无论您是使用的多线程、还是加入了非阻塞模式,这都是在应用程序层面的处理,而底层socketServer所匹配的操作系统的IO模型始终是“同步IO”,最根本的问题并没有解决。
那么,如果你真想单纯使用线程来解决问题,那么您自己都可以计算出来您一个服务器节点可以一次接受多大的并发了。看来,单纯使用线程解决这个问题不是最好的办法。
4、多路复用IO(IO Multiplex)
我将详细讲解操作系统支持的多路复用IO的工作方式,并介绍JAVA 1.4版本中加入的 JAVA NIO对多路复用IO的实现。(东西太多,我们放下下篇中)
5、异步IO(真正的NIO)
我将详细讲解操作系统支持的异步IO方式,并介绍JAVA 1.7版本中加入的NIO2.0(AIO)对异步IO的实现。(东西太多,我们放下下篇中)
- 系统之间通讯方式之(Java阻塞同步模式和非阻塞同步模式详解)(三)
- 同步异步和阻塞非阻塞模式
- 同步异步阻塞非阻塞Reactor模式和Proactor模式 (目前JAVA的NIO就属于同步非阻塞IO)
- IO模式——同步(阻塞、非阻塞)、异步
- reactor和proactor模式 同步异步 阻塞 非阻塞
- I/O模式 同步、异步、阻塞、非阻塞
- 同步和异步,阻塞和非阻塞详解
- TCP同步与异步及阻塞模式,多线程+阻塞模式,非阻塞模式简单介绍
- TCP同步与异步及阻塞模式,多线程+阻塞模式,非阻塞模式简单介绍
- TCP同步与异步及阻塞模式,多线程+阻塞模式,非阻塞模式简单介绍
- TCP同步与异步及阻塞模式,多线程+阻塞模式,非阻塞模式简单介绍
- TCP同步与异步及阻塞模式,多线程+阻塞模式,非阻塞模式简单介绍
- TCP同步与异步及阻塞模式,多线程+阻塞模式,非阻塞模式简单介绍
- 网络编程之 Socket的模式(一) --- “阻塞/非阻塞” 与 “同步/异步”
- 网络编程之 Socket的模式(一) --- “阻塞/非阻塞” 与 “同步/异步”
- 高性能IO设计模式之阻塞/非阻塞,同步/异步解析
- 阻塞/非阻塞/同步/异步详解
- 同步、异步、阻塞与非阻塞详解
- Django-restframework30 Format suffixes(后缀格式)
- 网站侵权处理案例
- 类,面向对象,面向过程,对象
- 计算机视觉&点云处理学术指引
- Unity5实现在一个主窗口有多摄像机窗口显示功能
- 系统之间通讯方式之(Java阻塞同步模式和非阻塞同步模式详解)(三)
- 数电111111111
- JS/PHP中,数组与字符串的转换,这次总算是记住了
- linux系统挂载ntfs格式移动硬盘
- [Paper]Improved Stereo Matching with Constant Highway Networks and Reflective Confidence Learning
- 使用Imagenet VGG-19模型进行图片识别
- Django-restframework31 Returning URLs
- Linux基础知识
- 杜教板子