Java Socket通信介紹及可能遇到的問(wèn)題解決
前言
本文主要給大家介紹了關(guān)于Java中Socket通信的相關(guān)內(nèi)容,分享出來(lái)供大家參考學(xué)習(xí),下面話不多說(shuō)了,來(lái)一起看看詳細(xì)的介紹吧。
Java中基于TCP協(xié)議實(shí)現(xiàn)網(wǎng)絡(luò)通信的兩個(gè)類:客戶端的Socket和服務(wù)器端的ServerSocket。
Socket通信模型如圖所示:
不管Socket通信的功能有多復(fù)雜,任何socket通信過(guò)程的基本結(jié)構(gòu)都是一樣的。
其基本步驟為:
①分別在客戶端和服務(wù)器端創(chuàng)建Socket和ServerSocket實(shí)例;服務(wù)器端通過(guò).accept()方法等待請(qǐng)求并阻塞。請(qǐng)求收到后,建立連接Socket對(duì)象。
②通過(guò)getInputStream和getOutputStream方法分別在客戶端和服務(wù)器端打開輸入輸出流
③利用IO流進(jìn)行讀寫操作
④關(guān)閉所有的流資源和套接字資源。
其中,編程工作主要集中在第三步,其他的部分代碼基本相同。所有步驟都可能拋出IO異常!
我在編寫一個(gè)簡(jiǎn)單的socket程序時(shí),使用的Socket通信出現(xiàn)了一個(gè)問(wèn)題:我在客戶端寫入的數(shù)據(jù),在服務(wù)器端無(wú)法輸出。當(dāng)我從客戶端斷開連接時(shí),之前寫入的所有數(shù)據(jù)立刻在服務(wù)器端輸出出來(lái)了。經(jīng)過(guò)反復(fù)的驗(yàn)證和求解,以下是我的結(jié)論和解決方法。希望有同樣問(wèn)題的小伙伴看完可以解決問(wèn)題。
通過(guò)一端的Socket建立了PrintWriter類來(lái)寫入數(shù)據(jù),通過(guò)另一端的Socket建立了BufferedReader類來(lái)讀取數(shù)據(jù)并輸出。
如果數(shù)據(jù)寫入后沒(méi)有被顯示,可能的原因有兩種:
一、寫入的數(shù)據(jù)存儲(chǔ)在緩沖區(qū)中,沒(méi)有被寫入IO流中:
如果不主動(dòng)的干涉,寫入的數(shù)據(jù)會(huì)一直堆在緩沖區(qū)中,直到緩沖區(qū)滿了引發(fā)JVM自動(dòng)刷新緩沖區(qū)。顯然這不符合我們的需求。對(duì)于這種情況,PrintWriter類提供了flush()方法來(lái)強(qiáng)制刷新緩沖區(qū),將緩沖區(qū)數(shù)據(jù)寫入IO流中。另外,PrintWriter類的構(gòu)造器有一個(gè)參數(shù)”boolean autoflush“,這個(gè)參數(shù)默認(rèn)為false,如果設(shè)置為true,則會(huì)開啟自動(dòng)刷新緩沖區(qū)功能。但是請(qǐng)注意,這里的自動(dòng)刷新是有觸發(fā)條件的,那就是:PrintWriter類寫入數(shù)據(jù)的方法必須是println、printf或者format方法時(shí),才會(huì)觸發(fā)自動(dòng)刷新。如果是調(diào)用write()這類方法寫入數(shù)據(jù),是不會(huì)觸發(fā)自動(dòng)刷新的!總結(jié)起來(lái),就是三點(diǎn):autoflush參數(shù)設(shè)置,write和println方法的選擇,flush方法的使用。對(duì)這三個(gè)進(jìn)行組合,就能保證在Socket通信的某一端寫入數(shù)據(jù)時(shí),數(shù)據(jù)一定能成功地寫入到IO流中!
二、讀取數(shù)據(jù)使用了readLine()方法,該方法沒(méi)有正常的結(jié)束:
請(qǐng)注意,BufferedReader類的readLine()方法是一個(gè)阻塞函數(shù)!也就是說(shuō),這個(gè)方法本身是讀取一行數(shù)據(jù),但是它自己識(shí)別不了什么叫做“一行”!當(dāng)調(diào)用該方法讀取完一段數(shù)據(jù)后,它會(huì)阻塞,而不會(huì)return它的讀取數(shù)據(jù)。這就是為什么有的時(shí)候明明已經(jīng)刷新了緩沖區(qū)正確的寫入數(shù)據(jù)了,還是通過(guò)輸入流讀取數(shù)據(jù)并顯示出來(lái)的原因。
對(duì)于readLine()方法,它解除阻塞、正確結(jié)束并返回讀取的值,只有以下幾種情況:
①讀取的數(shù)據(jù)里含有回車符"\r"或者換行符"\n"或者回車換行符"\r\n";
②讀取的數(shù)據(jù)是在另一端通過(guò)println方法寫入的,因?yàn)閜rintln方法自帶換行符;
③BufferedReader類的緩沖區(qū)滿了,那么JVM會(huì)自動(dòng)刷新緩沖區(qū)從而釋放“積攢”的數(shù)據(jù)(但是鑒于默認(rèn)緩沖區(qū)大小為8192個(gè)字符,對(duì)于小數(shù)據(jù)量的通信,顯然觸發(fā)不了);
④對(duì)于讀取的數(shù)據(jù),寫入這些數(shù)據(jù)的流發(fā)生異?;蛘咧苯雨P(guān)閉,那么readLine()就會(huì)把它吃的數(shù)據(jù)全部吐出來(lái)。這就剛好解釋了,為什么在我的程序中,斷開客戶端Socket連接,服務(wù)器端立刻輸出所有客戶端消息的原因。
綜上,在Socket通信過(guò)程中,保證某一端輸出流的緩沖被刷新,保證另一端的readLine方法能正常停止,即可解決寫入的數(shù)據(jù)在另一端無(wú)法輸出的問(wèn)題。
以下是我修改后能成功運(yùn)行的代碼,分別是服務(wù)器端Socket和客戶端Socket。
over!
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.io.BufferedWriter; import java.io.OutputStreamWriter; public class ShakingServer{ public static void main(String[] args) throws IOException { //創(chuàng)建服務(wù)器套接字實(shí)例,設(shè)置監(jiān)聽端口為2000 ServerSocket server=new ServerSocket(2000); //開始監(jiān)聽客戶端的請(qǐng)求,并阻塞 Socket socket=server.accept(); //請(qǐng)求收到后,自動(dòng)建立連接。通過(guò)IO流進(jìn)行數(shù)據(jù)傳輸 System.out.println("連接建立成功"); OutputStream os=socket.getOutputStream(); PrintWriter pw=new PrintWriter(new BufferedWriter(new OutputStreamWriter(os)),true); pw.write("歡迎訪問(wèn)搖頭耶穌的世界!"); pw.flush(); //因?yàn)槲谊P(guān)閉了輸出流,所以另一端的readLine方法才正常結(jié)束了 socket.shutdownOutput(); InputStream is=socket.getInputStream(); InputStreamReader isr=new InputStreamReader(is); BufferedReader br=new BufferedReader(isr); while(true) { String str=br.readLine(); if(str.equals("quit")) { break; } System.out.println("Client said: "+str); } socket.shutdownInput(); //socket.shutdownOutput(); socket.close(); server.close(); } }
import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.Socket; public class ShakingClient{ public static void main(String[] args) throws IOException{ //創(chuàng)建客戶端的套接字,設(shè)置連接的服務(wù)器的IP地址和端口號(hào) Socket socket=new Socket("169.254.132.203",2000); //輸入流讀取服務(wù)器發(fā)送的信息 BufferedReader br=new BufferedReader(new InputStreamReader(socket.getInputStream())); //開啟自動(dòng)刷新緩沖區(qū) PrintWriter pw=new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())),true); //從鍵盤讀取數(shù)據(jù) BufferedReader ii=new BufferedReader(new InputStreamReader(System.in)); System.out.println(br.readLine()); //因?yàn)殚_啟了自動(dòng)刷新,且調(diào)用的是println方法,所以可以不調(diào)用flush方法 pw.println("請(qǐng)求進(jìn)入搖頭耶穌的世界"); //pw.flush(); while(true) { String str=ii.readLine(); //使用了回車符來(lái)保證另一端的readLine方法正常結(jié)束 pw.write(str+"\r"); pw.flush(); //如果輸入quit則退出聊天室 if(str.equals("quit")) { break; } } socket.shutdownInput(); socket.shutdownOutput(); socket.close(); } }
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)腳本之家的支持。
相關(guān)文章
Spring Boot 2.0 配置屬性自定義轉(zhuǎn)換的方法
這篇文章主要介紹了Spring Boot 2.0 配置屬性自定義轉(zhuǎn)換的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-11-11java并發(fā)編程StampedLock高性能讀寫鎖詳解
這篇文章主要為大家介紹了java并發(fā)編程StampedLock高性能讀寫鎖的示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-05-05Java使用正則表達(dá)式提取XML節(jié)點(diǎn)內(nèi)容的方法示例
這篇文章主要介紹了Java使用正則表達(dá)式提取XML節(jié)點(diǎn)內(nèi)容的方法,結(jié)合具體實(shí)例形式分析了java針對(duì)xml格式字符串的正則匹配相關(guān)操作技巧,需要的朋友可以參考下2017-08-08使用springboot結(jié)合vue實(shí)現(xiàn)sso單點(diǎn)登錄
這篇文章主要為大家詳細(xì)介紹了如何使用springboot+vue實(shí)現(xiàn)sso單點(diǎn)登錄,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-06-06Java設(shè)計(jì)模式中的策略(Strategy)模式解讀
這篇文章主要介紹了Java設(shè)計(jì)模式中的策略(Strategy)模式解讀,對(duì)象的某個(gè)行為,在不同場(chǎng)景有不同實(shí)現(xiàn)方式,可以將這些行為的具體實(shí)現(xiàn)定義為一組策略,每個(gè)實(shí)現(xiàn)類實(shí)現(xiàn)一種策略,在不同場(chǎng)景使用不同的實(shí)現(xiàn),并且可以自由切換策略,需要的朋友可以參考下2023-10-10Spring?AOP?創(chuàng)建代理對(duì)象詳情
這篇文章介紹了Spring?AOP?創(chuàng)建代理對(duì)象詳情,主要介紹AOP?創(chuàng)建代理對(duì)象和上下文相關(guān)的內(nèi)容,下文分享具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-05-05類似Object監(jiān)視器方法的Condition接口(詳解)
下面小編就為大家?guī)?lái)一篇類似Object監(jiān)視器方法的Condition接口(詳解)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-05-05JavaMail實(shí)現(xiàn)發(fā)送郵件(QQ郵箱)
這篇文章主要為大家詳細(xì)介紹了JavaMail實(shí)現(xiàn)發(fā)送郵件(QQ郵箱),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-08-08