Socket整體流程
Socket編程主要涉及到客戶端和服務端兩個方面,首先是在服務器端創建一個服務器套接字(ServerSocket),並把它附加到一個端口上,服務器從這個端口監聽連接。端口號的范圍是0到65536,但是0到1024是為特權服務保留的端口號,我們可以選擇任意一個當前沒有被其他進程使用的端口。
客戶端請求與服務器進行連接的時候,根據服務器的域名或者IP地址,加上端口號,打開一個套接字。當服務器接受連接后,服務器和客戶端之間的通信就像輸入輸出流一樣進行操作。
如果一個程序創建了一個socket,並讓其監聽80端口,其實是向TCP/IP協議棧聲明了其對80端口的占有。以后,所有目標是80端口的TCP數據包都會轉發給該程序(這里的程序,因為使用的是Socket編程接口,所以首先由Socket層來處理)。所謂accept函數,其實抽象的是TCP的連接建立過程。 accept函數返回的新socket其實指代的是本次創建的連接,而一個連接是包括兩部分信息的,一個是源IP和源端口,另一個是宿IP和宿端口。所以,accept可以產生多個不同的socket,而這些socket里包含的宿IP和宿端口是不變的,變化的只是源IP和源端口。這樣的話,這些socket宿端口就可以都是80,而Socket層還是能根據源/宿對來准確地分辨出IP包和socket的歸屬關系,從而完成對TCP/IP協議的操作封裝。TCP/IP只是一個協議棧,就像操作系統的運行機制一樣,必須要具體實現,同時還要提供對外的操作接口。就像操作系統會提供標准的編程接口,比如 Win32編程接口一樣,TCP/IP也必須對外提供編程接口,這就是Socket編程接口。
客戶端上的使用
getInputStream()方法可以得到一個輸入流,客戶端的Socket對象上的getInputStream方法得到輸入流其實就是從服務器端發回的數據。
getOutputStream()方法得到的是一個輸出流,客戶端的Socket對象上的getOutputStream方法得到的輸出流其實就是發送給服務器端的數據。
服務器端上的使用
getInputStream()方法得到的是一個輸入流,服務端的Socket對象上的getInputStream方法得到的輸入流其實就是從客戶端發送給服務器端的數據流。
getOutputStream()方法得到的是一個輸出流,服務端的Socket對象上的getOutputStream方法得到的輸出流其實就是發送給客戶端的數據。
ServerSocket類的accept()阻塞
ServerSocket的accept()方法是偵聽並接受到此套接字的連接,就是一直等待連接,此方法在連接傳入之前一直阻塞(即后面的代碼不會往下執行)。直到接受到有socket的連接,然后創建並返回新的Socket對象。
read()阻塞
從socket上讀取對端發過來的數據一般有兩種方法:
1)按照字節流讀取
2)按照字符流讀取
這段代碼執行以后會發現read()方法發生了阻塞,經過查找資料發現:
read() 是一個阻塞函數,如果客戶端沒有聲明斷開outputStream那么它就會認為客戶端仍舊可能發送數據,所以就會一直阻塞而不是返回-1,所以System.out.println("服務器");這行代碼在連接斷開之前就一直不會執行,因為在while ((len = is.read(buf)) != -1) 這里阻塞了。
像read()這種阻塞讀取函數還有BufferedReader類種的 readLine()、DataInputStream種的readUTF()等。
這個特性使得編程非常方便也很高效。
但是這樣也有一個問題,就是如何讓程序從這兩個方法的阻塞調用中返回。
總結一下,有這么幾個方法:
1、發送完后調用Socket的shutdownOutput()方法關閉輸出流,這樣對端的輸入流上的read操作就會返回-1。 注意不能調用socket.getInputStream().close()。這樣會導致socket被關閉。
當然如果不需要繼續在socket上進行讀操作,也可以直接關閉socket。 但是這個方法不能用於通信雙方需要多次交互的情況。
1 os.write("sender say hello socket".getBytes()); 2 os.flush(); 3 client.shutdownOutput(); //調用shutdown 通知對端請求完畢
這個解決方案缺點非常明顯,socket任意一端都依賴於對方調用shutdownOutput()來完成read返回 -1,如果任意一方沒有執行shutdown函數那么就會出現問題。所以一般我們都會在socket請求時設置連接的超時時間 socket.setSoTimeout(5000);以防止長時間沒有響應造成系統癱瘓。
1 while (true) { 2 server = serverSocket.accept(); 3 System.out.println("server socket is start……"); 4 server.setSoTimeout(5000); 5 ..... 6 }
參考文章:
https://blog.csdn.net/anthony_ju/article/details/82192135
https://www.cnblogs.com/swordfall/p/10781281.html
https://www.cnblogs.com/gaoqiri/p/10055610.html
https://blog.csdn.net/yanchuang1/article/details/48049259