I/O 的 InputStream.read 方法的阻塞

近期打算研究下基于NIO的Netty框架,先来回顾一下I/O的基础。

 

JAVA里的IO 目前有两种,一种是早期发布的I/O模型,也就是所谓的BIO(Blocking I/O);另一种是JDK1.4里发布的基于 多路复用实现的NIO。

 

阻塞型 I/O,主要阻塞在两个地方:

第一:在调用InutStream.read 方法是阻塞的,它会一直等到数据到来时(或超时)才会返回;第二:在调用ServerSocket.accept()方法时,也会一直阻塞到有客户端连接才会返回;

目前大部分的客户端服务端的网络应用软件的早期版本的I/O都是使用阻塞型的I/O实现。处理模型参考:

阻塞型的I/O 存在以下几点问题:

首先,InputStream.read()方法在其缓存区未满时,会造成阻塞,只有一定的数据填满了缓存区或者客户端关闭了套接字,方法才会返回。
其次,会产生大量的垃圾,BufferedReader创建了缓存区来从套接字中读入数据,但是同样创建了一些字符串存储这些数据。这些String很快变成垃圾需要回收。
类似的,读写操作被阻塞而且向流中一次写入一个字符会造成效率低下,所以应该使用缓存区,但一旦使用缓存,流又会产生更多是垃圾。
另外,通常在JAVA中处理阻塞I/O要用到线程(大量的线程),一般是实现一个线程池来处理请求。线程使得服务器可以处理多个连接,但是他们同样也引发了许多问题。每个线程拥有
自己的栈空间并且占用一些CPU时间,耗费很大,而且很多时间是浪费了阻塞I/O操作上,没有有效利用CPU.
 

下面,来看一下阻塞I/O的具体的阻塞情况点:

首先来看一下JAVA文档中的 InputStream 的签名

public abstract class InputStream extends Object implements Closeable

此抽象类是表示字节输入流的所有类的超类。

需要定义 InputStream 的子类的应用程序必须始终提供返回下一个输入字节的方法。

 

个人理解,这种对象的概念有点像需要数据传输双方之间的一个通道,这个通道负责接收数据(与之对应还有OutPutStream 负责发送数据)。

 

到目前为止,我所接触到的I/O主要是 File I/O 和 Socket I/O。

 

InputStream 中的read方法用于读取数据,方法有3个重载。

abstract  int read()
          从输入流读取下一个数据字节。
 int read(byte[] b)
          从输入流中读取一定数量的字节并将其存储在缓冲区数组 b 中。
 int read(byte[] b, int off, int len)
          将输入流中最多 len 个数据字节读入字节数组。

 

其中InputStream.read()方法,这个方法是从流里每次只读取读取一个字节,效率会非常低。    

更好的方法是用InputStream.read(byte[] b)或者InputStream.read(byte[] b,int off,int len)方法,一次读取多个字节。

这里有一点需要特别注意:read 方法在输入数据可用、检测到文件末尾或者抛出异常前,此方法一直阻塞。

这是什么意思呢?

我们来看一个简单的Socket通信的例子:

 

 【Client】                                                                【ServerSocket】

                                                                                   1、accept()

                                                                                   服务端阻塞,至接收到客户端的请求

 

   2、new Socket("address",port);                                          

   建立一个和服务端的socket连接                               接收到客户端连接,accept 阻塞结束

 

                                                                                   3、socket.getInputStream().read();

                                                                                   从socket请求获取输入流,读取流中的数据。

                                                                                   这个时候问题来了:虽然和客户端的连接好了

                                                                                   但是服务端不知道客户端什么时候会发来数据,

                                                                                   另外,因为网络传输的原因,数据还可能被分

                                                                                   多次到达。结合上面的说明:也就好理解了。

                                                                                   read需要等待输入和输入的到达。

---------------------------------------------------------------------------------------------------------------------------

Socket流这里还存在另外一个问题,socket流和文件流不太一样,文件流很容易知道文件末尾,到了文件末尾,直接就把流close掉就OK了。但是socket 流不一样,你无法知道它什么时候到末尾,所以连接一直保持着,流也一直保持阻塞状态。即使用了带参数的read方法,返回了有效数据,但其实流仍然没有关闭,处于阻塞状态。
针对这种请情况,一般就需要通信的双方约定数据传输的协议了。比如,约定消息的头部首先明确此次传输数据的大小。这样服务端就可以有目的性的读取数据。

 

---------------------------------------------------------------------------------------------------------------------------

总结一下:

首先,Socket I/O时,发送方如果不将输出流进行关闭,接收方就会认为输入流没有结束,直到超时.
其次,我们判断一个信息是否已经完全的读取完毕,除了使用输入流结束这种办法,还可以自行封装一层协议,用于信息的交互.

 

这里其实是可以借鉴TCP长连接的实现的:Java当中的Socket类,其实是使用TCP协议进行传输的.

一般情况下,我们会在TCP的基础上再封装一层协议,用户长连接的传输.协议的信息包,也分包头和包体两个部分.
包体,主要就是我们要传输的信息.(维持连接的信息包,包体可为空)
包头,一般分为三个部分.第一部分是信息包的长度(长度一般是指整个信息包的长度);第二部分是包体信息的类型(在这里指出是否是维持连接包);第三部分是信息包的序列号,一般情况下,这个序列号要确保在传输过程中唯一标识该信息包.
如果为了安全起见,还可以在包体后添加包尾,包尾数据用于对包体数据的验证)
这样,通信双方就可以根据包长来判断一次接收的操作是否结束了.                                                                               

  

  

 

 

 

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值