起先是在Socket编程时,服务端取得客户端发送的数据,但是在InputStream.read()的时候,一直停在那,然后取了解了read方法才知道阻塞问题
代码示例:
//端口数据取得
byte[] b = new byte[1024];
in.read(b);//阻塞地方
String contents = new String(b).trim();//trim去除多余空格,否则,读进来的是byte[1024]个占位字节
Logger.getLogger(Constant.TASK).info("端口数据取得:" + contents);
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.次接收的操作是否结束了.
可以看一下源码
public abstract class InputStream extends Object implements Closeable
此抽象类是表示字节输入流的所有类的超类。
需要定义 InputStream
的子类的应用程序必须始终提供返回下一个输入字节的方法。
个人理解,这种对象的概念有点像需要数据传输双方之间的一个通道,这个通道负责接收数据(与之对应还有OutPutStream 负责发送数据)。
InputStream 中的read方法用于读取数据,方法有3个重载。
read()
从输入流读取下一个数据字节。
read(byte[] b)
从输入流中读取一定数量的字节并将其存储在缓冲区数组 b 中。
read(byte[] b, int off, int len)
将输入流中最多 len 个数据字节读入字节数组。
其中InputStream.read()方法,这个方法是从流里每次只读取读取一个字节,效率会非常低。
更好的方法是用InputStream.read(byte[] b)或者InputStream.read(byte[] b,int off,int len)方法,一次读取多个字节。
这里有一点需要特别注意:read 方法在输入数据可用、检测到文件末尾或者抛出异常前,此方法一直阻塞。
Socket流这里还存在另外一个问题,socket流和文件流不太一样,文件流很容易知道文件末尾,到了文件末尾,直接就把流close掉就OK了。但是socket 流不一样,你无法知道它什么时候到末尾,所以连接一直保持着,流也一直保持阻塞状态。即使用了带参数的read方法,返回了有效数据,但其实流仍然没有关闭,处于阻塞状态。
针对这种请情况,一般就需要通信的双方约定数据传输的协议了。
①比如,约定消息的头部首先明确此次传输数据的大小。这样服务端就可以有目的性的读取数据
如果采用一个一个字节读取的话,就需要先两方约定好。不然就会造成read一直在阻塞状态。
②采用InputStrea.shutdownOutput(),在客户端的关闭Stream输出,这样服务端就不会一直在等待输入流的输入
这时候可以采用一次性读取的read(b[])方法,注意的是b[]要不小于传输过来的数据大小,不然只会读取其中一部分。