JAVA套接字之TCP編程
1 TCP協議
TCP是面向諒解的協議。所謂連接,就是兩個對等實體為進行數據通信而進行的一種結合。面向連接服務是在數據交換之前,必須先建立連接。當數據交換結束后,則應終止這個連接。
面向連接服務具有:連接建立、數據傳輸和連接釋放這三個階段。在傳送數據時是按序傳送的。
當一臺計算機需要與另一臺遠程計算機連接時,TCP協議會讓他們建立一個連接:用于發送和接收數據的虛擬鏈路。TCP協議負責收集信息包,并將其按適當的次序放好傳送,在接收端收到后再將其正確的還原。為了保證數據包在傳送中準確無誤,TCP使用了重發機制:當一個通信實體發送一個消息給另一個通信實體后需要收到另一個實體的確認信息,如果沒有收到確認信息,則會再次重發剛才發送的信息。
TCP通信分為客戶端和服務器端,對應的對象是分別是Socket和ServerSocket。
Socket類是java執行客戶端TCP操作的基礎類,這個類本身使用代碼通過主機操作系統的本地TCP棧進行通信。Socket類的方法會建立和銷毀連接,設置各種Socket選項。
ServerSocket類是java執行服務器端操作的基礎類,該類運行于服務器,監聽入棧TCP連接,每個socket服務器監聽服務器的某個端口,當遠程主機的客戶端嘗試連接此端口時,服務器就被喚醒,并返回一個表示兩臺主機之間socket的正常的Socket對象。
ServerSocket和Socket通信流程:
2 ServerSocket
2.1 構造函數
ServerSocket()throws IOException
ServerSocket(int port)throws IOException
ServerSocket(int port, int backlog)throws IOException
ServerSocket(int port, int backlog, InetAddress bindAddr)throws IOException
在以上構造方法中,參數port指定服務器要綁定的端口(服務器要監聽的端口),參數backlog指定客戶端連接請求隊列的長度,參數bindAddr指定服務器要綁定的IP地址。
2.1.1 綁定端口
除了第一個不帶參數的構造方法以外,其他構造方法都會使服務器與特定的端口綁定,該端口由參數port指定。例如,ServerSocket serverSocket = new SererSocket(80);
如果運行時無法綁定到80端口,以上代碼會拋出IOException,更確切地說,是拋出BindException,它是IOException的子類。BindException一般由以下原因造成:
- 端口已經被其他服務器進程占用;
- 在某些操作系統中,如果沒有以超級管理員用戶身份來運行服務器程序,那么操作系統不允許服務器綁定到1~1023之間的端口。
- 如果把port設置為0,表示由操作系統來為服務器分配一個任意可用的端口。由操作系統分配的端口也稱為匿名端口。對于多數服務器,會使用明確的端口,而不會使用匿名端口,因為客戶程序需要事先知道服務器的端口,才能方便的訪問服務器。在某些場合,匿名端口有著特殊的用途。
2.1.2 設定客戶端連接請求隊列的長度
當服務器進程運行時,可能會同時監聽到多個客戶端的連接請求。例如,每當一個客戶端進程執行以下代碼:
Socket sock = new Socket("192.168.32.105",80);
就意味著在遠程主機的80端口上,監聽到一個客戶端的連接請求。管理客戶端請求的任務是由操作系統完成的。操作系統把這些請求連接存儲在一個先入先出的隊列中。許多操作系統限定了隊列的最大長度,一般為50.當隊列中的連接請求達到隊列的最大容量時,服務器進程所在的主機會拒絕新的連接請求。只有當服務器進程通過ServerSocket的accept()方法從隊列中取出連接請求,使隊列騰出空位時,隊列才能繼續加入新的連接請求。
對于客戶進程,如果它發出的連接請求被加入到服務器的隊列中,就意味著客戶端與服務器的連接建立成功,客戶進程從Socket構造方法中正常返回。如果客戶進程發出的連接請求被服務器拒絕,Socket構造方法會拋出ConnectionException。
ServerSocket構造方法的backlog參數用來顯式設置連接請求隊列的長度,它將覆蓋操作系統限定的隊列的最大長度。在以下幾種情況中,仍然會采用操作系統限定的隊列的最大長度:
- backlog參數的值大于操作系統限定的隊列的最大長度;
- backlog參數的值小于或者等于0;
- 在ServerSocket構造方法中沒有指定backlog。
2.1.3 設定綁定IP地址
如果主機只有一個IP地址,那么默認情況下,服務器程序就與該IP地址綁定。ServerSocket的第4個構造方法ServerSocket(int port, int backlog, InetAddress bindAddr)有一個bindAddr參數,它顯式指定服務器要綁定的IP地址,該構造方法適用于具有多個IP地址的主機。假定一個主機有兩個網卡,一個網卡用于連接到Internet, IP地址為222.67.5.94,還有一個網卡用于連接到本地局域網,IP地址為192.168.3.4。如果服務器僅僅被本地局域網中的客戶訪問,那么可以按如下方式創建ServerSocket:
ServerSocket serverSocket = new ServerSocket(80,10,InetAddress.getByName ("192.168.3.4"));
2.1.4 默認構造方法的作用
ServerSocket有一個不帶參數的默認構造方法。通過該方法創建的ServerSocket不與任何端口綁定,接下來還需要通過bind()方法與特定端口綁定。
這個默認構造方法的用途是,允許服務器在綁定到特定端口之前,先設置ServerSocket的一些選項。因為一旦服務器與特定端口綁定,有些選項就不能再改變了。
在以下代碼中,先把ServerSocket的SO_REUSEADDR選項設為true,然后再把它與8000端口綁定:
ServerSocket serverSocket=new ServerSocket();
serverSocket.setReuseAddress(true); //設置ServerSocket的選項
serverSocket.bind(new InetSocketAddress(8000)); //與8000端口綁定
如果把以上程序代碼改為:
ServerSocket serverSocket=new ServerSocket(8000);
serverSocket.setReuseAddress(true); //設置ServerSocket的選項
那么serverSocket.setReuseAddress(true)方法就不起任何作用了,因為SO_ REUSEADDR選項必須在服務器綁定端口之前設置才有效。
2.2 接收和關閉與客戶的連接
ServerSocket的accept()方法從連接請求隊列中取出一個客戶的連接請求,然后創建與客戶連接的Socket對象,并將它返回。如果隊列中沒有連接請求,accept()方法就會一直等待,直到接收到了連接請求才返回。
接下來,服務器從Socket對象中獲得輸入流和輸出流,就能與客戶交換數據。當服務器正在進行發送數據的操作時,如果客戶端斷開了連接,那么服務器端會拋出一個IOException的子類SocketException異常:
java.net.SocketException: Connection reset by peer
這只是服務器與單個客戶通信中出現的異常,這種異常應該被捕獲,使得服務器能繼續與其他客戶通信。
2.3 關閉ServerSocket
ServerSocket的close()方法是服務器釋放占用的端口,并且斷開與所有客戶端的連接。當一個服務器程序運行結束時,即使沒有執行ServerSocket的close()方法,操作系統也會釋放這個服務器占用的端口。因此,服務器程序并不一定要在結束之前執行ServerSocket的close方法。
在某些情況下,如果希望及時釋放服務器的端口,以便讓其他程序占用這個端口,則可以顯式調用close()方法。例如以下代碼用于掃描1~65535之間的端口號。如果ServerSocket成功創建,意味著該端口未被其他服務器進程綁定,否者說明該端口已經被其他進程占用:
for(int i = 1; i < 65535; i++){
try{
ServerSocket serverSocket = new ServerSocket(i);
serverSocket.close();
}catch(Exception e){
System.out.println("端口" + i + "已經被其他服務器進程占用");
}
}
以上程序代碼創建了一個ServerSocket對象后,就馬上關閉它,以便及時釋放它占用的端口,從而避免程序臨時占用系統的大多數端口。
ServerSocket的isClose()方法判斷ServerSocket是否關閉,只有執行了ServerSocket的close()方法,isClose()方法的返回值為true,即使ServerSocket還沒有和特定的端口綁定,isClose()方法的返回值也是false。
ServerSocket的isBound()方法判斷ServerSocket是否已經與一個端口綁定,只要ServerSocket已經與一個端口綁定,即使它已經被關閉,isBound()方法也會返回true。
如果要確定一個ServerSocket已經與特定端口綁定,并且還沒有被關閉,則可以采用以下方式:
boolean isOpen = serverSocket.isBound() && !serverSocket.isClosed();
2.4 獲取ServerSocket的信息
ServerSocket的以下兩個get方法可分別獲得服務器綁定的IP地址,以及綁定的端口:
- public InetAddress getInetAddress();
- public int getLocalPort()
前面已經講到,在構造ServerSocket時,如果把端口設為0,那么將由操作系統為服務器分配一個端口(稱為匿名端口),程序只要調用getLocalPort()方法就能獲知這個端口號。如例程3-3所示的RandomPort創建了一個ServerSocket,它使用的就是匿名端口。
多數服務器會監聽固定的端口,這樣才便于客戶程序訪問服務器。匿名端口一般適用于服務器與客戶之間的臨時通信,通信結束,就斷開連接,并且ServerSocket占用的臨時端口也被釋放。
3 Socket
3.1 構造函數
Socket()
Socket(InetAddress address, int port)throws UnknownHostException, IOException
Socket(InetAddress address, int port, InetAddress localAddress, int localPort)throws IOException
Socket(String host, int port)throws UnknownHostException, IOException
Socket(String host, int port, InetAddress localAddress, int localPort)throws IOException
除了第一種不帶參數以外,其他構造函數會嘗試建立與服務器的連接。如果失敗會拋出IOException錯誤,如果成功則返回Socket對象。
InetAddress是一個用于記錄主機的類,其靜態getHostByName(String msg)可以返回一個實例,其靜態方法getLocalHost()也可以獲得當前主機的IP地址,并返回一個實例。Socket(String host, int port, InetAddress localAddress, int localPort)構造函數的參數分別為目標IP、目標端口、綁定本地IP、綁定本地端口。
3.2 Socket方法
getInetAddress(); 遠程服務端的IP地址
getPort(); 遠程服務端的端口
getLocalAddress() 本地客戶端的IP地址
getLocalPort() 本地客戶端的端口
getInputStream(); 獲得輸入流
getOutStream(); 獲得輸出流
3.3 Socket狀態
isClosed(); //連接是否已關閉,若關閉,返回true;否則返回false
isConnect(); //如果曾經連接過,返回true;否則返回false
isBound(); //如果Socket已經與本地一個端口綁定,返回true;否則返回false
如果要確認Socket的狀態是否處于連接中,可以通過下面語句。
//判斷當前是否處于連接
boolean isConnection = socket.isConnedted() && !socket.isClosed();
參考博文
http://www.cnblogs.com/xujian2014/p/4660570.html