java socket字符串_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 packageutil;2

3 importjava.io.File;4 importjava.io.FileInputStream;5 importjava.io.InputStream;6 importjava.io.OutputStream;7 importjava.net.Socket;8

9 public classSendImage10 {11 privateSocket socket;12 privateOutputStream 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) throwsException27 {28 socket = newSocket(ip, port);29 os =socket.getOutputStream();30 }31

32 public voidsend()33 {34 try

35 {36 File imageFile = new File("F:/1.jpg");37 InputStream is = newFileInputStream(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 intlength;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 voidclose()79 {80 try

81 {82 os.close();83 socket.close();84 }85 catch(Exception e)86 {87

88 }89 }90

91 /**

92 * 将长整型转换为byte数组93 *@paramn94 *@return

95 */

96 public static byte[] longToBytes(longn)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 returnb;108 }109

110 public static voidmain(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 packageutil;2

3 importjava.io.File;4 importjava.io.FileOutputStream;5 importjava.io.IOException;6 importjava.io.InputStream;7 importjava.net.ServerSocket;8 importjava.net.Socket;9

10 public classReceiveImage211 {12 privateServerSocket ss;13 privateThread 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() throwsException36 {37 ss = new ServerSocket(40123);38

39 listenThread = new Thread(newRunnable()40 {41 @Override42 public voidrun()43 {44 listen();45 }46 });47

48 listenThread.start();49 }50

51 /**

52 * 监听链接53 */

54 private voidlisten()55 {56 while (!ss.isClosed())57 {58 try

59 {60 final Socket s =ss.accept();61 new Thread(newRunnable()62 {63 @Override64 public voidrun()65 {66 read(s);67 }68 }).start();69 }70 catch(IOException e)71 {72 e.printStackTrace();73 }74 }75 }76

77 /**

78 * 读取信息79 *80 *@paramsocket81 * 客户端链接过来的socket82 */

83 private voidread(Socket socket)84 {85 try

86 {87 InputStream is =socket.getInputStream();88 StringBuffer sb = newStringBuffer();89 int imageName = 0;90 while (!socket.isClosed())91 {92 intimageStart;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 = newString(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 intfile_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 intend;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 *@paramis174 * 输入流175 *@paramsb176 * 图片文件输出流177 *@throwsException178 */

179 private void readToBuffer(InputStream is, StringBuffer sb) throwsException180 {181 intreadLength;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 *@paramis195 * 输入流196 *@paramfos197 * 图片文件输出流198 *@paramlength199 * 需要读取的数据长度200 *@throwsException201 */

202 private void writeImage(InputStream is, FileOutputStream fos, long length) throwsException203 {204 byte[] imageByte = new byte[1024];205 intoneTimeReadLength;206

207 for (long readLength = 0; readLength

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 *@paramarray235 *@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 voidmain(String[] args)245 {246 try

247 {248 newReceiveImage2();249 }250 catch(Exception e)251 {252 e.printStackTrace();253 }254 }255 }

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

因为图片文件是二进制信息,所以传统的使用一个标志符来标志一个图片文件的结束并不合适,因为图片文件的二进制信息中,可能存在这个结束标识符,这样就会造成图片文件内容不全的状况(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 = newString(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 }

输出的结果:

java socket字符串_Java使用Socket进行字符串和图片文件同时传输_第1张图片

使用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 }

结果如下:

java socket字符串_Java使用Socket进行字符串和图片文件同时传输_第2张图片

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

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

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

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

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

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

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

你可能感兴趣的:(java,socket字符串)