Socket中Connection Reset問題分析


現象描述

1、先后啟動服務端和客戶端,客戶端正常執行完畢,服務端出現Connection Reset異常,錯誤定位在while ((bufferSize = is.read(bytes))!=-1)
2、服務端先后接收文件名和文件內容,文件內容無法接收到

服務端代碼

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
    public static void main(String[] args) throws IOException {
        ServerSocket server = new ServerSocket(55335);
        File dir = new File("received");
        if (!dir.exists()){
            dir.mkdir();
        }
        while (true){
            Socket socket = server.accept();
            new Thread(()->{
                try {
                    String name = null;
                    InputStream is = socket.getInputStream();
                    BufferedReader reader = new BufferedReader(new InputStreamReader(is));
                    if ((name = reader.readLine())==null){
                        System.out.println("出現異常!無法完成此次傳輸...");
                        return;
                    }
                    System.out.println("文件名為:"+name);
                    System.out.println("開始接收...");
                    File file = new File(dir,name);
                    FileOutputStream fos = new FileOutputStream(file);
                    int size = 0;
                    int bufferSize;
                    byte[] bytes = new byte[1024];
                    while ((bufferSize = is.read(bytes))!=-1){
                        System.out.println(bufferSize);
                        fos.write(bytes,0,bufferSize);
                        System.out.println("已寫入"+(size+=bufferSize)+"B數據");
                    }
                    fos.flush();
                    System.out.println(bufferSize);
                    fos.close();
                    System.out.println("寫入完成!路徑為:"+file.getAbsolutePath());
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }).start();

            OutputStream os = socket.getOutputStream();
            os.write("hello\t".getBytes());
        }
    }
}

服務端代碼邏輯如下:

  • 循環等待建立連接
  • 通過字符流接收客戶端發送過來的數據,作為新文件的文件名
  • 通過字節流接收客戶端發送過來的數據,並寫入指定文件

客戶端代碼

import java.io.*;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Scanner;

public class Client {
    public static void main(String[] args) throws IOException, InterruptedException {
        Scanner sc = new Scanner(System.in);
        byte[] bytes = new byte[1024];
        String path;
        File file;
        System.out.println("輸入文件路徑:");
        path = sc.nextLine();
        file = new File(path);
        System.out.println("路徑為:"+file.getAbsolutePath());
        System.out.println("文件大小:"+file.length()+"B");
        if (!file.exists()){
            System.out.println("文件不存在!");
            return;
        }
        int size = 0;
        int bufferSize = 0;
        Socket socket = new Socket("localhost",55335);
        OutputStream os = socket.getOutputStream();
        os.write((file.getName()+"\n").getBytes());
        os.flush();
        FileInputStream fis = new FileInputStream(file);
        while ((bufferSize=fis.read(bytes))!=-1){
            size+=bufferSize;
            os.write(bytes);
            System.out.println("已傳輸"+size+"B");
        }
        os.flush();
        System.out.println("傳輸完成");
        Thread.sleep(1000);
    }
}

客戶端代碼邏輯如下:

  • 建立socket連接
  • 先通過字符流把文件名發送到服務端
  • 然后通過字節流把文件內容發送到服務端

分析過程

  • 服務端在while ((bufferSize = is.read(bytes))!=-1) 時Connection Reset,即服務端數據還未讀取完畢,客戶端已退出
  • 核對客戶端代碼,發現發送完數據,沉睡了一秒后結束運行。正常來說,測試文件比較小,應該可以正常接收完畢。但是客戶端最后並未執行socket.shutdownOutput(),通知服務端數據已發送完畢,導致服務端一直處理讀取數據狀態,但由於數據一直未就緒導致服務端IO堵塞。這時,客戶端退出,服務端就異常了
  • 發現上述問題,我們在客戶端加上socket.shutdownOutput(),服務端正常執行完畢,驗證上述結論
  • 出現新的問題,服務端未接收到后續發送到的數據,本地只寫入了一個空文件
  • debug模式開啟,在客戶端while ((bufferSize=fis.read(bytes))!=-1) 加上斷點,服務端竟然可以接收到數據。感覺有點奇怪,隨后又在客戶端第一次輸出流os.flush()后沉睡一秒,去除斷點也可以正常執行並接收到數據。看來和發送接收的時間點有關系,糾結中...
  • 客戶端中有兩次發送數據,第一次可以接收,第二次不行。兩次中間增加執行間隔,又都可以。可以得出,肯定是前一次發送對第二次造成了影響。隨后,把第一次發送文件名的代碼注釋掉,同時注釋掉服務端接收的代碼,文件數據接收正常
  • 觀察兩次寫入的不同,第一次內容加上了換行符。然后再看服務端代碼,第一次使用的緩存字符流,讀完文件內容時使用的卻是字節流,交替使用了緩沖字符流和字節流
  • 這樣就可以解釋上述現象了,緩沖字符流讀取數據時有緩存,直接轉向InputStream去讀取,緩存中的數據是讀不到的。因為測試發送的數據量比較小,都進入了緩沖區,轉換成字節流后就造成讀取不到任何數據的情況。加斷點或者沉睡,延緩了第二次數據發送,服務端在切換成字節流后才接收到數據,所以又可以正常接收

解決方案

使用字節流或者字符流,不要交替使用

  • 字節流:先發送文件名占用字節數(int類型),再發送文件名,最后發送文件內容。服務端先讀取4個字節轉化為int,並作為第二次讀取的字節數讀取文件名,最后按指定步長讀取文件內容
  • 字符流:逐行讀取文件並發送,服務端統一為字符流讀取


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM