Java使用Socket进行字符串和图片文件同时传输
2013-01-07 13:47
866 查看
最近开发中使用到将字符串和图片同时传输的功能。我这边是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中,发送图片的是客户端。类写的比较简单。
图片接受端,使用的是服务器端来接收图片。类依然写的很简单。
客户端并没有什么太难理解的地方,按照规定的格式发送内容就可以了。下面主要说一说服务器端接收图片。
首先先说一下关于Long类型转字节数组和字节数组转Long类型,我是直接使用别人已经实现的方法。链接如下:/article/4783970.html。
因为图片文件是二进制信息,所以传统的使用一个标志符来标志一个图片文件的结束并不合适,因为图片文件的二进制信息中,可能存在这个结束标识符,这样就会造成图片文件内容不全的状况(base64转码可以解决这个问题,但是我们不讨论base64)。所以,我们在文件内容之前先发送文件的消息,我们根据接收来大小来确定图片文件的长度。
在代码中,之所以使用到了ISO编码,是因为UTF-8是可变长度的编码,原来的字节数组就被改变了。而ISO8859-1通常叫做Latin-1,Latin-1包括了书写所有西方欧洲语言不可缺少的附加字符,其中0~127的字符与ASCII码相同,它是单字节的编码方式,这样第二种方式生成的String里的字节数组就跟原来的字节数组一样。在new String使用其他编码如GBK,GB2312的话一样也会导致字节数组发生变化,因此要想获取String里单字节数组,就应该使用ISO8859-1编码。
例如
输出的结果:
![](http://images.cnitblog.com/blog/440501/201301/07132618-7109798af3334a65800f9f08ac4d63b5.png)
使用ISO编码
结果如下:
![](http://images.cnitblog.com/blog/440501/201301/07132853-e5694e1497354342a2a2a27428293736.png)
下面,讲一讲接收端的接收一次的逻辑。
1.会接收一些数据,如果这些数据中不存在图片文件开始的标识,则继续读取。当读取的数据中存在图片文件开始的标识,则将图片文件开始标识前的信息输出,并且删除以及删除图片文件开始标识。
2.判断stringbuffer中是否存在文件名称结束的标识,如果没有,则继续读取信息,否则保存并输出文件名称。删除stringbuffer中图片名称和图片名称结束的标识。
3.判断stringbuffer中是否有8个以上的字节数据,如果没有,则继续读取,否则获取8个字节的数据作为图片文件的长度。删除stringbuffer中图片文件的长度。
4.判断stringbuffer的长度是否大于图片文件长度,如果大于,说明图片文件内容都在stringbuffer中,则从stringbuffer中提取图片文件大小的长度作为文件信息,否则将stringbuffer中的内容输出到文件输出流,在从Socket输入流中读取剩余的图片文件内容。删除stringbuffer中图片文件内容。
5.判断stringbuffer中是否存在一条完整信息结束的标识,如果没有,则继续读取,否则截取完整信息结束标识前的信息输出。
文笔稀烂,大家凑活着看,如果看不懂我写的东西,就看代码吧。
网络上关于图片的传输一般有两种方式,一个是通过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中,发送图片的是客户端。类写的比较简单。
package util; import java.io.File; import java.io.FileInputStream; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; public class SendImage { private Socket socket; private OutputStream os; /** * 图片开始的标识 */ private final String IMAGE_START = "image:"; /** * 一条完整信息结束的标识 */ private final String MESSAGE_END = "over"; /** * 文件名结束的表示 */ private final String FILE_NAME_END = "?"; public SendImage(String ip, int port) throws Exception { socket = new Socket(ip, port); os = socket.getOutputStream(); } public void send() { try { File imageFile = new File("F:/1.jpg"); InputStream is = new FileInputStream(imageFile); long fileLength = imageFile.length(); System.out.println("图片长度:" + fileLength); /*发送第一部分的文本信息,对应text1*/ os.write("要开始发送图片了哦".getBytes()); /*发送图片开始的标识,对应image_start*/ os.write(IMAGE_START.getBytes()); /*发送图片文件名称,对应image_file_name*/ os.write(imageFile.getName().getBytes()); /*发送图片文件名称结束的标识,对应image_file_name_end*/ os.write(FILE_NAME_END.getBytes()); /*发送图片文件的长度,对应image_file_length*/ byte[] bs = longToBytes(fileLength); os.write(bs); /*发送图片文件,对应image*/ int length; byte[] b = new byte[1024]; while ((length = is.read(b)) > 0) { os.write(b, 0, length); } /*发送第二部分文本信息,对应text2*/ os.write("图片发送结束了".getBytes()); /*发送一条完整信息结束的标识,对应message_end*/ os.write(MESSAGE_END.getBytes()); } catch (Exception e) { e.printStackTrace(); } } public void close() { try { os.close(); socket.close(); } catch (Exception e) { } } /** * 将长整型转换为byte数组 * @param n * @return */ public static byte[] longToBytes(long n) { byte[] b = new byte[8]; b[7] = (byte) (n & 0xff); b[6] = (byte) (n >> 8 & 0xff); b[5] = (byte) (n >> 16 & 0xff); b[4] = (byte) (n >> 24 & 0xff); b[3] = (byte) (n >> 32 & 0xff); b[2] = (byte) (n >> 40 & 0xff); b[1] = (byte) (n >> 48 & 0xff); b[0] = (byte) (n >> 56 & 0xff); return b; } public static void main(String[] args) { try { SendImage send = new SendImage("127.0.0.1", 40123); /*测试多次发送*/ send.send(); send.send(); send.send(); send.close(); } catch (Exception e) { e.printStackTrace(); } } }
图片接受端,使用的是服务器端来接收图片。类依然写的很简单。
package util; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.ServerSocket; import java.net.Socket; public class ReceiveImage2 { private ServerSocket ss; private Thread listenThread; /** * 图片开始的标识 */ private final String IMAGE_START = "image:"; /** * 一条完整信息结束的标识 */ private final String MESSAGE_END = "over"; /** * 文件名结束的表示 */ private final String FILE_NAME_END = "?"; /** * 默认的编码,我的是UTF-8,大家可以更改成自己的编码 */ private final String DEFAULT_ENCODE = "UTF-8"; /** * ISO编码 */ private final String ISO_ENCODE = "ISO-8859-1"; public ReceiveImage2() throws Exception { ss = new ServerSocket(40123); listenThread = new Thread(new Runnable() { @Override public void run() { listen(); } }); listenThread.start(); } /** * 监听链接 */ private void listen() { while (!ss.isClosed()) { try { final Socket s = ss.accept(); new Thread(new Runnable() { @Override public void run() { read(s); } }).start(); } catch (IOException e) { e.printStackTrace(); } } } /** * 读取信息 * * @param socket * 客户端链接过来的socket */ private void read(Socket socket) { try { InputStream is = socket.getInputStream(); StringBuffer sb = new StringBuffer(); int imageName = 0; while (!socket.isClosed()) { int imageStart; while ((imageStart = sb.indexOf(IMAGE_START)) < 0) readToBuffer(is, sb); System.out.println("开始读取第一部分文本信息"); String text1 = sb.substring(0, imageStart); text1 = new String(text1.getBytes(ISO_ENCODE), DEFAULT_ENCODE); System.out.println("第一部分文本信息:" + text1); sb.delete(0, imageStart + IMAGE_START.length()); System.out.println("开始读取文件名称"); int file_name_end; while ((file_name_end = sb.indexOf(FILE_NAME_END)) < 0) readToBuffer(is, sb); String file_name = new String(sb.substring(0, file_name_end).getBytes(ISO_ENCODE), DEFAULT_ENCODE); System.out.println("文件名称:" + file_name); sb.delete(0, file_name_end + FILE_NAME_END.length()); System.out.println("开始读取文件长度"); while (sb.length() < 8) readToBuffer(is, sb); String imageLengthString = sb.substring(0, 8); byte[] imageLengthByteArray = imageLengthString.getBytes(ISO_ENCODE); long imageLength = bytesToLong(imageLengthByteArray); System.out.println("文件长度:" + imageLength); sb.delete(0, 8); System.out.println("开始读取文件"); byte[] image = sb.toString().getBytes(ISO_ENCODE); FileOutputStream fos = new FileOutputStream(new File("F:/接收文件" + imageName + file_name)); if (imageLength > image.length) { System.out.println("文件只有部分在数组中"); fos.write(image); System.out.println("已经写了" + image.length + "还需要写" + (imageLength - image.length)); writeImage(is, fos, imageLength - image.length); sb.delete(0, sb.length()); } else { System.out.println("文件已经在数组中"); fos.write(image, 0, (int) imageLength); sb.delete(0, (int) imageLength); } fos.close(); imageName++; System.out.println("文件已经保存"); int end; while ((end = sb.indexOf(MESSAGE_END)) < 0) { readToBuffer(is, sb); } String text2 = new String(sb.substring(0, end).getBytes(ISO_ENCODE), DEFAULT_ENCODE); System.out.println("第二部分文本信息:" + text2); sb.delete(0, end + MESSAGE_END.length()); } } catch (Exception e) { e.printStackTrace(); } finally { try { socket.close(); } catch (IOException e) { } System.out.println("线程结束"); } } /** * 将输入流中的数据读取到stringbuffer中,一次最多读取1024个长度 * * @param is * 输入流 * @param sb * 图片文件输出流 * @throws Exception */ private void readToBuffer(InputStream is, StringBuffer sb) throws Exception { int readLength; byte[] b = new byte[1024]; readLength = is.read(b); if (readLength == -1) throw new RuntimeException("读取到了-1,说明Socket已经关闭"); String s = new String(b, 0, readLength, ISO_ENCODE); sb.append(s); } /** * 从输入流中读取图片信息到图片文件输出流中 * * @param is * 输入流 * @param fos * 图片文件输出流 * @param length * 需要读取的数据长度 * @throws Exception */ private void writeImage(InputStream is, FileOutputStream fos, long length) throws Exception { byte[] imageByte = new byte[1024]; int oneTimeReadLength; for (long readLength = 0; readLength < length;) { if (readLength + imageByte.length <= length) { System.out.println("剩余的字节数大于1024,将尽可能多的读取内容"); oneTimeReadLength = is.read(imageByte); } else { System.out.println("剩余的字节数小于1024,将只读取" + (length - readLength) + "字节"); oneTimeReadLength = is.read(imageByte, 0, (int) (length - readLength)); } if (oneTimeReadLength == -1) throw new RuntimeException("读取文件时,读取到了-1,说明Socket已经结束"); System.out.println("实际读取长度" + oneTimeReadLength + "字节"); readLength += oneTimeReadLength; fos.write(imageByte, 0, oneTimeReadLength); System.out.println("继续追加" + readLength + "字节长度"); } } /** * 将byte数组转化为Long类型 * * @param array * @return */ public static long bytesToLong(byte[] array) { return ((((long) array[0] & 0xff) << 56) | (((long) array[1] & 0xff) << 48) | (((long) array[2] & 0xff) << 40) | (((long) array[3] & 0xff) << 32) | (((long) array[4] & 0xff) << 24) | (((long) array[5] & 0xff) << 16) | (((long) array[6] & 0xff) << 8) | (((long) array[7] & 0xff) << 0)); } public static void main(String[] args) { try { new ReceiveImage2(); } catch (Exception e) { e.printStackTrace(); } } }
客户端并没有什么太难理解的地方,按照规定的格式发送内容就可以了。下面主要说一说服务器端接收图片。
首先先说一下关于Long类型转字节数组和字节数组转Long类型,我是直接使用别人已经实现的方法。链接如下:/article/4783970.html。
因为图片文件是二进制信息,所以传统的使用一个标志符来标志一个图片文件的结束并不合适,因为图片文件的二进制信息中,可能存在这个结束标识符,这样就会造成图片文件内容不全的状况(base64转码可以解决这个问题,但是我们不讨论base64)。所以,我们在文件内容之前先发送文件的消息,我们根据接收来大小来确定图片文件的长度。
在代码中,之所以使用到了ISO编码,是因为UTF-8是可变长度的编码,原来的字节数组就被改变了。而ISO8859-1通常叫做Latin-1,Latin-1包括了书写所有西方欧洲语言不可缺少的附加字符,其中0~127的字符与ASCII码相同,它是单字节的编码方式,这样第二种方式生成的String里的字节数组就跟原来的字节数组一样。在new String使用其他编码如GBK,GB2312的话一样也会导致字节数组发生变化,因此要想获取String里单字节数组,就应该使用ISO8859-1编码。
例如
byte[] b = new byte[8]; b[6] = 5; b[7] = -4; for (int i = 0; i < b.length; i++) { System.out.println(b[i]); } System.out.println("---------华丽丽的分割线----------------"); String s = null; s = new String(b); byte[] b2 = null; b2 = s.getBytes(); for (int i = 0; i < b2.length; i++) { System.out.println(b2[i]); }
输出的结果:
![](http://images.cnitblog.com/blog/440501/201301/07132618-7109798af3334a65800f9f08ac4d63b5.png)
使用ISO编码
byte[] b = new byte[8]; b[6] = 5; b[7] = -4; for (int i = 0; i < b.length; i++) { System.out.println(b[i]); } System.out.println("---------华丽丽的分割线----------------"); String s = null; s = new String(b,"ISO-8859-1"); byte[] b2 = null; b2 = s.getBytes("ISO-8859-1"); for (int i = 0; i < b2.length; i++) { System.out.println(b2[i]); }
结果如下:
![](http://images.cnitblog.com/blog/440501/201301/07132853-e5694e1497354342a2a2a27428293736.png)
下面,讲一讲接收端的接收一次的逻辑。
1.会接收一些数据,如果这些数据中不存在图片文件开始的标识,则继续读取。当读取的数据中存在图片文件开始的标识,则将图片文件开始标识前的信息输出,并且删除以及删除图片文件开始标识。
2.判断stringbuffer中是否存在文件名称结束的标识,如果没有,则继续读取信息,否则保存并输出文件名称。删除stringbuffer中图片名称和图片名称结束的标识。
3.判断stringbuffer中是否有8个以上的字节数据,如果没有,则继续读取,否则获取8个字节的数据作为图片文件的长度。删除stringbuffer中图片文件的长度。
4.判断stringbuffer的长度是否大于图片文件长度,如果大于,说明图片文件内容都在stringbuffer中,则从stringbuffer中提取图片文件大小的长度作为文件信息,否则将stringbuffer中的内容输出到文件输出流,在从Socket输入流中读取剩余的图片文件内容。删除stringbuffer中图片文件内容。
5.判断stringbuffer中是否存在一条完整信息结束的标识,如果没有,则继续读取,否则截取完整信息结束标识前的信息输出。
文笔稀烂,大家凑活着看,如果看不懂我写的东西,就看代码吧。
相关文章推荐
- Java使用Socket进行字符串和图片文件同时传输
- Python---对html文件内容进行搜索取出特定URL地址字符串,保存成列表,并使用每个url下载图片,并保存到硬盘上,使用正则re
- JAVA 使用springMVC 上传多张图片或文件,并对图片进行按比例缩放处理
- 笔记:使用json传输图片,根据个人经验:我做不到,想了个办法将文件转变成字符串并压缩
- android c#.net使用socket进行局域网多文件传输
- Java的Socket连接同时传输图片、文本等多种信息
- JAVA中使用RSA通过秘钥文件对字符串进行加密解密
- Python---对html文件内容进行搜索取出特定URL地址字符串,保存成列表,并使用每个url下载图片,并保存到硬盘上,使用bs4,beautifulsoup模块
- java socket通信-传输文件图片
- java使用文件或字节数组方式加载图片,并修改图片后进行保存、输出等
- java中图片文件的传输及显示(Socket以及ServerSocket演示)
- Android使用Socket(Tcp/Udp)协议进行数据传输(传输大文件)
- 详解Android使用Socket对大文件进行加密传输
- Java使用Socket通信传输文件的方法示例
- Java Socket图片文件传输
- Java使用Socket传输文件遇到的问题
- Java使用Socket传输文件遇到的问题
- 使用Java的Socket套接字实现echo和大文件传输
- Java使用Socket传输文件遇到的问题(转)
- Java TCP使用Socket进行网络图片传送(6)