- 論壇徽章:
- 0
|
這個例子將以最簡單的方式運用套接字對服務(wù)器和客戶機進行操作。服務(wù)器的全部工作就是等候建立一個連接,然后用那個連接產(chǎn)生的Socket創(chuàng)建一個InputStream以及一個OutputStream。在這之后,它從InputStream讀入的所有東西都會反饋給OutputStream,直到接收到行中止(END)為止,最后關(guān)閉連接。
客戶機連接與服務(wù)器的連接,然后創(chuàng)建一個OutputStream。文本行通過OutputStream發(fā)送?蛻魴C也會創(chuàng)建一個InputStream,用它收聽服務(wù)器說些什么(本例只不過是反饋回來的同樣的字句)。
服務(wù)器與客戶機(程序)都使用同樣的端口號,而且客戶機利用本地主機地址連接位于同一臺機器中的服務(wù)器(程序),所以不必在一個物理性的網(wǎng)絡(luò)里完成測試(在某些配置環(huán)境中,可能需要同真正的網(wǎng)絡(luò)建立連接,否則程序不能工作——盡管實際并不通過那個網(wǎng)絡(luò)通信)。
下面是服務(wù)器程序:
package c1;
import java.io.*;
import java.net.*;
public class Test {
// Choose a port outside of the range 1-1024:
public static final int PORT = 8080;
public static void main(String[] args)
throws IOException {
ServerSocket s = new ServerSocket(PORT);
System.out.println("Started: " + s);
try {
// Blocks until a connection occurs:
Socket socket = s.accept();
try {
System.out.println(
"Connection accepted: "+ socket);
BufferedReader in =
new BufferedReader(
new InputStreamReader(
socket.getInputStream()));
// Output is automatically flushed
// by PrintWriter:
PrintWriter out =
new PrintWriter(
new BufferedWriter(
new OutputStreamWriter(
socket.getOutputStream())),true);
while (true) {
String str = in.readLine();
if (str.equals("END")) break;
System.out.println("Echoing: " + str);
out.println("send to client"+str);
}
// Always close the two sockets...
} finally {
System.out.println("closing...");
socket.close();
}
} finally {
s.close();
}
}
} ///:~
可以看到,ServerSocket需要的只是一個端口編號,不需要IP地址(因為它就在這臺機器上運行)。調(diào)用accept()時,方法會暫時陷入停頓狀態(tài)(堵塞),直到某個客戶嘗試同它建立連接。換言之,盡管它在那里等候連接,但其他進程仍能正常運行(參考第14章)。建好一個連接以后,accept()就會返回一個Socket對象,它是那個連接的代表。
清除套接字的責(zé)任在這里得到了很藝術(shù)的處理。假如ServerSocket構(gòu)建器失敗,則程序簡單地退出(注意必須保證ServerSocket的構(gòu)建器在失敗之后不會留下任何打開的網(wǎng)絡(luò)套接字)。針對這種情況,main()會“擲”出一個IOException違例,所以不必使用一個try塊。若ServerSocket構(gòu)建器成功執(zhí)行,則其他所有方法調(diào)用都必須到一個try-finally代碼塊里尋求保護,以確保無論塊以什么方式留下,ServerSocket都能正確地關(guān)閉。
同樣的道理也適用于由accept()返回的Socket。若accept()失敗,那么我們必須保證Socket不再存在或者含有任何資源,以便不必清除它們。但假若執(zhí)行成功,則后續(xù)的語句必須進入一個try-finally塊內(nèi),以保障在它們失敗的情況下,Socket仍能得到正確的清除。由于套接字使用了重要的非內(nèi)存資源,所以在這里必須特別謹(jǐn)慎,必須自己動手將它們清除(Java中沒有提供“破壞器”來幫助我們做這件事情)。
無論ServerSocket還是由accept()產(chǎn)生的Socket都打印到System.out里。這意味著它們的toString方法會得到自動調(diào)用。這樣便產(chǎn)生了:
ServerSocket[addr=0.0.0.0,PORT=0,localport=8080]
Socket[addr=127.0.0.1,PORT=1077,localport=8080]
大家不久就會看到它們?nèi)绾闻c客戶程序做的事情配合。
程序的下一部分看來似乎僅僅是打開文件,以便讀取和寫入,只是InputStream和OutputStream是從Socket對象創(chuàng)建的。利用兩個“轉(zhuǎn)換器”類InputStreamReader和OutputStreamWriter,InputStream和OutputStream對象已經(jīng)分別轉(zhuǎn)換成為Java 1.1的Reader和Writer對象。也可以直接使用Java1.0的InputStream和OutputStream類,但對輸出來說,使用Writer方式具有明顯的優(yōu)勢。這一優(yōu)勢是通過PrintWriter表現(xiàn)出來的,它有一個過載的構(gòu)建器,能獲取第二個參數(shù)——一個布爾值標(biāo)志,指向是否在每一次println()結(jié)束的時候自動刷新輸出(但不適用于print()語句)。每次寫入了輸出內(nèi)容后(寫進out),它的緩沖區(qū)必須刷新,使信息能正式通過網(wǎng)絡(luò)傳遞出去。對目前這個例子來說,刷新顯得尤為重要,因為客戶和服務(wù)器在采取下一步操作之前都要等待一行文本內(nèi)容的到達(dá)。若刷新沒有發(fā)生,那么信息不會進入網(wǎng)絡(luò),除非緩沖區(qū)滿(溢出),這會為本例帶來許多問題。
編寫網(wǎng)絡(luò)應(yīng)用程序時,需要特別注意自動刷新機制的使用。每次刷新緩沖區(qū)時,必須創(chuàng)建和發(fā)出一個數(shù)據(jù)包(數(shù)據(jù)封)。就目前的情況來說,這正是我們所希望的,因為假如包內(nèi)包含了還沒有發(fā)出的文本行,服務(wù)器和客戶機之間的相互“握手”就會停止。換句話說,一行的末尾就是一條消息的末尾。但在其他許多情況下,消息并不是用行分隔的,所以不如不用自動刷新機制,而用內(nèi)建的緩沖區(qū)判決機制來決定何時發(fā)送一個數(shù)據(jù)包。這樣一來,我們可以發(fā)出較大的數(shù)據(jù)包,而且處理進程也能加快。
注意和我們打開的幾乎所有數(shù)據(jù)流一樣,它們都要進行緩沖處理。本章末尾有一個練習(xí),清楚展現(xiàn)了假如我們不對數(shù)據(jù)流進行緩沖,那么會得到什么樣的后果(速度會變慢)。
無限while循環(huán)從BufferedReader in內(nèi)讀取文本行,并將信息寫入System.out,然后寫入PrintWriter.out。注意這可以是任何數(shù)據(jù)流,它們只是在表面上同網(wǎng)絡(luò)連接。
客戶程序發(fā)出包含了"END"的行后,程序會中止循環(huán),并關(guān)閉Socket。
下面是客戶程序的源碼:
package c1;
import java.net.*;
import java.io.*;
public class Test1 {
public static void main(String[] args)
throws IOException {
// Passing null to getByName() produces the
// special "Local Loopback" IP address, for
// testing on one machine w/o a network:
InetAddress addr =
InetAddress.getByName(null);
// Alternatively, you can use
// the address or name:
// InetAddress addr =
// InetAddress.getByName("127.0.0.1");
// InetAddress addr =
// InetAddress.getByName("localhost");
System.out.println("addr = " + addr);
Socket socket =
new Socket(addr, Test.PORT);
// Guard everything in a try-finally to make
// sure that the socket is closed:
try {
System.out.println("socket = " + socket);
BufferedReader in =
new BufferedReader(
new InputStreamReader(
socket.getInputStream()));
// Output is automatically flushed
// by PrintWriter:
PrintWriter out =
new PrintWriter(
new BufferedWriter(
new OutputStreamWriter(
socket.getOutputStream())),true);
for(int i = 0; i
在main()中,大家可看到獲得本地主機IP地址的InetAddress的三種途徑:使用null,使用localhost,或者直接使用保留地址127.0.0.1。當(dāng)然,如果想通過網(wǎng)絡(luò)同一臺遠(yuǎn)程主機連接,也可以換用那臺機器的IP地址。打印出InetAddress addr后(通過對toString()方法的自動調(diào)用),結(jié)果如下:
localhost/127.0.0.1
通過向getByName()傳遞一個null,它會默認(rèn)尋找localhost,并生成特殊的保留地址127.0.0.1。注意在名為socket的套接字創(chuàng)建時,同時使用了InetAddress以及端口號。打印這樣的某個Socket對象時,為了真正理解它的含義,請記住一次獨一無二的因特網(wǎng)連接是用下述四種數(shù)據(jù)標(biāo)識的:clientHost(客戶主機)、clientPortNumber(客戶端口號)、serverHost(服務(wù)主機)以及serverPortNumber(服務(wù)端口號)。服務(wù)程序啟動后,會在本地主機(127.0.0.1)上建立為它分配的端口(8080)。一旦客戶程序發(fā)出請求,機器上下一個可用的端口就會分配給它(這種情況下是1077),這一行動也在與服務(wù)程序相同的機器(127.0.0.1)上進行,F(xiàn)在,為了使數(shù)據(jù)能在客戶及服務(wù)程序之間來回傳送,每一端都需要知道把數(shù)據(jù)發(fā)到哪里。所以在同一個“已知”服務(wù)程序連接的時候,客戶會發(fā)出一個“返回地址”,使服務(wù)器程序知道將自己的數(shù)據(jù)發(fā)到哪兒。我們在服務(wù)器端的示范輸出中可以體會到這一情況:
Socket[addr=127.0.0.1,port=1077,localport=8080]
這意味著服務(wù)器剛才已接受了來自127.0.0.1這臺機器的端口1077的連接,同時監(jiān)聽自己的本地端口(8080)。而在客戶端:
Socket[addr=localhost/127.0.0.1,PORT=8080,localport=1077]
這意味著客戶已用自己的本地端口1077與127.0.0.1機器上的端口8080建立了 連接。
大家會注意到每次重新啟動客戶程序的時候,本地端口的編號都會增加。這個編號從1025(剛好在系統(tǒng)保留的1-1024之外)開始,并會一直增加下去,除非我們重啟機器。若重新啟動機器,端口號仍然會從1025開始增值(在Unix機器中,一旦超過保留的套按字范圍,數(shù)字就會再次從最小的可用數(shù)字開始)。
創(chuàng)建好Socket對象后,將其轉(zhuǎn)換成BufferedReader和PrintWriter的過程便與在服務(wù)器中相同(同樣地,兩種情況下都要從一個Socket開始)。在這里,客戶通過發(fā)出字串"howdy",并在后面跟隨一個數(shù)字,從而初始化通信。注意緩沖區(qū)必須再次刷新(這是自動發(fā)生的,通過傳遞給PrintWriter構(gòu)建器的第二個參數(shù))。若緩沖區(qū)沒有刷新,那么整個會話(通信)都會被掛起,因為用于初始化的“howdy”永遠(yuǎn)不會發(fā)送出去(緩沖區(qū)不夠滿,不足以造成發(fā)送動作的自動進行)。從服務(wù)器返回的每一行都會寫入System.out,以驗證一切都在正常運轉(zhuǎn)。為中止會話,需要發(fā)出一個"END"。若客戶程序簡單地掛起,那么服務(wù)器會“擲”出一個違例。
大家在這里可以看到我們采用了同樣的措施來確保由Socket代表的網(wǎng)絡(luò)資源得到正確的清除,這是用一個try-finally塊實現(xiàn)的。
運行結(jié)果:
服務(wù)器:
Started: ServerSocket[addr=0.0.0.0/0.0.0.0,port=0,localport=8080]
Connection accepted: Socket[addr=/127.0.0.1,port=2254,localport=8080]
Echoing: sent to server howdy 0
Echoing: sent to server howdy 1
Echoing: sent to server howdy 2
Echoing: sent to server howdy 3
Echoing: sent to server howdy 4
Echoing: sent to server howdy 5
Echoing: sent to server howdy 6
Echoing: sent to server howdy 7
Echoing: sent to server howdy 8
Echoing: sent to server howdy 9
closing...
運行結(jié)果:
客戶端:
D:\workspace\test>java c1.Test1
addr = localhost/127.0.0.1
socket = Socket[addr=localhost/127.0.0.1,port=8080,localport=2254]
send to clientsent to server howdy 0
send to clientsent to server howdy 1
send to clientsent to server howdy 2
send to clientsent to server howdy 3
send to clientsent to server howdy 4
send to clientsent to server howdy 5
send to clientsent to server howdy 6
send to clientsent to server howdy 7
send to clientsent to server howdy 8
send to clientsent to server howdy 9
closing...
本文來自ChinaUnix博客,如果查看原文請點:http://blog.chinaunix.net/u/19919/showart_2154295.html |
|