Java Socket实现文件发送和接收功能以及遇到的Bug问题

 更新时间:2024年08月12日 08:57:22   作者:I am Groot!  
这篇文章主要介绍了Java Socket实现文件发送和接收功能以及遇到的Bug问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

Java Socket实现文件发送和接收功能

在Java中,如何用Socket实现文件的发送和接收功能?

我的第一版代码

文件发送:

public void sendFile(String filePath) {//过长、过多的密文信息直接发送文件
		File file = new File(filePath);
        try {
        	DataOutputStream d_out = new DataOutputStream(socket.getOutputStream());
            FileInputStream f_in = new FileInputStream(file);
            int all = 0;
            byte[] buffer = new byte[4096];
            int read = 0;
            while ((read = (f_in.read(buffer))) > 0) {
                d_out.write(buffer, 0, read);
                all += read;
            }
            System.out.println("Send file length: "+all);
            d_out.flush();
            f_in.close();
            d_out.close();//注意这一行
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

文件接收:

public void receiveFile(String filePath) {//接收文件
		try {
			DataOutputStream dosOutputStream = new DataOutputStream(new FileOutputStream(filePath));
			byte[] buf = new byte[4096];
			int len = 0;
			System.out.println("开始接收文件!");
			d_in = new DataInputStream(sock.getInputStream());
			while((len = d_in.read(buf)) != -1) { 
				dosOutputStream.write(buf, 0, len);
			}
			dosOutputStream.flush();
			System.out.println("文件接收结束!");
			//d_in.close();
			dosOutputStream.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

上面的写法的确实现了文件发送和接收的功能(Socket初始化这里没有给出,大家可以自行补充这部分,是可以运行的),但是这个实现方式存在一个很致命的问题,就是只能完成一次文件的发送和接收。

在此之后如果你想再调用文件的发送和接收方法,就会遇到如下"Socket is closed"这个问题:

为什么会遇到这一问题

首先肯定是因为Socket被我关闭了,但我并没有写"socket.close();"这样的代码呀,为什么还是被关闭了呢?

我们把注意力放到上面提到的文件发送的代码上,注意这一行:

d_out.close();

这里原本的目的是把DataOutputStream给关闭掉,结束我们的文件发送输出流。

但是当我们关闭DataOutputStream时,Socket也会随之关闭,这便有了后面想再次执行sendFile方法时,出现的"Socket is closed"问题。

所以为了能连续多次地发送、接收不同的文件,这一行代码肯定是不能要了。

把这行代码注释掉,修改后的代码为

文件发送:

public void sendFile(String filePath) {//过长、过多的密文信息直接发送文件
		File file = new File(filePath);
        try {
        	DataOutputStream d_out = new DataOutputStream(socket.getOutputStream());
            FileInputStream f_in = new FileInputStream(file);
            int all = 0;
            byte[] buffer = new byte[4096];
            int read = 0;
            while ((read = (f_in.read(buffer))) > 0) {
                d_out.write(buffer, 0, read);
                all += read;
            }
            System.out.println("Send file length: "+all);
            d_out.flush();
            f_in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

文件接收:

public void receiveFile(String filePath) {//接收文件
		try {
			DataOutputStream dosOutputStream = new DataOutputStream(new FileOutputStream(filePath));
			byte[] buf = new byte[4096];
			int len = 0;
			System.out.println("开始接收文件!");
			d_in = new DataInputStream(sock.getInputStream());
			while((len = d_in.read(buf)) != -1) { 
				dosOutputStream.write(buf, 0, len);
			}
			dosOutputStream.flush();
			System.out.println("文件接收结束!");
			//d_in.close();
			dosOutputStream.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

这样修改后又遇到了新的问题

那就是在接收文件时,发生了阻塞的现象:

控制台就一直卡在“开始接收文件!”这行输出这里,显然是因为文件接收的while循环被卡住了,但刚刚明明没有这个问题啊,为什么注释掉“sendFile”中的d_out.close();会使得“receiveFile”的while循环被卡住呢?

while((len = d_in.read(buf)) != -1) { 
    dosOutputStream.write(buf, 0, len);
}

这篇文章给出了比较好的解答:https://www.jb51.net/program/325734b3l.htm

原因如下

“只要客户端的DataOutputStream不close掉,那么服务端的DataInputStream read就永远不等于-1。即使文件的数据已经传完了,DataInputStream依旧会等着客户端DataOutputStream再传数据过来。最后只能通过判断文件的的大小来确认文件是否已经传输完成。”

这便是为什么在d_out.close();没有被注释前是没有这个Bug的:因为当我们将DataOutputStream close掉时,接收方的while循环也就结束了。

所以我们现在可以采用len的实际长度来判断是否已经传输完成,修改后的代码如下:

while((len = d_in.read(buf)) != -1) { 
    dosOutputStream.write(buf, 0, len);
	if(len < buf.length) break;
}

即:判断buf是否被填满,没有填满(len<buf.length)则代表已经接收完毕

经过测试,问题解决

最终代码如下:

文件发送:

​
public void sendFile(String filePath) {//发送文件
		File file = new File(filePath);
        try {
        	DataOutputStream d_out = new DataOutputStream(socket.getOutputStream());
            FileInputStream f_in = new FileInputStream(file);
            int all = 0;
            byte[] buffer = new byte[4096];
            int read = 0;
            while ((read = (f_in.read(buffer))) > 0) {
                d_out.write(buffer, 0, read);
                all += read;
            }
            System.out.println("Send file length: "+all);
            d_out.flush();
            f_in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

​

文件接收:

public void receiveFile(String filePath) {//接收文件
		try {
			DataOutputStream dosOutputStream = new DataOutputStream(new FileOutputStream(filePath));
			byte[] buf = new byte[4096];
			int len = 0;
			System.out.println("开始接收文件!");
			d_in = new DataInputStream(sock.getInputStream());
			while((len = d_in.read(buf)) != -1) { 
				dosOutputStream.write(buf, 0, len);
                if(len < buf.length) break;
			}
			dosOutputStream.flush();
			System.out.println("文件接收结束!");
			dosOutputStream.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • 详谈HashMap和ConcurrentHashMap的区别(HashMap的底层源码)

    详谈HashMap和ConcurrentHashMap的区别(HashMap的底层源码)

    下面小编就为大家带来一篇详谈HashMap和ConcurrentHashMap的区别(HashMap的底层源码)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-08-08
  • 详解SpringBoot目录结构划分

    详解SpringBoot目录结构划分

    代码目录结构是一个在项目开发中非常重要的部分,本文主要介绍了详解SpringBoot目录结构划分,具有一定的参考价值,感兴趣的可以了解一下
    2024-08-08
  • nacos配置实例权重不生效问题

    nacos配置实例权重不生效问题

    这篇文章主要介绍了nacos配置实例权重不生效问题及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2024-07-07
  • Java InputStream的多种使用详解

    Java InputStream的多种使用详解

    这篇文章主要介绍了Java InputStream的多种使用详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-12-12
  • Java多线程通信实现方式详解

    Java多线程通信实现方式详解

    这篇文章主要介绍了Java多线程通信实现方式详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-11-11
  • Java this关键字的引用详解

    Java this关键字的引用详解

    每个类中都有一个本类的this引用,类似:类名 this;实例对象时,this引用指向对象本身,其最主要的作用是让类中的方法可以访问本类中的另外一个方法或属性,因为调用一个方法或属性(非类方法/属性)必须要有实例
    2022-03-03
  • Java SpringMVC实现国际化整合案例分析(i18n)

    Java SpringMVC实现国际化整合案例分析(i18n)

    本篇文章主要介绍了Java SpringMVC实现国际化整合案例分析(i18n),具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-05-05
  • Java基础之JDK1.8新特性lambda表达式详解

    Java基础之JDK1.8新特性lambda表达式详解

    函数式接口有且仅有一个抽象方法,但是可以有多个非抽象方法的接口,函数式接口可以被隐式转换为lambda表达式,这篇文章主要介绍了Java基础之lambda表达式(JDK1.8新特性),需要的朋友可以参考下
    2023-08-08
  • SpringBoot配置数据库密码加密的方法

    SpringBoot配置数据库密码加密的方法

    由于系统安全的考虑,配置文件中不能出现明文密码的问题,本文就给大家详细介绍下springboot配置数据库密码加密的方法,下面话不多说了,来一起看看详细的介绍吧,需要的朋友可以参考下
    2023-08-08
  • Java常用类之字符串相关类使用详解

    Java常用类之字符串相关类使用详解

    String、StringBuilder、StringBuffer类是Java中常用的三个字符串相关类。本文将通过示例为大家讲解一下他们的用法,需要的可以参考一下
    2022-08-08

最新评论