標籤:while 自動 連接埠號碼 解決 src 構造 break 原因 boolean
Java中基於TCP協議實現網路通訊的兩個類:用戶端的Socket和伺服器端的ServerSocket。
Socket通訊模型:
不管Socket通訊的功能有多複雜,任何socket通訊過程的基本結構都是一樣的。其基本步驟為:
①分別在用戶端和伺服器端建立Socket和ServerSocket執行個體;伺服器端通過.accept()方法等待請求並阻塞。請求收到後,建立串連Socket對象。
②通過getInputStream和getOutputStream方法分別在用戶端和伺服器端開啟輸入輸出資料流
③利用IO流進行讀寫操作
④關閉所有的流資源和通訊端資源。
其中,編程工作主要集中在第三步,其他的部分代碼基本相同。所有步驟都可能拋出IO異常!
我在編寫一個簡單的socket程式時,使用的Socket通訊出現了一個問題:我在用戶端寫入的資料,在伺服器端無法輸出。當我從用戶端中斷連線時,之前寫入的所有資料立刻在伺服器端輸出出來了。經過反覆的驗證和求解,以下是我的結論和解決方案。希望有同樣問題的小夥伴看完可以解決問題。
通過一端的Socket建立了PrintWriter類來寫入資料,通過另一端的Socket建立了BufferedReader類來讀取資料並輸出。如果資料寫入後沒有被顯示,可能的原因有兩種:
一、寫入的資料存放區在緩衝區中,沒有被寫入IO流中:
如果不主動的幹涉,寫入的資料會一直堆在緩衝區中,直到緩衝區滿了引發JVM自動重新整理緩衝區。顯然這不符合我們的需求。對於這種情況,PrintWriter類提供了flush()方法來強制重新整理緩衝區,將緩衝區資料寫入IO流中。另外,PrintWriter類的構造器有一個參數”boolean autoflush“,這個參數預設為false,如果設定為true,則會開啟自動重新整理緩衝區功能。但是請注意,這裡的自動重新整理是有觸發條件的,那就是:PrintWriter類寫入資料的方法必須是println、printf或者format方法時,才會觸發自動重新整理。如果是調用write()這類方法寫入資料,是不會觸發自動重新整理的!總結起來,就是三點:autoflush參數設定,write和println方法的選擇,flush方法的使用。對這三個進行組合,就能保證在Socket通訊的某一端寫入資料時,資料一定能成功地寫入到IO流中!
二、讀取資料使用了readLine()方法,該方法沒有正常的結束:
請注意,BufferedReader類的readLine()方法是一個阻塞函數!也就是說,這個方法本身是讀取一行資料,但是它自己識別不了什麼叫做“一行”!當調用該方法讀取完一段資料後,它會阻塞,而不會return它的讀取資料。這就是為什麼有的時候明明已經重新整理了緩衝區正確的寫入資料了,還是通過輸入資料流讀取資料並顯示出來的原因。
對於readLine()方法,它解除阻塞、正確結束並返回讀取的值,只有以下幾種情況:
①讀取的資料裡含有斷行符號符"\r"或者分行符號"\n"或者斷行符號分行符號"\r\n";
②讀取的資料是在另一端通過println方法寫入的,因為println方法內建分行符號;
③BufferedReader類的緩衝區滿了,那麼JVM會自動重新整理緩衝區從而釋放“積攢”的資料(但是鑒於預設緩衝區大小為8192個字元,對於小資料量的通訊,顯然觸發不了);
④對於讀取的資料,寫入這些資料的流發生異常或者直接關閉,那麼readLine()就會把它吃的資料全部吐出來。這就剛好解釋了,為什麼在我的程式中,斷開用戶端Socket串連,伺服器端立刻輸出所有用戶端訊息的原因。
綜上,在Socket通訊過程中,保證某一端輸出資料流的緩衝被重新整理,保證另一端的readLine方法能正常停止,即可解決寫入的資料在另一端無法輸出的問題。
以下是我修改後能成功啟動並執行代碼,分別是伺服器端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 { //建立伺服器通訊端執行個體,設定監聽連接埠為2000 ServerSocket server=new ServerSocket(2000); //開始監聽用戶端的請求,並阻塞 Socket socket=server.accept(); //請求收到後,自動建立串連。通過IO流進行資料轉送 System.out.println("串連建立成功"); OutputStream os=socket.getOutputStream(); PrintWriter pw=new PrintWriter(new BufferedWriter(new OutputStreamWriter(os)),true); pw.write("歡迎訪問搖頭耶穌的世界!"); pw.flush(); //因為我關閉了輸出資料流,所以另一端的readLine方法才正常結束了 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{ //建立用戶端的通訊端,設定已連線的服務器的IP地址和連接埠號碼 Socket socket=new Socket("169.254.132.203",2000); //輸入資料流讀取伺服器發送的資訊 BufferedReader br=new BufferedReader(new InputStreamReader(socket.getInputStream())); //開啟自動重新整理緩衝區 PrintWriter pw=new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())),true); //從鍵盤讀取資料 BufferedReader ii=new BufferedReader(new InputStreamReader(System.in)); System.out.println(br.readLine()); //因為開啟了自動重新整理,且調用的是println方法,所以可以不調用flush方法 pw.println("請求進入搖頭耶穌的世界"); //pw.flush(); while(true) { String str=ii.readLine(); //使用了斷行符號符來保證另一端的readLine方法正常結束 pw.write(str+"\r"); pw.flush(); //如果輸入quit則退出聊天室 if(str.equals("quit")) { break; } } socket.shutdownInput(); socket.shutdownOutput(); socket.close(); }}
Java Socket通訊以及可能出現的問題解決