透過程式建立了實際的概念之後,現在應該回到最開始的問題,Socket是什麼?是實現電腦通訊的一種方式,這毫無疑問.但如何能夠用最輕易理解的語言比較形象而又不偏頗的描述它的原理呢?
BrUCe Eckel 在他的《java 編程思想》一書中這樣描述套接字:
套接字是一種軟體抽象,用於表達兩台機器之間的連接「終端」。對於一個給定的連接,每台機器上都有一個套接字,您也可以想像它們之間有一條虛擬的“電纜”,“電纜”的每一端都插入到套接字中。當然,機器之間的實體硬體和電纜連接都是完全未知的。抽象的全部目的是使我們無須知道不必知道的細節.
按我的理解,抽象點來說,一個Socket就是一個電話聽筒,你有一個,和你通話的人也有一個,只不過其中有一個人的聽筒叫ServerSocket,另一個人的聽筒叫Socket.至於誰是ServerSocket,誰是Socket,這不重要,因為客戶端和伺服器端本來就是相對的,可以互相轉換的.通話的兩個人透過拿起兩個聽筒建立了一條通道,這條通道通不通就要看是不是雙方都拿起聽筒了,假如只有一方拿起聽筒,那就只能聽到一些嘟嘟的聲音,證實通道不同.這裡,拿起聽筒的過程就是Socket初始化的過程.建立了通道之後,也就是大家都拿起聽筒之後,通道兩端的人就可以開始通話了.這裡又有兩個過程,即A對B說話,B接聽,和B對A說話,A收聽,這兩個過程是透過兩條線路完成的.傳輸在這兩條線路上的,就是流.流隱藏了所有傳輸的細節,使得通信雙方都認為,他們傳過去的是聲音,而不是編碼.
前面寫的伺服器端的程式實際上是單任務版本,伺服器對客戶機的處理機制是在同一時間段內只能處理一個連接,因為handleConnection中採取的是不斷循環的阻塞方法,偵測到一個,就處理一個,然後再偵測到一個,就再處理一個,假如有多個連接同時請求,那隻能排隊等候.這樣的程式是無法在網路中應付多個連線的,因為你無法保證在同一時間內只有一個客戶提出與伺服器的連接請求,而用阻塞的方法應付多客戶連接其速度之慢是可想而知的.
這樣就催生了面向多連接的版本.顯然,透過多執行緒可以來實現我們的要求.
由於要解決的是處理客戶連接的問題,因此我們的工作只是在伺服器端的程式當中修改.其原理不難推出,就是在偵測到一個連接請求之後,馬上建立一個線程去處理它,然後繼續兼聽下一個連接請求.所以,我們只需要將原來在handleConnection中的代碼原封不動的放到線程的執行代碼中,而在handleConnection中添加上新建線程的程式碼就可以了,十分簡單.
同上一篇的風格一樣,我們來觀察各個部分的程式碼細節.
首先為這個多線程的版本創建類MultiThreadRemoteFileServer
看看這個類的定義
import java.io.*;
import java.net.*;
public class MultiThreadRemoteFileServer{
PRotected int listenPort;
public MultiThreadRemoteFileServer(int aListenPort){
}
public MultiThreadRemoteFileServer(int aListenPort){
} public void acceptConnections() {
}
public void handleConnection(Socket incomingConnection) {
}
}
幾乎和RemoteFileServer是一樣的,唯一的區別是在我們現在創建的這個類別中增加了一個構造函數,這是一個建構子為了能夠使得監聽的連接埠號碼由我們自己來定.定義如下
public MultithreadedRemoteFileServer(int aListenPort) {
listenPort = aListenPort;
}
先來看main()
public static void {
MultithreadedRemoteFileServer server = new MultithreadedRemoteFileServer(3000);
server.acceptConnections();
}
沒有區別吧,和RemoteFileServer的main()
我們主要關心的改動都在後面
現在看acceptConnection監聽程式
public void acceptConnections() {
try {
ServerSocket server = new ServerSocket(listenPort, 5);//Port,沒有時候建立伺服器Socket的時候了一個參數,這個參數是用來指定能夠同時申請連線的最大數目,預設值是50
Socket incomingConnection = null;
while (true) {
incomingConnection = server.accept();
handle(incomingConnection);
}
} catch (BindException e) {
System.out.println("Unable to bind to port " + listenPort);
} catch (IOException e) {
System.out.println("Unable to instantiate Unableet port: " + listenPort);
}
}
改動的地方就一個,多了個參數.這裡是它的工作機制。假設我們指定待發數(backlog 值)是5並且有五台客戶機請求連接到我們的伺服器。我們的伺服器將著手處理第一個連接,但處理該連接需要很長時間。由於我們的待發值是 5,所以我們一次可以放五個請求到佇列中。我們正在處理一個,所以這意味著還有其它五個正在等待。等待的和正在處理的一共有六個。當我們的伺服器仍忙於接受一號連線(記住佇列中還有2?6 號)時,假如有第七個客戶機提出連線申請,那麼,該第七個客戶機將遭到拒絕
接著看,我們的下一個改動顯然是在處理監聽到的線程的方法handleConnection中,前面已經說了,在多線程的版本中,我們檢測到一個連接請求,就馬上生成一個線程,然後就不用理它了,那麼在這裡就是新建線程的一句話.
public void handleConnection(Socket connectionToHandle) {
new Thread(new ConnectionHandler(connectionToHandle)).start();
}
我們注重到有一個新的類別我們注重到有一個新的類別Handler ,這個類別是Runnable的,即是一個接口類(這是用接口實現的一個線程,要是有不明白的話,可以去看看17號的關於線程的東西).我們用ConnectionHandler 創建一個新Thread 並啟動它。正如我們剛才所說的,原來在RemoteFileServer的handleConnection中的程式碼統統原封不動的轉移到了這個介面類別ConnectionHandler的run()方法中來了.
那麼我們來看看整個ConnectionHandler類別的定義吧。
class ConnectionHandler implements Runnable {
protected Socket socketToHandle;
public ConnectionHandler(Socket aSocketToHandle) {
socketToHandle = aSocketHandle]///////TetToHandle) {
socketToHandle = aSocketfid;//////////////通過句柄透過處理執行的執行物)。 //原來對Socket的讀/寫的程式碼都在這裡了
try {
PrintWriter streamWriter = new PrintWriter(socketToHandle.getOutputStream());
BufferedReader streamReader = new BufferedReader(new InStreamStreamReader(new InputStream);
String fileToRead = streamReader.readLine();
BufferedReader fileReader = new BufferedReader(new FileReader(fileToRead));
String line = null;
whi. .println(line);
fileReader.close();
streamWriter.close();
streamReader.close();
} catch (Exception e) {
System.out.println("Error handling a client: " + e);
}
}
}
ConnectionHandler 的run() 方法所做的事情就是RemoteFileServer 上的handleConnection() 所做的事情。先把 InputStream 和 OutputStream 分別包裝(用 Socket 的 getOutputStream() 和 getInputStream())進 BufferedReader 和 PrintWriter。然後我們用這些程式碼逐行地讀目標檔.由於InputStream中裝的是文件路徑,所以中間還需要使用FileReader流將文件路徑包裝,再經由BufferedReader包裝讀出.
我們的多線程伺服器研究完了,同樣,我們回顧一下創建和使用“多線程版”的伺服器的步驟:
1.修改acceptConnections() 以用缺省為50(或任何您想要的大於1 的指定數字)實例化ServerSocket。
2. 修改 ServerSocket 的 handleConnection() 以用 ConnectionHandler 的一個實例產生一個新的 Thread。
3.借用 RemoteFileServer 的 handleConnection() 方法的程式碼實作 ConnectionHandler 類別的run()函數。
以上就是菜鳥初學Java的備忘錄(六)的內容,更多相關內容請關注PHP中文網(m.sbmmt.com)!