Java使用Socket進行字符串和圖片文件同時傳輸


  最近開發中使用到將字符串和圖片同時傳輸的功能。我這邊是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         try
113         {
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                 else
131                 {
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         finally
156         {
157             try
158             {
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 is
174      *            輸入流
175      * @param sb
176      *            圖片文件輸出流
177      * @throws Exception
178      */
179     private void readToBuffer(InputStream is, StringBuffer sb) throws Exception
180     {
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 is
195      *            輸入流
196      * @param fos
197      *            圖片文件輸出流
198      * @param length
199      *            需要讀取的數據長度
200      * @throws Exception
201      */
202     private void writeImage(InputStream is, FileOutputStream fos, long length) throws Exception
203     {
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             else
215             {
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 array
235      * @return
236      */
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         try
247         {
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中是否存在一條完整信息結束的標識,如果沒有,則繼續讀取,否則截取完整信息結束標識前的信息輸出。

 

  文筆稀爛,大家湊活着看,如果看不懂我寫的東西,就看代碼吧。

  

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM