需求:
实现一个客服端和一个服务端,客服端传送一个文件夹到服务端,服务端接收这个文件夹,其中内容完全一样。
问题:
由于文件夹内文件可能存在非文本文件,所以不能使用字符流来传送数据,由于当客户端与服务端用字节流收发数据的过程中并不是同步进行的,它是客户端先发送一定容量的数据缓存到服务端然后服务端再从中提取,而字节流在提取过程中每次传输的数据无法分隔,只能靠字节数组来提取数据,而每次提取要用多大的字节数组是未知的,所以如果传送的数据很大,就会很难准确地提取到我想要的数据,容易产生乱码。
方法
绑定头结点法:
每次传送数据时,先获取本次要传输的数据的容量,再发送这个容量数据转化为字节数组用4个字节的空间存储。再输送数据。
接收的时候再定义一个容量为4的字节数组,再将数组内的值转化为整型,得到这个整数以后,再定义一个此整数长度的字节数组来接收这次传送过来的数据。
难点
1.要把整个文件夹传送过去,文件夹内可能存在文件和文件夹,每次传输过去要判断所传送的是文件还是文件夹
2.一个字节只能存储-127-127 范围的值,得到本次要转送的数据量以后,如果数据量过大,不能只用一个字节来存储发送,而用4个字节存储这个整数也是一个难点。
解决方法
1.在传送文件或文件夹名字的时候 在首部或尾部加一个标注用来区分是文件还是文件夹
2.用字节运算符左移右移与或操作将一个整数转换为一个字节数组;
代码实现如下:
1.客户端:
public class SocketMultipleDirectory {
public static void main(String[] args) throws IOException {
//创建File对象,参数为要发送的文件的路径
File file = new File(“D:\新建文件夹”);
// FileInputStream fis = new FileInputStream(file);
// BufferedInputStream bis = new BufferedInputStream(fis);
// BufferedOutputStream bos = new BufferedOutputStream(s.getOutputStream());
// send(bis,bos,file);
send(file);
}
private static void send(File file) throws IOException {
Socket s = new Socket("localhost", 7879);
System.out.println("链接成功!");
BufferedOutputStream bos = new BufferedOutputStream(s.getOutputStream());
int len;
//如果file不是一个文件夹
if (!file.isDirectory()) {
// 先把文件名的字节长度发过去,再发文件名
System.out.println("发送一个文件");
String ss = file.getPath().replace("D:\\新建文件夹", "" );
byte[] name = ss.getBytes();
len = name.length;
System.out.println("文件名是:"+name);
byte[] in = SocketMultipleTools.intTobyteArray(len);
bos.write(in);
bos.write(name);
bos.flush();
//接下来就开始发送正式的文件内容
//先发送文件的所有内容的大小过去
FileInputStream fis = new FileInputStream(file);
BufferedInputStream bis = new BufferedInputStream(fis);
len = bis.available();
in = SocketMultipleTools.intTobyteArray(len);
bos.write(in);
bos.flush();
//再发送文件内容
byte[] bys = new byte[1024];
while ((len = bis.read(bys)) != -1){
bos.write(bys);
bos.flush();
}
bos.close();
bis.close();
s.close();
} else {
System.out.println("发送一个文件夹");
String ss = file.getPath().replace( "D:\\新建文件夹", "$");
byte[] name = ss.getBytes();
len = name.length;
System.out.println("文件名是:"+name);
byte[] in = SocketMultipleTools.intTobyteArray(len);
bos.write(in);
bos.write(name);
bos.flush();
s.close();
for(File f:file.listFiles()){
send(f);
}
}
}
}
2.服务端
public class ServerSocketMultipleDirectory {
public static void main(String[] args) throws IOException {
ServerSocket ss = new ServerSocket(7879);
//创建File对象,参数为接收到的文件所要存储的路径
File file = new File(“E:\recivefile”);
// FileOutputStream fos = new FileOutputStream(file);
// BufferedOutputStream bos = new BufferedOutputStream(fos);
while(true){
receive(file,ss);
}
}
//精准传输文件完成
private static void receive(File file, ServerSocket ss) throws IOException {
Socket s = ss.accept();
System.out.println("链接成功");
BufferedInputStream bis = new BufferedInputStream(s.getInputStream());
//先把文件名长度和文件名取出来并判断是不是文件夹
byte[] leng = new byte[4];
bis.read(leng);
int len = SocketMultipleTools.byteArrayToInt(leng);
byte[] bys1 = new byte[len];
bis.read(bys1);
//文件是一个文件夹的情况
if((new String(bys1,0,1,"utf-8")).startsWith("$")){
System.out.println("接收到一个文件夹");
if((new String(bys1,"utf-8")).equals("$")) {
} else {
String name = new String(bys1, 0, len, "utf-8");
name = name.replace("$","");
File f = new File(file, name);
System.out.println("文件夹的名字是" + name);
f.mkdirs();
}
//不是文件夹的情况
}else{
//先创建文件
System.out.println("接收到一个文件");
String name = new String(bys1,0,len,"utf-8");
System.out.println("文件名字是"+name);
File f = new File(file,name);
f.createNewFile();
//再接收数据
FileOutputStream fos = new FileOutputStream(f);
BufferedOutputStream bos = new BufferedOutputStream(fos);
bis.read(leng);
len = SocketMultipleTools.byteArrayToInt(leng);
System.out.println("文件长度为"+len);
byte[] bys = new byte[1024];
for (int i = 0; i < len/1024; i++) {
bis.read(bys);
bos.write(bys);
bos.flush();
}
byte[] bys2 = new byte[len%1024];
bis.read(bys2);
bos.write(bys2);
bos.flush();
bis.close();
bos.close();
System.out.println("文件接收完成");
}
s.close();
System.out.println("已经断开");
3.字节数组与整数之间的转化工具类
public class SocketMultipleTools {
private SocketMultipleTools() {
}
//在字节运输的过程中会为了保证数据的精准传输,我们采用的是固定头部方法
//就是在字节传输中先把前4个byte的空间用来存本次传输的所要传送的内容大小的int值
//所以要先将int类型的数据先以二进制的形式拆分成四个的byte类型的数据
public static byte[] intTobyteArray(int a){
return new byte[]{
(byte)((a >> 24)&0xff),
(byte)((a >> 16)&0xff),
(byte)((a >> 8)&0xff),
(byte)(a&0xff)
};
}
public static int byteArrayToInt(byte[] bys){
return (bys[0]&0xff)<<24 |(bys[1]&0xff)<<16 |(bys[2]&0xff)<<8 |(bys[3]&0xff);
}
}