likes
comments
collection
share

记一次 IOException: 远程主机强迫关闭了一个现有的连接

作者站长头像
站长
· 阅读数 13

前言

我这个错误主要是第三方接口调用我的下载文件接口出现的, 如果不是和文件下载相关, 可忽略

事情发生在和第三方设备对接上, 功能大概是: 给设备下发消息, 设备来我的平台下载文件, 中间的通讯那个公司自己封装了一个 jar包, 启动 jar包之后我只要调用接口就行了

这个功能是年初二月份写完的, 当时测试也没有问题, 但是因为他们硬件的问题换了一个样品进行测试的时候发现这个功能就不好用了, 于是进行了问题排查, 本篇文章主要是记录问题和解决

实名不喜欢对接第三方设备, 但是公司有需要没得办法

文中所出现的代码都是超精简过的, 主打的就是一个通俗易懂

总结写前面

这篇文章知识点总结还是要写前面的, 直接省时好吧

对于response的操作

  • 下载文件:
// 指定文件下载名
response.addHeader("Content-Disposition", "attachment;filename=" + fileName);
// 指定长度, 推荐file.length()方法, inputStream.available()方法获取网络流不一定准确
//response.setHeader("Content-Length", String.valueOf(inputStream.available()));
response.setHeader("Content-Length", String.valueOf(file.length()));
  • 在线预览
// 指定播放格式, 代码中为 mp3播放
response.setContentType("audio/mp3");
  • conn.getContentLength() == -1
// 可以尝试下述代码, 理由: 2.2版本以上HttpURLConnection跟服务交互采用了"gzip"压缩
conn.setRequestProperty("Accept-Encoding", "identity");

第一版代码

        File file = new File(filePath);
        InputStream inputStream = new FileInputStream(file);
        try {
            IOUtils.copy(inputStream, response.getOutputStream());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            inputStream.close();
        }

二月份代码非常简单, 就是获取文件流, 然后做到调用接口能在页面播放, 我以为就是调用我的文件流, 而不是把文件下载到设备内部, 依据来自于对方平台相同功能调用结果如下图

记一次 IOException: 远程主机强迫关闭了一个现有的连接

直接就是一个音频文件在线播放好吧, 那我肯定也要这么做啊, 于是就有了这一版代码, 测试也通过了, 设备可以正常播放音频

但是当前两天测试时发现不好用了, 本地测试会直接报错

记一次 IOException: 远程主机强迫关闭了一个现有的连接

这不行啊, 于是就开始研究哪里出了问题

经过和对方工程师的沟通, 他告诉我是下载到设备里面, 我一整个裂开好吧

记一次 IOException: 远程主机强迫关闭了一个现有的连接

第二版代码

于是就有了第二版, 这次我直接规定输出流是下载, 同时把在线播放的也写出来了, 最后用哪个就把另一个给注释掉

        File file = new File(filePath);
        InputStream inputStream = new FileInputStream(file);
        try {
            // 下载
            String[] split = filePath.split("/");
            String fileName = URLEncoder.encode(split[split.length-1], "UTF-8");
            response.addHeader("Content-Disposition", "attachment;filename=" + fileName);
            // 在线播放
//            response.setContentType("audio/mp3");
            IOUtils.copy(inputStream, response.getOutputStream());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            inputStream.close();
        }

但是毫无软用, 我就使用他们的平台进行测试, 发现可以正常播放, 这谁受得了啊, 我就去联系对方工程师, 看能不能提供一下设备下载文件的源码, 对方还是很配合的, 赞一手

对方下载文件代码(精简版)

代码中的 else 是我写的, 输出一下 conn的长度

    public void download() throws Exception {

        //准备拼接新的文件名
        File file = new File("D:\\project\\iot-cloud\\iot-server\\server-xixun\\target\\上下五千年.mp3");
        //1K的数据缓冲
        byte[] bs = new byte[1024];
        //读取到的数据长度
        int len;
        try {
            //通过文件地址构建url对象
            //下载地址
            URL url = new URL("http://127.0.0.1/openApi/file/audio/137e6887926f4f559511c560702562e7");
            //[2.3]拿到httpURLconnection对象 用于接收或发送数据
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            //[2.4]发送GET请求  系统默认就是GET请求
            conn.setRequestMethod("GET");
            //[2.5]设置请求时间
            conn.setConnectTimeout(5000);
            //获取链接
            //URLConnection conn = url.openConnection();
            //获取文件的长度
            conn.setRequestProperty("Accept-Encoding", "identity");
            long contextLength = conn.getContentLength();
            //下载成功
            if (contextLength > 0) {
                //真实下载,地址正确
                //创建输入流
                InputStream is = conn.getInputStream();

                //输出的文件流
                FileOutputStream os = new FileOutputStream(file);
                FileDescriptor fd = os.getFD();

                //开始读取
                while ((len = is.read(bs)) != -1) {
                    os.write(bs, 0, len);
                }
                os.flush();
                fd.sync();
                //完毕关闭所有连接
                os.close();
                is.close();
            }else{
                System.out.println("长度过小");
            }

        } catch (Exception e) {
            throw new Exception();
        }
    }

这个注释是不是值一个大拇哥😼

测试与解决

测试过程如下:

  • 启动我方代码平台
  • 对方代码放在单独的springboot启动
  • postman进行调用

结果:

  • 不论我方代码是在线播放还是下载, conn 长度一直为 -1

身为尊贵的CV开发工程师碰到这种情况我直接起手一个百度

记一次 IOException: 远程主机强迫关闭了一个现有的连接

但是我点开之后, 发现都是说让我添加这行代码, 理由是: 2.2版本以上HttpURLConnection跟服务交互采用了"gzip"压缩

    conn.setRequestProperty("Accept-Encoding", "identity");

但是我试了一下并不行, 没得办法, 瞎猫碰死耗子看一下源代码

记一次 IOException: 远程主机强迫关闭了一个现有的连接

记一次 IOException: 远程主机强迫关闭了一个现有的连接

记一次 IOException: 远程主机强迫关闭了一个现有的连接

柳暗花明又一村有没有, 刚点进去俩方法, 就看到了一个熟悉的东西, 获取文件的长度, 还有content-length

在之前的两版代码我们知道我是没有主动去配置 response.setHeader("Content-Length","")

那回到我的代码上继续更改

第三版代码

        File file = new File(filePath);
        InputStream inputStream = new FileInputStream(file);
        try {
            // 下载
            String[] split = filePath.split("/");
            String fileName = URLEncoder.encode(split[split.length-1], "UTF-8");
            response.addHeader("Content-Disposition", "attachment;filename=" + fileName);
//            response.setHeader("Content-Length", String.valueOf(inputStream.available()));
            response.setHeader("Content-Length", String.valueOf(file.length()));
            // 在线播放
//            response.setContentType("audio/mp3");
            IOUtils.copy(inputStream, response.getOutputStream());
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            inputStream.close();
        }

有细心的兄弟可能会看到上面获取文件长度我用了两种方法, 都可以使用, 但是更推荐 file.length()方法

inputStream.available()读取本地流一般没问题, 但是读取网络流时可能会不准确, 例如socket流, 因为网络通讯是存在间断性的, 一串字节可能会多次发送

本文内容到此结束了

如有收获欢迎点赞👍收藏💖关注✔️,您的鼓励是我最大的动力。

如有错误❌疑问💬欢迎各位大佬指出。

我是 宁轩 , 我们下次再见