Socket InputStream读取无结束符且不等长数据

来源:互联网 发布:人工蜂群算法工具箱 编辑:程序博客网 时间:2024/06/04 19:36

首先请查看一下JavaAPI,可以看到InputStream读取流有三个方法,分别为read(),read(byte[] b),read(byte[] b, int off, int len)。其中read()方法是一次读取一个字节,效率是非常低的。所以最好是使用后面两个方法。

例如以下代码:

Java代码  收藏代码
  1. /** 
  2.  * 读取流 
  3.  *  
  4.  * @param inStream 
  5.  * @return 字节数组 
  6.  * @throws Exception 
  7.  */  
  8. public static byte[] readStream(InputStream inStream) throws Exception {  
  9.     ByteArrayOutputStream outSteam = new ByteArrayOutputStream();  
  10.     byte[] buffer = new byte[1024];  
  11.     int len = -1;  
  12.     while ((len = inStream.read(buffer)) != -1) {  
  13.         outSteam.write(buffer, 0, len);  
  14.     }  
  15.     outSteam.close();  
  16.     inStream.close();  
  17.     return outSteam.toByteArray();  
  18. }  

 

我们来测试一下:

Java代码  收藏代码
  1. public static void main(String[] args) {  
  2.     try {  
  3.         File file = new File("C:\\ceshi.txt");  
  4.         FileInputStream fin = new FileInputStream(file);  
  5.         byte[] filebt = readStream(fin);  
  6.         System.out.println(filebt.length);  
  7.     } catch (Exception e) {  
  8.         e.printStackTrace();  
  9.     }     
  10. }  

后台会打印这个文本的字节大小。看起来,这个是没有问题的。

 

 

关于InputStream类的available()方法
这个方法的意思是返回此输入流下一个方法调用可以不受阻塞地从此输入流读取(或跳过)的估计字节数。为什么需要这个方法?因为在一些网络应用中,数据流并不是一次性就能传递的,如果我们还是像上面那样去将这个流转换,会出问题的。我们来做一个例子,这是一个Socket编程的简单例子,具体Socket内容我会在后面文章中解释的。

首先编写两个类,一个用户初始化Socket服务,并且处理每个请求都有新的线程去处理,代码如下:

Java代码  收藏代码
  1. package com.service;  
  2. import java.net.*;  
  3. public class DstService {  
  4.     public static void main(String[] args) {  
  5.         try {  
  6.             // 启动监听端口 8001  
  7.             ServerSocket ss = new ServerSocket(8001);  
  8.             boolean bRunning = true;  
  9.             while (bRunning) {  
  10.                 // 接收请求  
  11.                 Socket s = ss.accept();  
  12.                 // 将请求指定一个线程去执行  
  13.                 new Thread(new DstServiceImpl(s)).start();  
  14.             }  
  15.         } catch (Exception e) {  
  16.             e.printStackTrace();  
  17.         }  
  18.     }  
  19. }  

那么处理类我们也来看一下:

Java代码  收藏代码
  1. package com.service;  
  2. import java.io.*;  
  3. import java.net.*;  
  4. import com.util.*;  
  5. public class DstServiceImpl implements Runnable {  
  6.     Socket socket = null;  
  7.     public DstServiceImpl(Socket s) {  
  8.         this.socket = s;  
  9.     }  
  10.     public void run() {  
  11.         try {  
  12.             InputStream ips = socket.getInputStream();  
  13.             OutputStream ops = socket.getOutputStream();  
  14.             while (true) {  
  15.                 byte[] bt = StreamTool.readStream(ips);  
  16.                 String str = new String(bt);  
  17.                 System.out.println("主机收到信息:" + str);  
  18.                 String restr = "你好,主机已经收到信息!";  
  19.                 ops.write(restr.getBytes());  
  20.                 ops.flush();  
  21.             }  
  22.         } catch (Exception e) {  
  23.             e.printStackTrace();  
  24.         }  
  25.     }  
  26. }  

 至于工具类,我就直接给代码了:

Java代码  收藏代码
  1. package com.util;  
  2. import java.io.*;  
  3. public class StreamTool {     
  4.     public static void main(String[] args) {  
  5.         try {  
  6.             File file = new File("C:\\ceshi.txt");  
  7.             FileInputStream fin = new FileInputStream(file);  
  8.             byte[] filebt = readStream(fin);  
  9.             System.out.println(filebt.length);  
  10.         } catch (Exception e) {  
  11.             e.printStackTrace();  
  12.         }     
  13.     }     
  14.     /** 
  15.      * @功能 读取流 
  16.      * @param inStream 
  17.      * @return 字节数组 
  18.      * @throws Exception 
  19.      */  
  20.     public static byte[] readStream(InputStream inStream) throws Exception {  
  21.         ByteArrayOutputStream outSteam = new ByteArrayOutputStream();  
  22.         byte[] buffer = new byte[1024];  
  23.         int len = -1;  
  24.         while ((len = inStream.read(buffer)) != -1) {  
  25.             outSteam.write(buffer, 0, len);  
  26.         }  
  27.         outSteam.close();  
  28.         inStream.close();  
  29.         return outSteam.toByteArray();  
  30.     }  
  31. }  

 你可以直接运行这个类,会看到流被转换的效果。

我们来写一个Socket客户端测试一下:

Java代码  收藏代码
  1. package com.client;  
  2. import java.io.*;  
  3. import java.net.*;  
  4. import com.util.*;  
  5. public class DstClient {  
  6.     public static void main(String[] args) {  
  7.         try {  
  8.             Socket socket = new Socket("127.0.0.1"8001);  
  9.             // 开启保持活动状态的套接字  
  10.             socket.setKeepAlive(true);  
  11.             // 设置读取超时时间  
  12.             socket.setSoTimeout(30 * 1000);  
  13.             OutputStream ops = socket.getOutputStream();  
  14.             String mess = "你好,我是崔素强!";  
  15.             ops.write(mess.getBytes());  
  16.             InputStream ips = socket.getInputStream();  
  17.             byte[] rebyte = StreamTool.readStream(ips);  
  18.             String remess = new String(rebyte);  
  19.             System.out.println("收到主机消息:" + remess);  
  20.             socket.close();  
  21.         } catch (Exception e) {  
  22.             e.printStackTrace();  
  23.         }  
  24.     }  
  25. }  

 先运行DstService,然后运行客户端,看效果。会发现,控制台没有任何输出。经过调试发现,因为请求死在了

Java代码  收藏代码
  1. while ((len = inStream.read(buffer)) != -1) {  

这行代码上面。这就是在网络应用中会造成的后果。那么如何解决呢?有的人给出了如下代码:

Java代码  收藏代码
  1. int count = in.available();  
  2. byte[] b = new byte[count];  
  3. in.read(b);  

可是在进行网络操作时往往出错,因为你调用available()方法时,对发发送的数据可能还没有到达,你得到的count是0。需要做如下修改,是我们的读取流方法改成如下:

Java代码  收藏代码
  1. /** 
  2.  * @功能 读取流 
  3.  * @param inStream 
  4.  * @return 字节数组 
  5.  * @throws Exception 
  6.  */  
  7. public static byte[] readStream(InputStream inStream) throws Exception {  
  8.     int count = 0;  
  9.     while (count == 0) {  
  10.         count = inStream.available();  
  11.     }  
  12.     byte[] b = new byte[count];  
  13.     inStream.read(b);  
  14.     return b;  
  15. }  

下面你在运行,会看到服务端和客户端都收到了消息。 

 

 

关于InputStream.read(byte[] b)和InputStream.read(byte[] b,int off,int len)这两个方法都是用来从流里读取多个字节的,有经验的程序员就会发现,这两个方法经常 读取不到自己想要读取的个数的字节。比如第一个方法,程序员往往希望程序能读取到b.length个字节,而实际情况是,系统往往读取不了这么多。仔细阅读Java的API说明就发现了,这个方法 并不保证能读取这么多个字节,它只能保证最多读取这么多个字节(最少1个)。因此,如果要让程序读取count个字节,最好用以下代码:

Java代码  收藏代码
  1. int count = 100;  
  2. byte[] b = new byte[count];  
  3. int readCount = 0// 已经成功读取的字节的个数  
  4. while (readCount < count) {  
  5.     readCount += inStream.read(b, readCount, count - readCount);  
  6. }  

 这样就能保证读取100个字节,除非中途遇到IO异常或者到了数据流的结尾情况



但是对于对数据不等长的情况,上面的代码还是存在很大问题,如果带读取的数据大于100字节会导致数据不完整,如果小于100字节inStream.read方法会阻塞线程无法退出,当服务器发来下一条消息的时候会出现两个线程同时读取同一个inputstream的问题(两个线程根据CPU时间片交叉读取数据)造成数据读取的不完整。我们要想办法发现阻塞,并优雅的结束阻塞线程,于是想到了handler 的 postDelayed   removeCallbacks


 private class MyRunnable implements  Runnable{     private byte[] data;     public MyRunnable(byte[] b){         data = b;     }     @Override     public void run() {         setState(READ_SUCCESS,new String(data));//getBytes()         //关闭socket,下次请求重新创建连接         close();     } } private MyRunnable myRunnable;// private Handler myHandler = new Handler();// 如果 Bluetooth是在thread中被创建的,那么这个handler投递的消息会被投递到子线程,而子线程没有处理消息的能力 // 解决办法两种 1 .getMainLooper  2.Looper.prepare    private Handler myHandler = null;    private void read(InputStream in) throws IOException {    int count = 500;//写小了数据读不全,写多了最后一次read会阻塞。。。     byte[] b = new byte[count];     int readCount = 0;     while (readCount < count ) {         try{             myRunnable = new MyRunnable(b);             myHandler = new Handler(Looper.getMainLooper());             myHandler.postDelayed(myRunnable, 2000);             readCount += in.read(b, readCount, count - readCount);             myHandler.removeCallbacks(myRunnable);         }catch (Exception e){              Log.i("yzy","e="+e);             break;         }     } }


但是在如何关闭read所阻塞的线程这个地方试了很多方法都没有work,首先Thread.stop()使用会报错,intercept只能销毁由于wait和sleep而阻塞的线程,使用ExecutorService  的shutDownNow也不能杀死这个线程。


最终通过每次发请求重新建立socket连接完成了需求。




原创粉丝点击