基于Java的TCP Socket通信详解(计算机端/Android手机端)
TCP Socket通信是一種比較常用的基于連接的網絡通信方式。本文通過Java實現TCP Socket通信,并將其用于計算機端、Android手機端,同時做到代碼規范化,實現代碼最大化復用。
本文代碼可在GitHub下載,建議對照源碼閱讀文章?https://github.com/jzj1993/JavaTcpSocket
TCP連接的建立
客戶端和服務器間通過三次握手建立TCP連接。在Java中,連接建立完成后,服務器端和客戶端分別獲取到一個Socket實例,之后就可以通過這個Socket實例進行通信。服務器端和客戶端使用不同的方法獲取Socket實例。
服務器端
在服務器端,通過ServerSocket實現對指定端口的監聽,代碼如下。其中port為int型端口數值,取值0~65535,0~1024為系統保留端口,這里取值1234。如果發生錯誤將會拋出異常。
通過ServerSocket.accept()方法接受客戶端連接。這個方法是阻塞的,從調用時開始監聽端口,直到客戶端連接建立時,執行結束并返回Socket實例。連接建立失敗會拋出異常。
客戶端
客戶端直接通過實例化的形式,產生Socket實例。實例化的過程中,嘗試連接指定的服務器主機。連接成功則實例化完成,連接失敗則拋出異常。hostIP為主機的IP地址,port為端口號,和服務器主機監聽的端口號保持一致。
連接的建立過程
以上代碼的執行順序是:
Socket的讀寫
以收發字符串為例來說明Socket的讀寫。
向Socket對象寫入數據,則會發送至TCP連接的另一方。這個操作在服務器端和客戶端是一樣的。可通過獲取Socket的輸出流來寫入UTF8格式編碼的字符串,代碼如下。寫入完成后,就會被發送到連接的另一端。
在接收端,通過獲取Socket的輸入流,就可以讀取字符串數據,代碼如下。readUTF()方法是阻塞的,直到對方發送完一個字符串,該方法才會執行結束并返回收到的字符串。如果連接中斷,或強制關閉Socket的輸入流,即執行socket.shutdownInput(),該方法會拋出異常。
在建立了TCP連接后,由于無法確定對方的數據發送時間,為了保證及時接收到數據,通過一個新線程不斷調用in.readUTF()方法讀取數據(相當于輪詢法);并在接收到數據后回調相關函數,對數據進行處理。
TCP連接的斷開
TCP Socket連接是雙向的,通過四次揮手的方式斷開,雙方分別調用Socket.close()方法斷開連接。連接斷開的過程中,一般一方A先斷開連接,另一方B發現A斷開連接后,也斷開連接。為方便表述,將先斷開連接的一方A稱為“主動斷開連接”;后斷開的一方B,則為“被動斷開連接”。
在一方B阻塞執行in.readUTF()方法時,如果對方A主動斷開Socket連接,這個方法會拋出異常。從而在B處理異常時,可以被動的斷開這邊的連接。
為保證主動斷開連接的一方不會阻塞在in.readUTF()方法中,需要先執行socket.shutdownInput()。所以主動斷開連接的代碼如下。
被動斷開連接的一方,在捕獲到in.readUTF()的異常后,斷開Socket連接。
SocketTransceiver的實現
考慮到在服務器端和客戶端,Socket對象的操作是完全一樣的,所以實現了一個SocketTransceiver(收發器),實現對Socket的直接操作,其他代碼則通過SocketTransceiver間接操作Socket對象實現數據收發、斷開連接等。
SocketTransceiver實現的功能有:
- 開啟新線程不斷查詢Socket是否收到數據;
- 將字符串、文件等類型的數據進行打包,并通過Socket發送;
- 從Socket接收數據,并自動解析出數據(字符串、文件等),接收完成后回調相應的方法;
- 在發生錯誤、連接被動斷開時,自動斷開連接并進行相關處理,并回調相應方法。
SocketTransceiver.class使用抽象類實現,回調方法是抽象的,實例化時對抽象方法進行實現,處理回調。完整代碼見附件。
TcpServer的實現
TcpServer為TCP Socket服務器端程序。為了讓服務器能同時接受并處理來自多個客戶端的TCP連接請求:
- TcpServer中用一個監聽線程對端口進行監聽,即阻塞執行server.accept()方法,等待接受客戶端連接;
- 服務器端每次與一個客戶端建立連接,即accept()方法執行結束并返回一個Socket對象,就會用一個SocketTransceiver對這個Socket進行操作;
- 連接建立后,監聽線程再次執行server.accept()方法,繼續監聽端口并等待下一個連接;
- 服務器端有一個List<SocketTransceiver>,保存當前連接的每個客戶端對應的SocketTransceiver對象,在需要時可取出并進行操作。
TcpServer.class的完整代碼見附件。
TcpClient的實現
TcpClient為TCP Socket客戶端程序。主要工作是進行Socket的連接,并利用SocketTransceiver對Socket進行操作。TcpClient.class的完整代碼見附件。
桌面服務器端的測試代碼
完成了上面這三個主要的類,服務器端的實現就非常容易了。服務器端需要使用SocketTransceiver.class、TcpServer.class兩個類。
服務器端編寫了一個桌面版本,測試代碼ClsMainServer.class在工程SocketServer-Desktop中,詳見附件。在測試代碼中,實現了一個服務器程序,監聽端口1234并接受客戶端連接,每次接收到客戶端主動發送的數據,就將數據原樣返回給這個客戶端。
桌面客戶端的測試代碼
客戶端需要使用SocketTransceiver.class、TcpClient.class兩個類。
客戶端的桌面版本測試代碼ClsMainClient.class在工程SocketClient-Desktop中,詳見附件。在測試代碼中,實現了兩個客戶端,并交替向服務器發送數據。客戶端直接連接本機服務器端,IP地址為127.0.0.1。
安卓客戶端的實現
同樣的代碼還可以直接用于安卓客戶端。安卓客戶端編寫了一個簡單的界面,輸入IP和端口可進行連接,同時可以輸入字符串發送至服務器端,另外會將接收到的數據顯示出來。
安卓端程序的實現,要注意幾點:
- 網絡操作代碼不能在主線程(即UI線程)中執行
- 界面操作不能在非UI線程執行,回調中不能直接執行刷新界面的代碼,可以通過向UI線程的Handler發送Runnable對象進行操作,即Handler.post(Runnable r)方法。
- 需要在AndroidManifest.xml中添加網絡訪問權限<uses-permission android:name="android.permission.INTERNET" />
測試可用的代碼詳見附件中的工程SocketClient-Android。
網絡連接相關問題
如果代碼編譯通過,但是測試不能正常運行,則有可能為網絡連接配置相關問題。
電腦和手機之間的網絡連接方式
- 電腦共享Wifi,用手機連接
- 手機電腦連在一個局域網
確保IP地址與端口設置正確
確保程序中的IP地址和端口設置正確。服務器端代碼只需設置端口,最好不要處于0~1024之間,并避免端口被系統其它程序占用;在客戶端,使用的端口和服務器相同,IP地址設置如下:
- 如果客戶端和服務器是在同一個設備上,IP地址可以直接用127.0.0.1,也可以用服務器任意網卡的IP地址
- 如果服務器和客戶端處于同一個局域網,IP地址用服務器連接到該局域網的網卡的IP地址
- 如果客戶端連接到服務器共享的Wifi網絡,IP地址用服務器共享Wifi的無線網卡的IP地址
防火墻設置
在Windows中,需要設置防火墻允許程序訪問網絡。通常在軟件第一次訪問網絡時,會彈出窗口提示是否允許程序訪問網絡,勾選允許并點擊確定即可。也可以在控制面板設置:控制面板 --> 系統和安全 --> Windows 防火墻 --> 允許程序或功能通過Windows防火墻 --> 更改設置 --> 勾選Java后面的選框并確認即可,如圖。
嘗試用管理員權限運行Eclipse
如果仍然不能連接,可以嘗試用管理員權限運行Eclipse。
代碼測試
Eclipse窗口操作技巧
- 默認情況下,多個程序同時在命令行輸出時,Eclipse的Console會自動切換到最后輸出字符串的程序,點擊圖釘形狀的按鈕如圖,可以讓Console不再切換。
- 點擊右側的加號按鈕,下拉菜單中選擇New Console View,可以新建一個Console命令行窗口,設置每個窗口顯示不同程序輸出的內容。
- 點擊Console中的紅色方形按鈕可以停止程序的運行。
測試
將工程SocketServer-Desktop和SocketClient-Desktop導入Eclipse中,先執行服務器端,再執行客戶端。如果先執行客戶端,會提示連接失敗。
桌面客戶端程序啟動后會創建兩個TcpClient客戶端實例同時連接服務器,并交替發送數據。客戶端連接和發送數據時,服務器端會輸出相應的提示信息。
停止服務器端程序,客戶端會提示連接中斷,并中止程序執行;停止客戶端程序,服務器端會提示客戶端斷開連接。
將安卓版的Client編譯并安裝到手機,連接網絡讓安卓端和計算機端在同一個局域網中。讓服務器端程序啟動,再點擊安卓端的“連接”按鈕進行連接,如果網絡錯誤、服務器未啟動等原因,安卓端會提示“連接失敗”。如果連接成功,服務端會輸出客戶端連接的提示信息。
同樣,可以在安卓客戶端向服務器發送數據,服務器會將數據原樣返回。
執行結果如圖。
本文由jzj1993原創,轉載請注明來源:http://www.hainter.com/java-tcp-socket
總結
以上是生活随笔為你收集整理的基于Java的TCP Socket通信详解(计算机端/Android手机端)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 鸿星尔克的基金叫什么 鸿星尔克的基金名字
- 下一篇: 沪惠保在哪里看保单