最近在写Android的时候用到socket来传输多个文件,在网上找了不少方案,自己也试了一些,发现大多数方案存在一些问题。
这些问题是:
1. 每传输一个新的文件都要关闭旧的socket建一个新的socket,这样效率不高,也会产生一系列bug。能不能在一个socket连接里面把所有文件传完?
2. 很多方案并没有传输文件名,而是采取在接收端直接按照预知的文件类型创建随机的文件名。这样的好处是不用单独处理文件名的传输,但带来了兼容性的问题。比如在传输图片的时候,不止有jpg、png的文件类型,可能还有3gp等更多的文件类型,此时如果他们的后缀不能互换,假如发送端是3gp接收端直接保存为jpg,会不会使保存的文件无法打开?还有,同一个socket链接也不能同时处理不能类型的文件,比如我要同时传送图片和音频,这种方案显然也是不行的。所以我认为为了兼容性,文件名还是要传的。
3. 还有就是文件名何时传输的问题。是在一开始先把所有文件名先传过去,还是传一个文件名加一个文件,再传一个文件加一个文件,以此类推。我觉得这个是无所谓的,看个人选择。
最终方案选择:
我最后定下来的就是,先传所有文件总类型和文件数量。文件总类型指的是,举个例子,比如这次传输是只传了图片还是图片和音频混着来。文件数量顾名思义,这次一共要传多少个文件。
然后分别传每个文件。每个文件的传输分为文件头信息和文件本身。文件头信息包括“start”标签和文件名和文件长度。
下面来看具体的代码:
首先是发送端:
连接的建立就不赘述了,每个程序情况都不一样,不清楚的按问题谷歌/百度,这篇文章的重点不在此。直接看如何发送。
//所有的文件名都放在list里面
String[] fileNames = intent.getStringArrayExtra("FILE_LIST");
//dos是输出流,这一行放在这里怕看代码的人看不到引起误会
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
//写入文件总类型和文件数
dos.writeInt(requestCode);
dos.writeInt(fileNames.length);
File file;
//每一个循环就会发送一个文件
for (int i=0; i<fileNames.length; i++){
...
//省略过程,到这一步你要得到需要的文件实例和文件输出流和文件长度
file = new File(filePath);
fileLength = (int)file.length();
FileInputStream fis = (FileInputStream)is;
//写入文件头信息,要保证头文件信息长度一致!!记得规范一下头文件格式
String fileMessage = String.format("Start--%-128s--%012d",file.getName(),file.length());
dos.write(fileMessage.getBytes(),0,fileMessage.getBytes().length);
dos.flush();
//写入文件信息
byte[] bytes = new byte[1024];
int length = 0;
long progress = 0;
while((length = fis.read(bytes, 0, bytes.length)) != -1) {
if (length!=1024) System.out.println(length);
dos.write(bytes, 0, length);
dos.flush();
progress += length;
if(fis != null)
fis.close();
}
}
发送端没什么好说的,应该都看得懂。下面说重点接收端。
接收端代码:
接收端的代码是重点。重点在前一个文件的末尾和后一个文件的头文件不能搞乱。因为文件大小一般不会是1024的整数倍,所以如果bytes[]一直是1024的话,可能会把前一个文件的末尾和后一个文件的前部分一起接收。你收到的头文件就会是乱码的。所以要及时调整bytes[]的大小。让文件接收的大小和文件大小对上。具体的逻辑如代码所示。
//获取输入流,没什么好说的
dis = new DataInputStream(client.getInputStream());
//读取文件总类型和文件数
int requestCode = dis.readInt();
int numOfFiles = dis.readInt();
//每一个循环读取一个文件
for (int i=0; i<numOfFiles; i++){
//读取文件头信息
byte[] fileMessageByte = new byte[149];
int headerLength = dis.read(fileMessageByte,0,fileMessageByte.length);
String fileMessage = new String(fileMessageByte);
//parse文件头信息,得到文件名称和文件长度
String fileNameWithSpace = fileMessage.split("--")[1];
String fileName = fileNameWithSpace.split(" ")[0];
long fileLength = Long.parseLong(fileMessage.split("--")[2]);
//创建文件实例和文件输出流
File file = new File(context.getExternalFilesDir("received"),
System.currentTimeMillis()+"-"+fileName);
FileOutputStream fos = null;
fos = new FileOutputStream(file);
//开始接受文件主体
byte[] bytes = new byte[1024];
int length = 0;
long progress = 0;
//这个地方是关键,你输出的总byte必须和文件长度是一样的,所以当剩余的文件长度小于传输
//的bytes[]的长度的话,要把bytes[]的长度重新设置为小于等于剩余文件的长度。当fileLength
//为0的时候,所有数据传输完毕。
while(((length = dis.read(bytes, 0, bytes.length)) != -1)) {
fos.write(bytes, 0, length);
fos.flush();
progress += length;
fileLength -= length;
if (fileLength == 0){
break;
}
if (fileLength < bytes.length){
bytes = new byte[(int)fileLength];
}
}
if(fos != null) {
fos.close();
}
}
我的项目地址为https://github.com/no-10/WiFiDirect
具体的代码位置
想在实战项目中参考的可以参考一下。