基于Java的TCP Socket通信详解
TCP Socket通信是一種比較常用的基于連接的網(wǎng)絡通信方式。本文通過Java實現(xiàn)TCP Socket通信,并將其用于計算機端,Android手機端,硬件設備端,同時做到代碼規(guī)范化,實現(xiàn)代碼最大化復用。
| 本文代碼可在GitHub下載,建議對照源碼閱讀文章 https://github.com/IVanMissAya/tcp_server
TCP連接的建立
客戶端和服務器間通過 三次握手 建立TCP連接。在Java中,連接建立完成后,服務器端和客戶端分別獲取到一個Socket實例,之后就可以通過這個Socket實例進行通信。服務器端和客戶端使用不同的方法獲取Socket實例。
服務器端
在服務器端,通過ServerSocket實現(xiàn)對指定端口的監(jiān)聽,代碼如下。其中port為int型端口數(shù)值,取值065535,01024為系統(tǒng)保留端口,這里取值1234。如果發(fā)生錯誤將會拋出異常。
int port = 1234; ServerSocket server = new ServerSocket(port);通過ServerSocket.accept()方法接受客戶端連接。這個方法是阻塞的,從調(diào)用時開始監(jiān)聽端口,直到客戶端連接建立時,執(zhí)行結(jié)束并返回Socket實例。連接建立失敗會拋出異常。
Socket socket = server.accept();客戶端
客戶端直接通過實例化的形式,產(chǎn)生Socket實例。實例化的過程中,嘗試連接指定的服務器主機。連接成功則實例化完成,連接失敗則拋出異常。hostIP為主機的IP地址,port為端口號,和服務器主機監(jiān)聽的端口號保持一致。
String hostIP = "127.0.0.1"; int port = 1234; Socket socket = new Socket(hostIP, port);連接的建立過程
以上代碼的執(zhí)行順序是:
- 服務器端實例化ServerSocket:new ServerSocket(port);
- 服務器端執(zhí)行accept(),監(jiān)聽指定端口,此方法阻塞等待客戶端連接:server.accept();
- 客戶端實例化Socket實例,嘗試連接服務器:new Socket(hostIP, port);
- TCP三次握手成功,服務器端的accept()返回Socket實例,同時客戶端的Socket實例化成功。
Socket的讀寫
以收發(fā)字符串為例來說明Socket的讀寫。
向Socket對象寫入數(shù)據(jù),則會發(fā)送至TCP連接的另一方。這個操作在服務器端和客戶端是一樣的。可通過獲取Socket的輸出流來寫入UTF8格式編碼的字符串,代碼如下。寫入完成后,就會被發(fā)送到連接的另一端。
private DataOutputStream out; out = new DataOutputStream(socket.getOutputStream()); String s = "Test"; out.writeUTF(s); out.flush();在接收端,通過獲取Socket的輸入流,就可以讀取字符串數(shù)據(jù),代碼如下。readUTF()方法是阻塞的,直到對方發(fā)送完一個字符串,該方法才會執(zhí)行結(jié)束并返回收到的字符串。如果連接中斷,或強制關閉Socket的輸入流,即執(zhí)行socket.shutdownInput(),該方法會拋出異常。
private DataInputStream in; in = new DataInputStream(socket.getInputStream()); String s = in.readUTF();在建立了TCP連接后,由于無法確定對方的數(shù)據(jù)發(fā)送時間,為了保證及時接收到數(shù)據(jù),通過一個新線程不斷調(diào)用in.readUTF()方法讀取數(shù)據(jù)(相當于輪詢法);并在接收到數(shù)據(jù)后回調(diào)相關函數(shù),對數(shù)據(jù)進行處理。
TCP連接的斷開
TCP Socket連接是雙向的,通過 四次揮手 的方式斷開,雙方分別調(diào)用Socket.close()方法斷開連接。連接斷開的過程中,一般一方A先斷開連接,另一方B發(fā)現(xiàn)A斷開連接后,也斷開連接。為方便表述,將先斷開連接的一方A稱為“主動斷開連接”;后斷開的一方B,則為“被動斷開連接”。
在一方B阻塞執(zhí)行in.readUTF()方法時,如果對方A主動斷開Socket連接,這個方法會拋出異常。從而在B處理異常時,可以被動的斷開這邊的連接。
為保證主動斷開連接的一方不會阻塞在in.readUTF()方法中,需要先執(zhí)行socket.shutdownInput()。所以主動斷開連接的代碼如下。
socket.shutdownInput(); in.close(); socket.close();被動斷開連接的一方,在捕獲到in.readUTF()的異常后,斷開Socket連接。
try {String s = in.readUTF(); } catch (IOException e) {// 連接被斷開(被動)try {in.close();socket.close();in = null;socket = null;} catch (IOException e) {e.printStackTrace();} }SocketTransceiver的實現(xiàn)
考慮到在服務器端和客戶端,Socket對象的操作是完全一樣的,所以實現(xiàn)了一個SocketTransceiver(收發(fā)器),實現(xiàn)對Socket的直接操作,其他代碼則通過SocketTransceiver間接操作Socket對象實現(xiàn)數(shù)據(jù)收發(fā)、斷開連接等。
SocketTransceiver實現(xiàn)的功能有:
- 開啟新線程不斷查詢Socket是否收到數(shù)據(jù);
- 將字符串、文件等類型的數(shù)據(jù)進行打包,并通過Socket發(fā)送;
- 從Socket接收數(shù)據(jù),并自動解析出數(shù)據(jù)(字符串、文件等),接收完成后回調(diào)相應的方法;
- 在發(fā)生錯誤、連接被動斷開時,自動斷開連接并進行相關處理,并回調(diào)相應方法。
SocketTransceiver.class 使用抽象類實現(xiàn),回調(diào)方法是抽象的,實例化時對抽象方法進行實現(xiàn),處理回調(diào)。完整代碼見附件。
TcpServer的實現(xiàn)
TcpServer為TCP Socket服務器端程序。為了讓服務器能同時接受并處理來自多個客戶端的TCP連接請求:
- TcpServer中用一個監(jiān)聽線程對端口進行監(jiān)聽,即阻塞執(zhí)行server.accept()方法,等待接受客戶端連接;
- 服務器端每次與一個客戶端建立連接,即accept()方法執(zhí)行結(jié)束并返回一個Socket對象,就會用一個SocketTransceiver對這個Socket進行操作;
- 連接建立后,監(jiān)聽線程再次執(zhí)行server.accept()方法,繼續(xù)監(jiān)聽端口并等待下一個連接;
- 服務器端有一個List,保存當前連接的每個客戶端對應的SocketTransceiver對象,在需要時可取出并進行操作。
TcpClient的實現(xiàn)
TcpClient為TCP Socket客戶端程序。主要工作是進行Socket的連接,并利用SocketTransceiver對Socket進行操作。
另附 windows 下tcp 測試工具
sokit 是一款開源免費的 TCP / UDP 測試(調(diào)試)工具, 可以用來接收,發(fā)送或轉(zhuǎn)發(fā)TCP/UDP數(shù)據(jù)包。 下載地址: http://sqdownd.onlinedown.net/down/sokit-1.3-win32-chs.zip
TCPUDP測試工具用于開發(fā)網(wǎng)絡通訊程序時,在服務器或客戶端測試TCP/UDP通訊連接和測試數(shù)據(jù)的接收和發(fā)送情況。 下載地址: http://fastsoft.onlinedown.net/down/TCPUDPDebug102_Setup.exe
總結(jié)
以上是生活随笔為你收集整理的基于Java的TCP Socket通信详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: WebView退出时停止视频播放
- 下一篇: 对Java注解(Annotation)初