五、建立TCP客戶端
討論了套接字類的功能後,我們將分析一個完整的TCP客戶端程序。此處我們將看到的客戶端程序是一個daytime客戶端,它連接到一個daytime服務器程序以讀取當前的日期和時間。建立套接字連接並讀取信息是一個相當簡單的過程,只需要少量的代碼。默認情況下daytime服務運行在13端口上。並非每台計算機都運行了daytime服務器程序,但是Unix服務器是客戶端運行的很好的系統。如果你沒有訪問Unix服務器的權限,在第七部分我們給出了TCP daytime服務器程序代碼--有了這段代碼客戶端就可以運行了。
DaytimeClIEnt的代碼
import Java.Net.*
import Java.io.*;
public class DaytimeClIEnt
{
public static final int SERVICE_PORT = 13;
public static void main(String args[])
{
// 檢查主機名稱參數
if (args.length != 1)
{
System.out.println ("Syntax - DaytimeClIEnt host");
return;
}
// 獲取服務器程序的主機名稱
String hostname = args[0];
try
{
// 獲取一個連接到daytime服務的套接字
Socket daytime = new Socket (hostname,
SERVICE_PORT);
System.out.println ("Connection established");
// 在服務器程序停止的情況下設置套接字選項
daytime.setSoTimeout ( 2000 );
// 從服務器程序讀取信息
BufferedReader reader = new BufferedReader (
new InputStreamReader
(daytime.getInputStream()
));
System.out.println ("Results : " +
reader.readLine());
// 關閉連接
daytime.close();
}
catch (IOException ioe)
{
System.err.println ("Error " + ioe);
}
}
}
DaytimeClIEnt是如何工作的
Daytime應用程序是很容易理解的,它使用了文章前面談到的概念。建立套接字、獲取輸入流,在很少的事件中(在連接時像daytime一樣簡單的服務器程序失敗)激活超時設置。不是連接已篩選過的流,而是把有緩沖的讀取程序連接到套接字輸入流,並且把結果顯示給用戶。最後,在關閉套接字連接後客戶端終止。這是你可能得到的最簡單的套接字應用程序了--復雜性來自實現的網絡協議,而不是來自具體網絡的編程。
運行DaytimeClIEnt
運行上面的應用程序很簡單。簡單地把運行daytime服務的計算機的主機名稱作為命令行參數指定並運行它就可以了。如果daytime服務器程序使用了非標准的端口號(在後面會討論),記得需要改變端口號並重新編譯。
例如,如果服務器程序在本機上,為了運行客戶端將使用下面的命令:
Java DaytimeClIEnt localhost
注意
Daytime服務器程序必須正在運行中,否則該客戶端程序將不能建立連接。例如如果你正在使用Wintel系統而不是Unix,那麼你需要運行DaytimeServer(後面會談到
六、ServerSocket類
服務器套接字是一種特定類型的套接字,它用於提供TCP服務。客戶端套接字綁定到本地計算機的任何空的端口,並且連接到特定服務器程序的端口和主機。服務器套接字與它的差別是它們綁定到本地計算機的某個特定的端口,這樣遠程客戶端才能定位某種服務。客戶端套接字連接只能連接到一台計算機,然而服務器套接字能夠滿足多個客戶端的請求。
它工作的方法很簡單--客戶端知道服務運行在某個特定的端口(通常端口號是知名的,並且特定的協議使用特定的端口號,但是服務器程序也可能運行在非標准的端口上)。它們建立連接,在服務器程序內部,連接會被接受。服務器程序可以同時接受多個連接,在某個給定的時刻也可以選擇只接受一個連接。某個連接被接受後,它就表現為正常的套接字,形式為Socket對象--一旦你掌握了Socket類,編寫服務器程序就和編寫客戶端程序幾乎一樣簡單了。服務器程序和客戶端程序的唯一區別是服務器程序幫定到特定的端口,使用ServerSocket對象。 ServerSocket對象就像創建客戶端連接的工廠--你不必親自建立Socket類的實例。這些連接都模擬正常的套接字,因此你能夠把輸入和輸出過濾流關聯到這些連接上。
1、建立ServerSocket
你在建立服務器套接字後,就應該把它綁定到某個本地端口並准備接受輸入的連接。當客戶端試圖連接的時候,它們被放入一個隊列中。一旦這個隊列中的所有空間都被耗盡,其它的連接的就會被拒絕。
構造函數
建立服務器套接字的最簡單的途徑是綁定到某個本地地址,該地址作為使用構造函數的唯一的參數。例如,為了在端口80(通常用於Web服務器程序)上提供某個服務,將使用下面的代碼片斷:
try
{
// 綁定到80端口,提供TCP服務(類似與HTTP)
ServerSocket myServer = new ServerSocket ( 80 );
// ......
}
catch (IOException ioe)
{
System.err.println ("I/O error - " + ioe);
}
這是ServerSocket構造函數的最簡單的形式,但是下面有一些其它的允許更多自定義的構造函數。所有這些函數都是公共的。
· ServerSocket(int port)產生java.io.IOException、Java.lang.SecurityException異常--把服務器套接字綁定到特定的端口號,這樣遠程客戶端才能定位TCP服務。如果傳遞進來的值為零(zero),就使用任何空閒的端口--但是客戶端可能沒辦法訪問該服務,除非用什麼方式通知了客戶端端口號是多少。在默認情況下,隊列的大小設置為50,但是也提供了備用的構造函數,它允許修改這個設置。如果端口已經被綁定了,或者安全性約束條件(例如安全性規則或知名端口上的操作系統約束條件)阻擋了訪問,就會產生異常。
· ServerSocket(int port, int numberOfClIEnts)產生java.io.IOException、Java.lang.SecurityException異常--把服務器套接字綁定到特定的端口號並為隊列分配足夠的空間用於支持特定數量的客戶端套接字。它是ServerSocket(int port)構造函數的重載版本,如果端口已經被綁定了或安全性約束條件阻擋了訪問,就產生異常。
· ServerSocket(int port, int numberOfClIEnts, InetAddress address)產生java.io.IOException、Java.lang.SecurityException異常--把服務器套接字綁定到特定的端口號,為隊列分配足夠的空間以支持特定數量的客戶端套接字。它是ServerSocket(int port, int numberOfClIEnts)構造函數的重載版本,在多地址計算機上,它允許服務器套接字綁定到某個特定的IP地址。例如,某台計算機可能有兩塊網卡,或者使用虛擬IP地址把它配置成像幾台計算機一樣工作的時候。如果地址的值為空(null),服務器套接字將在所有的本地地址上接受請求。如果端口已經被綁定了或者安全性約束條件阻擋了訪問,就產生異常。
2、使用ServerSocket
雖然 Socket類幾乎是通用的,並且有很多方法,但是Server Socket類沒有太多的方法,除了接受請求並作為模擬客戶端和服務器之間連接的Socket對象的產生組件就沒有幾個了。其中最重要的方法是 accept()方法,它接受客戶端連接請求,但是還有其它幾個開發者可能感到有用的方法。
方法
如果沒有注明的話該方法就是公共的。
· Socket accept()產生java.io.IOException、Java.lang.Security異常--等待客戶端向某個服務器套接字請求連接,並接受連接。它是一種阻塞(blocking)I/O操作,並且不會返回,直到建立一個連接(除非設置了超時套接字選項)。當連接建立時,它將作為 Socket對象被返回。當接受連接的時候,每個客戶端請求都被默認的安全管理程序驗證,這使得接受一定IP地址並阻塞其它IP地址、產生異常成為可能。但是,服務器程序不必依賴安全管理程序阻塞或終止連接--可以通過調用客戶端套接字的getInetAddress()方法確定客戶端的身份。
· void close()產生Java.io.IOException異常--關閉服務器套接字,取消TCP端口的綁定,允許其它的服務使用該端口。
· InetAddress getInetAddress()--返回服務器套接字的地址,在多地址計算機中(例如某個計算機的本地主機可以通過兩個或多個IP地址訪問)它可能與本地地址不同。
· int getLocalPort()--返回服務器套接字綁定到的端口號。
· int getSoTimeout()產生Java.io.IOException異常--返回超時套接字選項的值,該值決定accept()操作可以阻塞多少毫秒。如果返回的值為零,accept()操作無限期阻塞。
· void implAccept(Socket socket)產生Java.io.IOException異常--這個方法允許ServerSocket子類傳遞一個未連接的套接字子類,讓這個套接字對象接受輸入的請求。使用implAccept方法接受連接時,重載的ServerSocket.accept()方法可以返回已連接的套接字。很少開發者希望對ServerSocket再細分類,在不必要的情況下應該避免使用它。
· static void setSocketFactory ( SocketImplFactory factory )產生java.io.IOException、Java.Net.SocketException、 Java.lang.SecurityException異常--為JVM指定服務器套接字產生組件。它是一個靜態的方法,在JVM的生存周期中只能調用一次。如果禁止指定新的套接字產生組件,或者已經指定了一個,就會產生異常。
· void setSoTimeout(int timeout)產生Java.Net.SocketException異常--為accept