Java使用Socket进行字符串和图片文件同时传输

来源:互联网 发布:中国程序员数量不够 编辑:程序博客网 时间:2024/04/24 23:47

  最近开发中使用到将字符串和图片同时传输的功能。我这边是Android端,要接收服务器端发送来的信息和图片。由于服务器端不是一个web servser,所以图片和字符串信息要混杂着传送。比较麻烦,花了一些时间解决这个问题。特记录。

  网络上关于图片的传输一般有两种方式,一个是通过base64编码,一个就是通过发送端先发送图片大小,在发送图片,接收端根据图片大小读取规定大小的数据保存到文件。由于base64会增加数据量,身为一个Android程序员,我并不想这么实现,所以我实现第二种方式。

  首先说一下通信协议:我定义的一条完整的数据如下:(text1)(image_start)(image_file_name)(image_file_name_end)(image_file_length)(image)(text2)(message_end)

  (text1)和(text2):图片文件可能是在一条完整数据的中部,所以图片文件前端和后端都可能存在文本数据

  (image_start):图片文件数据开始的标识,在程序中我使用的是(image:)。

  (image_file_name):图片文件的名称,其实增加这个内容,刚开始的时候只要是想获得文件的后缀名从而确定图片文件的类型,后来干脆就直接把图片文件的名称接收算了。。。。

  (imge_file_name_end):图片文件名结束的标识,在windows系统中,文件名中不能存在?,所以在程序中,我使用的是(?);

  (image_file_length):图片文件的长度,这部分是8个字节的内容。在java中,获取文件的长度,返回的是一个Long类型的数据,所以发送的时候,需要将Long类型转化为一个8字节的数据,接收时,要根据这8字节的数据,转化为Long类型。

  (image):具体的图片文件信息。

  (message_end):一条完整的消息结束的标识,在程序中我使用的是(over)。

  首先是发送图片的类,我在demo中,发送图片的是客户端。类写的比较简单。

  

复制代码
  1 package util;  2   3 import java.io.File;  4 import java.io.FileInputStream;  5 import java.io.InputStream;  6 import java.io.OutputStream;  7 import java.net.Socket;  8   9 public class SendImage 10 { 11     private Socket socket; 12     private OutputStream os; 13     /** 14      * 图片开始的标识 15      */ 16     private final String IMAGE_START = "image:"; 17     /** 18      * 一条完整信息结束的标识 19      */ 20     private final String MESSAGE_END = "over"; 21     /** 22      * 文件名结束的表示 23      */ 24     private final String FILE_NAME_END = "?"; 25  26     public SendImage(String ip, int port) throws Exception 27     { 28         socket = new Socket(ip, port); 29         os = socket.getOutputStream(); 30     } 31  32     public void send() 33     { 34         try 35         { 36             File imageFile = new File("F:/1.jpg"); 37             InputStream is = new FileInputStream(imageFile); 38              39             long fileLength = imageFile.length(); 40             System.out.println("图片长度:" +  fileLength); 41              42             /*发送第一部分的文本信息,对应text1*/ 43             os.write("要开始发送图片了哦".getBytes()); 44              45             /*发送图片开始的标识,对应image_start*/ 46             os.write(IMAGE_START.getBytes()); 47              48             /*发送图片文件名称,对应image_file_name*/ 49             os.write(imageFile.getName().getBytes()); 50              51             /*发送图片文件名称结束的标识,对应image_file_name_end*/ 52             os.write(FILE_NAME_END.getBytes()); 53              54             /*发送图片文件的长度,对应image_file_length*/ 55             byte[] bs = longToBytes(fileLength); 56             os.write(bs); 57              58             /*发送图片文件,对应image*/ 59             int length; 60             byte[] b = new byte[1024]; 61             while ((length = is.read(b)) > 0) 62             { 63                 os.write(b, 0, length); 64             } 65              66             /*发送第二部分文本信息,对应text2*/ 67             os.write("图片发送结束了".getBytes()); 68              69             /*发送一条完整信息结束的标识,对应message_end*/ 70             os.write(MESSAGE_END.getBytes()); 71         } 72         catch (Exception e) 73         { 74             e.printStackTrace(); 75         } 76     } 77      78     public void close() 79     { 80         try 81         { 82             os.close(); 83             socket.close(); 84         } 85         catch (Exception e) 86         { 87              88         } 89     } 90  91     /** 92      * 将长整型转换为byte数组 93      * @param n 94      * @return 95      */ 96     public static byte[] longToBytes(long n) 97     { 98         byte[] b = new byte[8]; 99         b[7] = (byte) (n & 0xff);100         b[6] = (byte) (n >> 8 & 0xff);101         b[5] = (byte) (n >> 16 & 0xff);102         b[4] = (byte) (n >> 24 & 0xff);103         b[3] = (byte) (n >> 32 & 0xff);104         b[2] = (byte) (n >> 40 & 0xff);105         b[1] = (byte) (n >> 48 & 0xff);106         b[0] = (byte) (n >> 56 & 0xff);107         return b;108     }109 110     public static void main(String[] args)111     {112         try113         {114             SendImage send = new SendImage("127.0.0.1", 40123);115             116             /*测试多次发送*/117             send.send();118             send.send();119             send.send();120             121             send.close();122         }123         catch (Exception e)124         {125             e.printStackTrace();126         }127     }128 }
复制代码

  图片接受端,使用的是服务器端来接收图片。类依然写的很简单。

  

复制代码
  1 package util;  2   3 import java.io.File;  4 import java.io.FileOutputStream;  5 import java.io.IOException;  6 import java.io.InputStream;  7 import java.net.ServerSocket;  8 import java.net.Socket;  9  10 public class ReceiveImage2 11 { 12     private ServerSocket ss; 13     private Thread listenThread; 14     /** 15      * 图片开始的标识 16      */ 17     private final String IMAGE_START = "image:"; 18     /** 19      * 一条完整信息结束的标识 20      */ 21     private final String MESSAGE_END = "over"; 22     /** 23      * 文件名结束的表示 24      */ 25     private final String FILE_NAME_END = "?"; 26     /** 27      * 默认的编码,我的是UTF-8,大家可以更改成自己的编码 28      */ 29     private final String DEFAULT_ENCODE = "UTF-8"; 30     /** 31      * ISO编码 32      */ 33     private final String ISO_ENCODE = "ISO-8859-1"; 34  35     public ReceiveImage2() throws Exception 36     { 37         ss = new ServerSocket(40123); 38  39         listenThread = new Thread(new Runnable() 40         { 41             @Override 42             public void run() 43             { 44                 listen(); 45             } 46         }); 47  48         listenThread.start(); 49     } 50  51     /** 52      * 监听链接 53      */ 54     private void listen() 55     { 56         while (!ss.isClosed()) 57         { 58             try 59             { 60                 final Socket s = ss.accept(); 61                 new Thread(new Runnable() 62                 { 63                     @Override 64                     public void run() 65                     { 66                         read(s); 67                     } 68                 }).start(); 69             } 70             catch (IOException e) 71             { 72                 e.printStackTrace(); 73             } 74         } 75     } 76  77     /** 78      * 读取信息 79      *  80      * @param socket 81      *            客户端链接过来的socket 82      */ 83     private void read(Socket socket) 84     { 85         try 86         { 87             InputStream is = socket.getInputStream(); 88             StringBuffer sb = new StringBuffer(); 89             int imageName = 0; 90             while (!socket.isClosed()) 91             { 92                 int imageStart; 93                 while ((imageStart = sb.indexOf(IMAGE_START)) < 0) 94                     readToBuffer(is, sb); 95  96                 System.out.println("开始读取第一部分文本信息"); 97                 String text1 = sb.substring(0, imageStart); 98                 text1 = new String(text1.getBytes(ISO_ENCODE), DEFAULT_ENCODE); 99                 System.out.println("第一部分文本信息:" + text1);100                 sb.delete(0, imageStart + IMAGE_START.length());101 102                 System.out.println("开始读取文件名称");103                 int file_name_end;104                 while ((file_name_end = sb.indexOf(FILE_NAME_END)) < 0)105                     readToBuffer(is, sb);106                 String file_name = new String(sb.substring(0, file_name_end).getBytes(ISO_ENCODE), DEFAULT_ENCODE);107                 System.out.println("文件名称:" + file_name);108                 sb.delete(0, file_name_end + FILE_NAME_END.length());109 110                 System.out.println("开始读取文件长度");111                 while (sb.length() < 8)112                     readToBuffer(is, sb);113                 String imageLengthString = sb.substring(0, 8);114                 byte[] imageLengthByteArray = imageLengthString.getBytes(ISO_ENCODE);115                 long imageLength = bytesToLong(imageLengthByteArray);116                 System.out.println("文件长度:" + imageLength);117                 sb.delete(0, 8);118 119                 System.out.println("开始读取文件");120                 byte[] image = sb.toString().getBytes(ISO_ENCODE);121                 FileOutputStream fos = new FileOutputStream(new File("F:/接收文件" + imageName + file_name));122                 if (imageLength > image.length)123                 {124                     System.out.println("文件只有部分在数组中");125                     fos.write(image);126                     System.out.println("已经写了" + image.length + "还需要写" + (imageLength - image.length));127                     writeImage(is, fos, imageLength - image.length);128                     sb.delete(0, sb.length());129                 }130                 else131                 {132                     System.out.println("文件已经在数组中");133                     fos.write(image, 0, (int) imageLength);134                     sb.delete(0, (int) imageLength);135                 }136                 fos.close();137                 imageName++;138                 System.out.println("文件已经保存");139 140                 int end;141                 while ((end = sb.indexOf(MESSAGE_END)) < 0)142                 {143                     readToBuffer(is, sb);144                 }145 146                 String text2 = new String(sb.substring(0, end).getBytes(ISO_ENCODE), DEFAULT_ENCODE);147                 System.out.println("第二部分文本信息:" + text2);148                 sb.delete(0, end + MESSAGE_END.length());149             }150         }151         catch (Exception e)152         {153             e.printStackTrace();154         }155         finally156         {157             try158             {159                 socket.close();160             }161             catch (IOException e)162             {163                 164             }165             System.out.println("线程结束");166         }167 168     }169 170     /**171      * 将输入流中的数据读取到stringbuffer中,一次最多读取1024个长度172      * 173      * @param is174      *            输入流175      * @param sb176      *            图片文件输出流177      * @throws Exception178      */179     private void readToBuffer(InputStream is, StringBuffer sb) throws Exception180     {181         int readLength;182         byte[] b = new byte[1024];183 184         readLength = is.read(b);185         if (readLength == -1)186             throw new RuntimeException("读取到了-1,说明Socket已经关闭");187         String s = new String(b, 0, readLength, ISO_ENCODE);188         sb.append(s);189     }190 191     /**192      * 从输入流中读取图片信息到图片文件输出流中193      * 194      * @param is195      *            输入流196      * @param fos197      *            图片文件输出流198      * @param length199      *            需要读取的数据长度200      * @throws Exception201      */202     private void writeImage(InputStream is, FileOutputStream fos, long length) throws Exception203     {204         byte[] imageByte = new byte[1024];205         int oneTimeReadLength;206 207         for (long readLength = 0; readLength < length;)208         {209             if (readLength + imageByte.length <= length)210             {211                 System.out.println("剩余的字节数大于1024,将尽可能多的读取内容");212                 oneTimeReadLength = is.read(imageByte);213             }214             else215             {216                 System.out.println("剩余的字节数小于1024,将只读取" + (length - readLength) + "字节");217                 oneTimeReadLength = is.read(imageByte, 0, (int) (length - readLength));218             }219 220             if (oneTimeReadLength == -1)221                 throw new RuntimeException("读取文件时,读取到了-1,说明Socket已经结束");222             System.out.println("实际读取长度" + oneTimeReadLength + "字节");223 224             readLength += oneTimeReadLength;225 226             fos.write(imageByte, 0, oneTimeReadLength);227             System.out.println("继续追加" + readLength + "字节长度");228         }229     }230 231     /**232      * 将byte数组转化为Long类型233      * 234      * @param array235      * @return236      */237     public static long bytesToLong(byte[] array)238     {239         return ((((long) array[0] & 0xff) << 56) | (((long) array[1] & 0xff) << 48) | (((long) array[2] & 0xff) << 40)240                 | (((long) array[3] & 0xff) << 32) | (((long) array[4] & 0xff) << 24)241                 | (((long) array[5] & 0xff) << 16) | (((long) array[6] & 0xff) << 8) | (((long) array[7] & 0xff) << 0));242     }243 244     public static void main(String[] args)245     {246         try247         {248             new ReceiveImage2();249         }250         catch (Exception e)251         {252             e.printStackTrace();253         }254     }255 }
复制代码

  客户端并没有什么太难理解的地方,按照规定的格式发送内容就可以了。下面主要说一说服务器端接收图片。

  首先先说一下关于Long类型转字节数组和字节数组转Long类型,我是直接使用别人已经实现的方法。链接如下:http://www.cnblogs.com/devinzhang/archive/2012/09/28/2707605.html。

  因为图片文件是二进制信息,所以传统的使用一个标志符来标志一个图片文件的结束并不合适,因为图片文件的二进制信息中,可能存在这个结束标识符,这样就会造成图片文件内容不全的状况(base64转码可以解决这个问题,但是我们不讨论base64)。所以,我们在文件内容之前先发送文件的消息,我们根据接收来大小来确定图片文件的长度。

  在代码中,之所以使用到了ISO编码,是因为UTF-8是可变长度的编码,原来的字节数组就被改变了。而ISO8859-1通常叫做Latin-1,Latin-1包括了书写所有西方欧洲语言不可缺少的附加字符,其中0~127的字符与ASCII码相同,它是单字节的编码方式,这样第二种方式生成的String里的字节数组就跟原来的字节数组一样。在new String使用其他编码如GBK,GB2312的话一样也会导致字节数组发生变化,因此要想获取String里单字节数组,就应该使用ISO8859-1编码。

  例如

复制代码
 1 byte[] b = new byte[8]; 2 b[6] = 5; 3 b[7] = -4; 4  5 for (int i = 0; i < b.length; i++) 6 { 7     System.out.println(b[i]); 8 } 9 10 System.out.println("---------华丽丽的分割线----------------");11 String s = null;12 s = new String(b);13 14 byte[] b2 = null;15 b2 = s.getBytes();16 17 for (int i = 0; i < b2.length; i++)18 {19     System.out.println(b2[i]);20 }
复制代码

  输出的结果:

  

  使用ISO编码

复制代码
 1 byte[] b = new byte[8]; 2 b[6] = 5; 3 b[7] = -4; 4  5 for (int i = 0; i < b.length; i++) 6 { 7     System.out.println(b[i]); 8 } 9 10 System.out.println("---------华丽丽的分割线----------------");11 String s = null;12 s = new String(b,"ISO-8859-1");13 14 byte[] b2 = null;15 b2 = s.getBytes("ISO-8859-1");16 17 for (int i = 0; i < b2.length; i++)18 {19     System.out.println(b2[i]);20 }
复制代码

  结果如下:

  

  下面,讲一讲接收端的接收一次的逻辑。

  1.会接收一些数据,如果这些数据中不存在图片文件开始的标识,则继续读取。当读取的数据中存在图片文件开始的标识,则将图片文件开始标识前的信息输出,并且删除以及删除图片文件开始标识。

  2.判断stringbuffer中是否存在文件名称结束的标识,如果没有,则继续读取信息,否则保存并输出文件名称。删除stringbuffer中图片名称和图片名称结束的标识。

  3.判断stringbuffer中是否有8个以上的字节数据,如果没有,则继续读取,否则获取8个字节的数据作为图片文件的长度。删除stringbuffer中图片文件的长度。

  4.判断stringbuffer的长度是否大于图片文件长度,如果大于,说明图片文件内容都在stringbuffer中,则从stringbuffer中提取图片文件大小的长度作为文件信息,否则将stringbuffer中的内容输出到文件输出流,在从Socket输入流中读取剩余的图片文件内容。删除stringbuffer中图片文件内容。

  5.判断stringbuffer中是否存在一条完整信息结束的标识,如果没有,则继续读取,否则截取完整信息结束标识前的信息输出。

 

  文笔稀烂,大家凑活着看,如果看不懂我写的东西,就看代码吧。

0 0