天天看點

記一次 IOException: 遠端主機強迫關閉了一個現有的連接配接

作者:愛做夢的程式員

前言

我這個錯誤主要是第三方接口調用我的下載下傳檔案接口出現的, 如果不是和檔案下載下傳相關, 可忽略

事情發生在和第三方裝置對接上, 功能大概是: 給裝置下發消息, 裝置來我的平台下載下傳檔案, 中間的通訊那個公司自己封裝了一個 jar包, 啟動 jar包之後我隻要調用接口就行了

這個功能是年初二月份寫完的, 當時測試也沒有問題, 但是因為他們硬體的問題換了一個樣品進行測試的時候發現這個功能就不好用了, 于是進行了問題排查, 本篇文章主要是記錄問題和解決

實名不喜歡對接第三方裝置, 但是公司有需要沒得辦法

文中所出現的代碼都是超精簡過的, 主打的就是一個通俗易懂

總結寫前面

這篇文章知識點總結還是要寫前面的, 直接省時好吧

對于response的操作

  • 下載下傳檔案:
java複制代碼// 指定檔案下載下傳名
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()));
           
  • 線上預覽
java複制代碼// 指定播放格式, 代碼中為 mp3播放
response.setContentType("audio/mp3");
           
  • conn.getContentLength() == -1
java複制代碼// 可以嘗試下述代碼, 理由: 2.2版本以上HttpURLConnection跟服務互動采用了"gzip"壓縮
conn.setRequestProperty("Accept-Encoding", "identity");
           

第一版代碼

java複制代碼        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: 遠端主機強迫關閉了一個現有的連接配接

第二版代碼

于是就有了第二版, 這次我直接規定輸出流是下載下傳, 同時把線上播放的也寫出來了, 最後用哪個就把另一個給注釋掉

java複制代碼        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的長度

java複制代碼    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"壓縮

java複制代碼    conn.setRequestProperty("Accept-Encoding", "identity");
           

但是我試了一下并不行, 沒得辦法, 瞎貓碰死耗子看一下源代碼

記一次 IOException: 遠端主機強迫關閉了一個現有的連接配接
記一次 IOException: 遠端主機強迫關閉了一個現有的連接配接
記一次 IOException: 遠端主機強迫關閉了一個現有的連接配接

柳暗花明又一村有沒有, 剛點進去倆方法, 就看到了一個熟悉的東西, 擷取檔案的長度, 還有content-length

在之前的兩版代碼我們知道我是沒有主動去配置 response.setHeader("Content-Length","")

那回到我的代碼上繼續更改

第三版代碼

java複制代碼        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流, 因為網絡通訊是存在間斷性的, 一串位元組可能會多次發送

繼續閱讀