Java?Socket實(shí)現(xiàn)文件發(fā)送和接收功能以及遇到的Bug問(wèn)題
Java Socket實(shí)現(xiàn)文件發(fā)送和接收功能
在Java中,如何用Socket實(shí)現(xiàn)文件的發(fā)送和接收功能?
我的第一版代碼
文件發(fā)送:
public void sendFile(String filePath) {//過(guò)長(zhǎng)、過(guò)多的密文信息直接發(fā)送文件
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("開(kāi)始接收文件!");
d_in = new DataInputStream(sock.getInputStream());
while((len = d_in.read(buf)) != -1) {
dosOutputStream.write(buf, 0, len);
}
dosOutputStream.flush();
System.out.println("文件接收結(jié)束!");
//d_in.close();
dosOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}上面的寫(xiě)法的確實(shí)現(xiàn)了文件發(fā)送和接收的功能(Socket初始化這里沒(méi)有給出,大家可以自行補(bǔ)充這部分,是可以運(yùn)行的),但是這個(gè)實(shí)現(xiàn)方式存在一個(gè)很致命的問(wèn)題,就是只能完成一次文件的發(fā)送和接收。
在此之后如果你想再調(diào)用文件的發(fā)送和接收方法,就會(huì)遇到如下"Socket is closed"這個(gè)問(wèn)題:

為什么會(huì)遇到這一問(wèn)題
首先肯定是因?yàn)镾ocket被我關(guān)閉了,但我并沒(méi)有寫(xiě)"socket.close();"這樣的代碼呀,為什么還是被關(guān)閉了呢?
我們把注意力放到上面提到的文件發(fā)送的代碼上,注意這一行:
d_out.close();
這里原本的目的是把DataOutputStream給關(guān)閉掉,結(jié)束我們的文件發(fā)送輸出流。
但是當(dāng)我們關(guān)閉DataOutputStream時(shí),Socket也會(huì)隨之關(guān)閉,這便有了后面想再次執(zhí)行sendFile方法時(shí),出現(xiàn)的"Socket is closed"問(wèn)題。
所以為了能連續(xù)多次地發(fā)送、接收不同的文件,這一行代碼肯定是不能要了。
把這行代碼注釋掉,修改后的代碼為
文件發(fā)送:
public void sendFile(String filePath) {//過(guò)長(zhǎng)、過(guò)多的密文信息直接發(fā)送文件
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("開(kāi)始接收文件!");
d_in = new DataInputStream(sock.getInputStream());
while((len = d_in.read(buf)) != -1) {
dosOutputStream.write(buf, 0, len);
}
dosOutputStream.flush();
System.out.println("文件接收結(jié)束!");
//d_in.close();
dosOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}這樣修改后又遇到了新的問(wèn)題
那就是在接收文件時(shí),發(fā)生了阻塞的現(xiàn)象:

控制臺(tái)就一直卡在“開(kāi)始接收文件!”這行輸出這里,顯然是因?yàn)槲募邮盏膚hile循環(huán)被卡住了,但剛剛明明沒(méi)有這個(gè)問(wèn)題啊,為什么注釋掉“sendFile”中的d_out.close();會(huì)使得“receiveFile”的while循環(huán)被卡住呢?
while((len = d_in.read(buf)) != -1) {
dosOutputStream.write(buf, 0, len);
}這篇文章給出了比較好的解答:http://www.dhdzp.com/program/325734b3l.htm
原因如下
“只要客戶(hù)端的DataOutputStream不close掉,那么服務(wù)端的DataInputStream read就永遠(yuǎn)不等于-1。即使文件的數(shù)據(jù)已經(jīng)傳完了,DataInputStream依舊會(huì)等著客戶(hù)端DataOutputStream再傳數(shù)據(jù)過(guò)來(lái)。最后只能通過(guò)判斷文件的的大小來(lái)確認(rèn)文件是否已經(jīng)傳輸完成。”
這便是為什么在d_out.close();沒(méi)有被注釋前是沒(méi)有這個(gè)Bug的:因?yàn)楫?dāng)我們將DataOutputStream close掉時(shí),接收方的while循環(huán)也就結(jié)束了。
所以我們現(xiàn)在可以采用len的實(shí)際長(zhǎng)度來(lái)判斷是否已經(jīng)傳輸完成,修改后的代碼如下:
while((len = d_in.read(buf)) != -1) {
dosOutputStream.write(buf, 0, len);
if(len < buf.length) break;
}即:判斷buf是否被填滿,沒(méi)有填滿(len<buf.length)則代表已經(jīng)接收完畢
經(jīng)過(guò)測(cè)試,問(wèn)題解決
最終代碼如下:
文件發(fā)送:
?
public void sendFile(String filePath) {//發(fā)送文件
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("開(kāi)始接收文件!");
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("文件接收結(jié)束!");
dosOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
java實(shí)現(xiàn)漢字轉(zhuǎn)unicode與漢字轉(zhuǎn)16進(jìn)制實(shí)例
這篇文章主要介紹了java實(shí)現(xiàn)漢字轉(zhuǎn)unicode與漢字轉(zhuǎn)16進(jìn)制的實(shí)現(xiàn)方法,是Java操作漢字編碼轉(zhuǎn)換的一個(gè)典型應(yīng)用,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2014-10-10
hashMap擴(kuò)容時(shí)應(yīng)該注意這些死循環(huán)問(wèn)題
今天給大家?guī)?lái)的是關(guān)于Java的相關(guān)知識(shí),文章圍繞著hashMap擴(kuò)容時(shí)的死循環(huán)問(wèn)題展開(kāi),文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下2021-06-06
Spring中的之啟動(dòng)過(guò)程obtainFreshBeanFactory詳解
這篇文章主要介紹了Spring中的之啟動(dòng)過(guò)程obtainFreshBeanFactory詳解,在refresh時(shí),prepareRefresh后,馬上就調(diào)用了obtainFreshBeanFactory創(chuàng)建beanFactory以及掃描bean信息(beanDefinition),并通過(guò)BeanDefinitionRegistry注冊(cè)到容器中,需要的朋友可以參考下2024-02-02
擴(kuò)展tk.mybatis的流式查詢(xún)功能實(shí)現(xiàn)
mybatis查詢(xún)默認(rèn)是一次獲取全部,如果數(shù)據(jù)過(guò)于龐大,就會(huì)導(dǎo)致OOM問(wèn)題,本文就介紹了tk.mybatis 流式查詢(xún),具有一定的參考價(jià)值,感興趣的可以了解一下2021-12-12
Java使用easypoi快速導(dǎo)入導(dǎo)出的實(shí)現(xiàn)
這篇文章主要介紹了實(shí)現(xiàn)Java使用easypoi快速導(dǎo)入導(dǎo)出的實(shí)現(xiàn),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-03-03
Java HttpServletResponse響應(yīng)實(shí)現(xiàn)過(guò)程詳解
這篇文章主要介紹了Java HttpServletResponse響應(yīng)實(shí)現(xiàn)過(guò)程詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-05-05
SpringBoot JVM參數(shù)調(diào)優(yōu)方式
這篇文章主要介紹了SpringBoot JVM參數(shù)調(diào)優(yōu)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-09-09
java自定義注解驗(yàn)證手機(jī)格式的實(shí)現(xiàn)示例
這篇文章主要介紹了java自定義注解驗(yàn)證手機(jī)格式的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03

