現象描述
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,並作為第二次讀取的字節數讀取文件名,最后按指定步長讀取文件內容
- 字符流:逐行讀取文件並發送,服務端統一為字符流讀取