原貼地址: http://jeffchen.cnblogs.com/archive/2005/12/29/307269.aspx
前一段時間看了IBM developerWork上的一篇關于socket的tutorial,由淺入深的實現了server-client程序,現在這里整理一下。
??? 什么是socket?
??? think in java 里給的回答:套接字是一種軟件抽象,用于表達兩臺機器之間的連接“終端”。對于一個給定的連接,每臺機器上都有一個套接字,您也可以想象它們之間有一條虛擬的“電纜”,“電纜”的每一端都插入到套接字中。當然,機器之間的物理硬件和電纜連接都是完全未知的。抽象的全部目的是使我們無須知道不必知道的細節。簡言之,一臺機器上的套接字與另一臺機器上的套接字交談就創建一條通信通道.
??? Socket 和 ServerSocket例子
???
一.客戶端步驟:
??? 1)用您想連接的機器的 IP 地址和端口實例化 Socket(如有問題則拋出 Exception)。
??? 2)獲取 Socket 上的流。
??? 3)把流包裝進 BufferedReader/PrintWriter 的實例,如果這樣做能使事情更簡單的話。
??? 4)對 Socket 進行讀寫。
??? 5)關閉打開的流。
???
??? RemoteFileClient類
??? import java.io.*;
??? import java.net.*;
???? public class RemoteFileClient {
??? protected String hostIp;
??? protected int hostPort;
??? protected BufferedReader socketReader;
??? protected PrintWriter socketWriter;
??? public RemoteFileClient(String aHostIp, int aHostPort) {
??????? hostIp = aHostIp;
??????? hostPort = aHostPort;
??? }
??? public static void main(String[] args) {
??? }
??? public void setUpConnection() {????????????? //連接到遠程服務器
??? }
??? public String getFile(String fileNameToGet) {//向遠程服務器請求 fileNameToGet 的內容,在服務器傳回其內容時接收該內容
??? }
??? public void tearDownConnection() {?????????? //從遠程服務器上斷開
??? }
}
?????? main()函數:
?? public static void main(String[] args) {
??? RemoteFileClient remoteFileClient = new RemoteFileClient("127.0.0.1", 3000);
??? remoteFileClient.setUpConnection();
??? String fileContents =
??????? remoteFileClient.getFile("C:\\WINNT\\Temp\\RemoteFile.txt");
??? remoteFileClient.tearDownConnection();
??? System.out.println(fileContents);
}
???? setUpConnection()函數實現:
?? public void setUpConnection() {
??? try {
??????? Socket client = new Socket(hostIp, hostPort);
??????? socketReader = new BufferedReader(
??????? ???? new InputStreamReader(client.getInputStream()));//使我們能夠讀取流的行
??????? socketWriter = new PrintWriter(client.getOutputStream());//使我們能夠發送文件請求到服務器:
??? } catch (UnknownHostException e) {
??????? System.out.println("Error setting up socket connection: unknown host at " + hostIp + ":" + hostPort);
??? } catch (IOException e) {
??????? System.out.println("Error setting up socket connection: " + e);
??? }
}
???? getFile() 的實現:
??? public String getFile(String fileNameToGet) {
??? StringBuffer fileLines = new StringBuffer();
??? try {
??????? socketWriter.println(fileNameToGet);? //把請求發送到主機,PrintWriter 是我們在創建連接期間建立的???????????????
??????? socketWriter.flush();
??????? String line = null;
??????? while ((line = socketReader.readLine()) != null)
??????????? fileLines.append(line + "\n");
??? } catch (IOException e) {
??????? System.out.println("Error reading from file: " + fileNameToGet);
??? }
??? return fileLines.toString();
}
??? tearDownConnection()方法的實現:
public void tearDownConnection() {
??? try {
??????? socketWriter.close();
??????? socketReader.close();
??? } catch (IOException e) {
??????? System.out.println("Error tearing down socket connection: " + e);
??? }
}
??? 在上次做的聊天服務器程序中,因為"清除"的方法不對,導致占用大量內存:(
二.服務器步驟:
??? 1)用一個您想讓它偵聽傳入客戶機連接的端口來實例化一個 ServerSocket(如有問題則拋出 Exception)。
??? 2)調用 ServerSocket 的 accept() 以在等待連接期間造成阻塞。
??? 3)獲取位于該底層 Socket 的流以進行讀寫操作。
??? 4)按使事情簡單化的原則包裝流。
??? 5)對 Socket 進行讀寫。
??? 6)關閉打開的流(并請記住,永遠不要在關閉 Writer 之前關閉 Reader)。
???
??? RemoteFileServer 類
??? import java.io.*;
??? import java.net.*;
??? public class RemoteFileServer {
??? protected int listenPort = 3000;
??? public static void main(String[] args) {
??? }
??? public void acceptConnections() {???????????????????????? //允許客戶機連接到服務器
??? }
??? public void handleConnection(Socket incomingConnection) { //與客戶機 Socket 交互以將您所請求的文件的內容發送到客戶機
??? }
}
???
???? main() 方法的實現:
???? public static void main(String[] args) {
??? RemoteFileServer server = new RemoteFileServer();
??? server.acceptConnections();
}
??? acceptConnections()方法的實現:
??? public void acceptConnections() {
??? try {
??????? ServerSocket server = new ServerSocket(listenPort);
??????? Socket incomingConnection = null;
??????? while (true) {
??????????? incomingConnection = server.accept();
??????????? handleConnection(incomingConnection);
??????? }
??? } catch (BindException e) {
??????? System.out.println("Unable to bind to port " + listenPort);
??? } catch (IOException e) {
??????? System.out.println("Unable to instantiate a ServerSocket on port: " + listenPort);
??? }
}
??? 我們通過調用該 ServerSocket 的 accept() 來告訴它開始偵聽。accept() 方法將造成阻塞直到來了一個連接請求。此時,accept()???? 返回一個新的 Socket,這個 Socket 綁定到服務器上一個隨機指定的端口,返回的 Socket 被傳遞給 handleConnection()。
??? handleConnection() 方法的實現:
??? public void handleConnection(Socket incomingConnection) {
??? try {
??????? OutputStream outputToSocket = incomingConnection.getOutputStream();
??????? InputStream inputFromSocket = incomingConnection.getInputStream();
??????? BufferedReader streamReader =
??????????? new BufferedReader(new InputStreamReader(inputFromSocket));
??????? FileReader fileReader = new FileReader(new File(streamReader.readLine()));//獲取一條有效的文件路徑
??????? BufferedReader bufferedFileReader = new BufferedReader(fileReader);
??????? PrintWriter streamWriter =
??????????? new PrintWriter(incomingConnection.getOutputStream());
??????? String line = null;
??????? while ((line = bufferedFileReader.readLine()) != null) {
??????????? streamWriter.println(line);
??????? }
??????? fileReader.close();
??????? streamWriter.close();
??????? streamReader.close();
??? } catch (Exception e) {
??????? System.out.println("Error handling a client: " + e);
??? }
}
???? 如果您在關閉 streamWriter 之前關閉 streamReader,則您可以往 Socket 寫任何東西,但卻沒有任何數據能通過通道(通道被關?????? 閉了)。
???
三.使服務器支持多線程
?? 一般步驟:
?? 1)修改 acceptConnections() 以用缺省為 50(或任何您想要的大于 1 的指定數字)實例化 ServerSocket。
?? 2)修改 ServerSocket 的 handleConnection() 以用 ConnectionHandler 的一個實例生成一個新的 Thread。
?? 3)借用 RemoteFileServer 的 handleConnection() 方法的代碼實現 ConnectionHandler 類
??? public void acceptConnections() {
??????? try {
??????? ServerSocket server = new ServerSocket(listenPort, 5);
??????? Socket incomingConnection = null;
??????? while (true) {
??????????? incomingConnection = server.accept();
??????????? handleConnection(incomingConnection);
??????? }
??? } catch (BindException e) {
??? System.out.println("Unable to bind to port " + listenPort);
??? } catch (IOException e) {
??? System.out.println("Unable to instantiate a ServerSocket on port: " + listenPort);
??? }
}
??? 假設我們指定待發數(backlog 值)是 5 并且有五臺客戶機請求連接到我們的服務器。我們的服務器將著手處理第一個連接,但處理該連接需要很長時間。由于我們的待發值是 5,所以我們一次可以放五個請求到隊列中。我們正在處理一個,所以這意味著還有其它五個正在等待。等待的和正在處理的一共有六個。當我們的服務器仍忙于接受一號連接(記住隊列中還有 2—6 號)時,如果有第七個客戶機提出連接申請,那么,該第七個客戶機將遭到拒絕。
???
?? public void handleConnection(Socket connectionToHandle) {
???? new Thread(new ConnectionHandler(connectionToHandle)).start();
}
??? 我們對 RemoteFileServer 所做的大改動就體現在這個方法上。我們仍然在服務器接受一個連接之后調用 handleConnection(),但現在我們把該 Socket 傳遞給 ConnectionHandler 的一個實例,它是 Runnable 的。我們用 ConnectionHandler 創建一個新 Thread 并啟動它。ConnectionHandler 的 run() 方法包含Socket 讀/寫和讀 File 的代碼,這些代碼原來在 RemoteFileServer 的 handleConnection() 中。
???
??? ConnectionHandler 類
??? import java.io.*;
??? import java.net.*;
public class ConnectionHandler implements Runnable{
?? Socket socketToHandle;
?? public ConnectionHandler(Socket aSocketToHandle) {
????? socketToHandle = aSocketToHandle;
?? }
?? public void run() {
?? }
}
??? run() 方法的實現,同RemoteFileServer 的 handleConnection():
??? public void run() {
??????? try {
??????????? PrintWriter streamWriter = new PrintWriter(socketToHandle.getOutputStream());
??????????? BufferedReader streamReader =
??????????????? new BufferedReader(new InputStreamReader(socketToHandle.getInputStream()));
??????????? String fileToRead = streamReader.readLine();
??????????? BufferedReader fileReader = new BufferedReader(new FileReader(fileToRead));
??????????? String line = null;
??????????? while ((line = fileReader.readLine()) != null)
??????????????? streamWriter.println(line);
??????????? fileReader.close();
??????????? streamWriter.close();
??????????? streamReader.close();
??????? } catch (Exception e) {
??????????? System.out.println("Error handling a client: " + e);
??????? }
??? }
四.更高效地管理服務器端
??? 我們可以維護一個進入的連接池,一定數量的 ConnectionHandler 將為它提供服務。這種設計能帶來以下好處:
??? 1)它限定了允許同時連接的數目。
??? 2)我們只需啟動 ConnectionHandler Thread 一次。
??? 步驟:
??? 1)創建一個新種類的連接處理程序(我們稱之為 PooledConnectionHandler)來處理池中的連接。
??? 2)修改服務器以創建和使用一組 PooledConnectionHandler
??? 在服務器端,我們在服務器啟動時創建一定數量的 ConnectionHandler,我們把進入的連接放入“池”中并讓 ConnectionHandler 打理剩下的事情。這種設計中有很多我們不打算討論的可能存在的技巧。例如,我們可以通過限定允許在“池”中建立的連接的數目來拒絕客戶機。
??? PooledRemoteFileServer類
??? import java.io.*;
??? import java.net.*;
??? import java.util.*;
??? public class PooledRemoteFileServer {
??? protected int maxConnections;???????? //我們的服務器能同時處理的活動客戶機連接的最大數目
??? protected int listenPort;???????????? //進入的連接的偵聽端口
??? protected ServerSocket serverSocket;? //接受客戶機連接請求的 ServerSocket
??? public PooledRemoteFileServer(int aListenPort, int maxConnections) {
??????? listenPort = aListenPort;
??????? this.maxConnections = maxConnections;
??? }
??? public static void main(String[] args) {
??? }
??? public void setUpHandlers() {??????? //創建數目為 maxConnections 的大量 PooledConnectionHandler
??? }
??? public void acceptConnections() {
??? }
??? protected void handleConnection(Socket incomingConnection) {
??? }
}
?? main() 方法的實現:
?? public static void main(String[] args) {
??? PooledRemoteFileServer server = new PooledRemoteFileServer(3000, 3);
??? server.setUpHandlers();
??? server.acceptConnections();
}
?? 我們實例化一個新的 PooledRemoteFileServer,它將通過調用 setUpHandlers() 來建立三個 PooledConnectionHandler。一旦服務器就緒,我們就告訴它 acceptConnections()。
??
?? public void setUpHandlers() {
??? for (int i = 0; i < maxConnections; i++) {
??????? PooledConnectionHandler currentHandler = new PooledConnectionHandler();
??????? new Thread(currentHandler, "Handler " + i).start();
??? }
}
??? setUpHandlers() 方法創建 maxConnections(例如 3)個 PooledConnectionHandler 并在新 Thread 中激活它們。用實現了 Runnable 的對象來創建 Thread 使我們可以在 Thread 調用 start() 并且可以期望在 Runnable 上調用了 run()。換句話說,我們的 PooledConnectionHandler 將等著處理進入的連接,每個都在它自己的 Thread 中進行。我們在示例中只創建三個 Thread,而且一旦服務器運行,這就不能被改變。
???
??? 我們實現需作改動的 handleConnections() 方法,它將委派 PooledConnectionHandler 處理連接:
??? protected void handleConnection(Socket connectionToHandle) {
PooledConnectionHandler.processRequest(connectionToHandle);
}
???
??? PooledConnectionHandler 類:
???
??? import java.io.*;
??? import java.util.*;
??? public class PooledConnectionHandler implements Runnable {
??? protected Socket connection;????????????????????? //當前正在處理的 Socket
??? protected static List pool = new LinkedList();??? //名為 pool 的靜態 LinkedList 保存需被處理的連接
??? public PooledConnectionHandler() {
??? }
??? public void handleConnection() {
??? }
??? public static void processRequest(Socket requestToHandle) {
??? }
??? public void run() {
??? }
}
??
?? processRequest() 方法,它將把傳入請求添加到池中,并告訴其它正在等待的對象該池已經有一些內容:
?? public static void processRequest(Socket requestToHandle) {
??? synchronized (pool) {
??????? pool.add(pool.size(), requestToHandle);
??????? pool.notifyAll();
??? }
}
?? 實現 PooledConnectionHandler 上需作改動的 run()方法,它將在連接池上等待,并且池中一有連接就處理它:
?? public void run() {
??????? while (true) {
???????????? synchronized (pool) {
????????????????? while (pool.isEmpty()) {
?????????????????????? try {
??????????????????????????? pool.wait();
?????????????????????? } catch (InterruptedException e) {
??????????????????????????? return;
?????????????????????? }
?????????????????? }
?????????????????? connection = (Socket) pool.remove(0);
???????????? }
???????????? handleConnection();
??????? }
}
???
??? 實現需做改動的 handleConnection() 方法,該方法將攫取連接的流,使用它們,并在任務完成之后清除它們:
??? public void handleConnection() {
??? try {
??????? PrintWriter streamWriter = new PrintWriter(connection.getOutputStream());
??????? BufferedReader streamReader =
??????????? new BufferedReader(new InputStreamReader(connection.getInputStream()));
??????? String fileToRead = streamReader.readLine();
??????? BufferedReader fileReader = new BufferedReader(new FileReader(fileToRead));
??????? String line = null;
??????? while ((line = fileReader.readLine()) != null)
??????????? streamWriter.println(line);
??????? fileReader.close();
??????? streamWriter.close();
??????? streamReader.close();
??? } catch (FileNotFoundException e) {
??????? System.out.println("Could not find requested file on the server.");
??? } catch (IOException e) {
??????? System.out.println("Error handling a client: " + e);
??? }
}
??? 總結:在現實生活中使用套接字只是這樣一件事,即通過貫徹優秀的 OO 設計原則來保護應用程序中各層間的封裝。
posted on 2007-01-23 09:55
OMG 閱讀(317)
評論(0) 編輯 收藏 所屬分類:
Soket