您的位置:首页 > 编程语言 > Java开发

Java Socket连续传输多张图片(不断开链接)

2016-11-02 16:54 1356 查看
一、问题描述

我们在做图片传输时,通常都是打开一个http请求,然后去下载一张图片,下载完成后,这个链接就断开了,在这样的情况下,我们最多允许传送一张图片,那么,像刚才那样的做法就不行了。而我的需求是,比如我有100张图片,我想传输完一百张图片在断开链接,或者更夸张点,我做了一个屏幕广播程序,需要把截图来的图片一张张的发给客户端,不想断开,也不能断开,然后我想什么时候断开就断开,不传图片我也不断开。这样的需求,用http请求就不太划算了,因为不断的请求链接然后在断开,是会降低传输效率的。那么有什么办法可以实现像刚才那样的需求呢,办法肯定是有的,接下来,我将我所积累的方法告诉大家。

二、方法

说到方法,那么我们就要用最原始的通信工具了,那就是Socket,要实现在一个链接上不停的传输图片(比如屏幕广播程序),就需要自定义一个应用层协议,才能实现样的需求,废话不多说,请看我自己定义的协议:

图片数据包头(8byte) | 图片编号(4byte) | 图片名称长度(4byte) | 图片名称 | 图片字节数据长度(8byte) | 图片数据 | 奇偶校验(4byte)


协议解释:

图片数据包头:定义这个包头就是表示我这是一张图片数据的开始,当我找到这个头之后,才可以去读取图片数据信息,不是随便找一个字节就开始读,那样的话,你永远也得不到一张图片。包头是你自己随意定义的一段特殊的数据,这样才可以和图片数据分开。

图片编号:为啥是4byte呢,其实它就是一个int型的值,协议中我写了这些单位的,都是Java的基本数据类型(除包头和奇偶校验外),相应的,图片数据 长度就是long型。

别的协议段不再解释,相信都看得懂,如果你的图片编号超过了int能承受的值,可以使用8byte,也就是long类型。有了这个协议,那么我们就可以传输图片了。

三、实现代码

1、先定义好协议包头,这个是随机的,想怎么定义就怎么定义,越特殊越好。

//协议包头和奇偶校验
public final static byte[] PICTURE_PACKAGE_HEAD = {(byte) 0xFF, (byte) 0xCF,
(byte) 0xFA, (byte) 0xBF, (byte) 0xF6, (byte) 0xAF, (byte) 0xFE,
(byte) 0xFF};
public final static byte[] PICTURE_PACKAGE_END = {(byte) 0xEF, (byte) 0xDA,
(byte) 0xFF, (byte) 0xFD};


2、打包协议,并通过socket发送出去。这里我们使用Java的工具类DataOutputStream,它可以发送基本数据类型数据,这样用起来方便,效率也高。

/**
* 假如图片数据为以下内容
* int pngNumber = 1001;
* String pngName = "Image01.png";
* byte[] pictures; //图片数据,如果不是byte数组,可用工具类进行转化
**/
DataOutputStream outputData = new DataOutputStream(pictureSocket.getOutputStream());
//发送图片头
outputData.write(PICTURE_PACKAGE_HEAD);
//发送图片编号
outputData.writeInt(pngNumber);
//发送图片名称长度,注意这个长度不是字符个数的长度,而是转化为byte之后的长度,不能弄错了
outputData.writeInt(pngName.getBytes().length);
//发送图片名称
outputData.write(pngName.getBytes());
//发送图片数据长度
outputData.writeLong(pictures.length);
//发送图片
outputData.write(pictures, 0, pictures.length);
//发送包尾
outputData.write(Command.PICTURE_PACKAGE_END);


当你建立好TCP链接后,就可以使用刚才那段代码进行发送图片数据。

3、客户端接收图片数据,并进行解析。

/**
* 解析图片数据
**/
public void receivePicture() {
//判断输入流是否为null
if (pictureSocket == null)
return;
try {
//标志头状态
boolean isHead = true;
//循环读取PICTURE_PACKAGE_HEAD.length个字节,并判断是否和我们定义的头相同
for (int i = 0; i < PICTURE_PACKAGE_HEAD.length; ++i) {
byte head = (byte) inputStream.read();
//如果不相同,那么结束循环,并丢弃这个字节
if (head != Command.PICTURE_PACKAGE_HEAD[i]) {
isHead = false;
break;
}
}
//如果找到头了,那么下一个字节就是图片数据的开始
if (isHead) {
DataInputStream inputData = new DataInputStream(inputStream);
//读取图片编号
int picNumber = inputData.readInt();
//读取图片名称长度
int picNameLen = inputData.readInt();
//读取图片名称
byte[] data = new byte[picNameLen];
inputData.readFully(data);
//将字节转化为字符
String picName = new String(data,0,data.length);
//释放data
data = null;
//读取图片数据长度
long picLeng = inputData.writeLong();
//new 一个字节数据输出流
ByteArrayOutputStream fos = new ByteArrayOutputStream();
//每一次读CACHE_SIZE个字节,CACHE_SIZE可以自定义,我的是1024 * 2
byte[] buffer = new byte[CACHE_SIZE];
int len = -1;
//循环读取
while (picLeng > 0
&& (len = inputData.read(buffer, 0,
picLeng < buffer.length ? (int) picLeng
: buffer.length)) != -1) {
fos.write(buffer, 0, len);
fos.flush();
//每读取后,picLeng的值要减去len个,直到picLeng = 0
picLeng -= len;
}
fos.flush();
buffer = null;

// 如果发现picLeng 不为0,说明图片接收不完整,那么就放弃这段数据
if (picLeng > 0) {
return;
}
//如果以上操作都成功,那么就取出图片数据
byte[] pictures = fos.toByteArray();
//然后使用相应的工具类就可以把pictures转化为图片了

fos.close();
fos = null;
}
//最后做一下奇偶校验,这里不在写,因为它可有可无,是为了防止包头数据和图片数据冲突,建立的尾部缓冲
} catch (IOException e) {
e.printStackTrace();
}
}


通过以上两个方法,我们可以实现在同一个链接里循环的发送图片,而且效率也很高。在这里,我使用的时DataInputStream和DataOutputStream这个两个类,有的项目不允许使用这两个类,那么你也可以只读取相应的字节数,然后在转化为int,long,string等类型的数据。

如果想要更高的效率,可以使用两个子线程去处理图片数据,一个线程负责接收数据,因为read方法是阻塞的,这样就需要建立一个数据队列,另一个线程不断的到这个队列里去取数据,我试过这样的做法,对技术的要求比较高,特别的对于内存的控制更严格。在嵌入式设备上更是一种挑战。有兴趣的可以去试一试。

由于代码量大,不便再此贴出,只贴出核心函数,各位理解这个思想,便可以自己去写,如果实在没有看明白的,可以与我讨论。

我在做的过程中还遇到一个问题,就是我客户端和服务端不仅要发送图片数据,我还要发送一些消息,也就是说不仅仅是图片数据,还有别的,那么这种情况下,在一个通道里传两种数据,肯定是会出事的,所以最好的办法就是:建立两个tcp链接通道,一个专门发送图片数据,一个专门用于发送消息数据。这样便可以解决刚才的问题。

注意:我在网上看到有的网友直接用DataInputStream去图片数据,而不定义数据包头,如果它读取的数据刚好不是你所写的数据,那么这个数据读出来肯定是错误的,所以一定要找到头之后,才能取数据。

由于小编技术能力有限,文中难免存在错误,还望指正,谢谢合作。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  socket java 图片