原貼地址: http://jeffchen.cnblogs.com/archive/2005/12/29/307269.aspx
前一段時(shí)間看了IBM developerWork上的一篇關(guān)于socket的tutorial,由淺入深的實(shí)現(xiàn)了server-client程序,現(xiàn)在這里整理一下。
??? 什么是socket?
??? think in java 里給的回答:套接字是一種軟件抽象,用于表達(dá)兩臺(tái)機(jī)器之間的連接“終端”。對(duì)于一個(gè)給定的連接,每臺(tái)機(jī)器上都有一個(gè)套接字,您也可以想象它們之間有一條虛擬的“電纜”,“電纜”的每一端都插入到套接字中。當(dāng)然,機(jī)器之間的物理硬件和電纜連接都是完全未知的。抽象的全部目的是使我們無須知道不必知道的細(xì)節(jié)。簡(jiǎn)言之,一臺(tái)機(jī)器上的套接字與另一臺(tái)機(jī)器上的套接字交談就創(chuàng)建一條通信通道.
??? Socket 和 ServerSocket例子
???
一.客戶端步驟:
??? 1)用您想連接的機(jī)器的 IP 地址和端口實(shí)例化 Socket(如有問題則拋出 Exception)。
??? 2)獲取 Socket 上的流。
??? 3)把流包裝進(jìn) BufferedReader/PrintWriter 的實(shí)例,如果這樣做能使事情更簡(jiǎn)單的話。
??? 4)對(duì) Socket 進(jìn)行讀寫。
??? 5)關(guān)閉打開的流。
???
??? 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() {????????????? //連接到遠(yuǎn)程服務(wù)器
??? }
??? public String getFile(String fileNameToGet) {//向遠(yuǎn)程服務(wù)器請(qǐng)求 fileNameToGet 的內(nèi)容,在服務(wù)器傳回其內(nèi)容時(shí)接收該內(nèi)容
??? }
??? public void tearDownConnection() {?????????? //從遠(yuǎn)程服務(wù)器上斷開
??? }
}
?????? main()函數(shù):
?? 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()函數(shù)實(shí)現(xiàn):
?? public void setUpConnection() {
??? try {
??????? Socket client = new Socket(hostIp, hostPort);
??????? socketReader = new BufferedReader(
??????? ???? new InputStreamReader(client.getInputStream()));//使我們能夠讀取流的行
??????? socketWriter = new PrintWriter(client.getOutputStream());//使我們能夠發(fā)送文件請(qǐng)求到服務(wù)器:
??? } 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() 的實(shí)現(xiàn):
??? public String getFile(String fileNameToGet) {
??? StringBuffer fileLines = new StringBuffer();
??? try {
??????? socketWriter.println(fileNameToGet);? //把請(qǐng)求發(fā)送到主機(jī),PrintWriter 是我們?cè)趧?chuàng)建連接期間建立的???????????????
??????? 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()方法的實(shí)現(xiàn):
public void tearDownConnection() {
??? try {
??????? socketWriter.close();
??????? socketReader.close();
??? } catch (IOException e) {
??????? System.out.println("Error tearing down socket connection: " + e);
??? }
}
??? 在上次做的聊天服務(wù)器程序中,因?yàn)?清除"的方法不對(duì),導(dǎo)致占用大量?jī)?nèi)存:(
二.服務(wù)器步驟:
??? 1)用一個(gè)您想讓它偵聽傳入客戶機(jī)連接的端口來實(shí)例化一個(gè) ServerSocket(如有問題則拋出 Exception)。
??? 2)調(diào)用 ServerSocket 的 accept() 以在等待連接期間造成阻塞。
??? 3)獲取位于該底層 Socket 的流以進(jìn)行讀寫操作。
??? 4)按使事情簡(jiǎn)單化的原則包裝流。
??? 5)對(duì) Socket 進(jìn)行讀寫。
??? 6)關(guān)閉打開的流(并請(qǐng)記住,永遠(yuǎn)不要在關(guān)閉 Writer 之前關(guān)閉 Reader)。
???
??? RemoteFileServer 類
??? import java.io.*;
??? import java.net.*;
??? public class RemoteFileServer {
??? protected int listenPort = 3000;
??? public static void main(String[] args) {
??? }
??? public void acceptConnections() {???????????????????????? //允許客戶機(jī)連接到服務(wù)器
??? }
??? public void handleConnection(Socket incomingConnection) { //與客戶機(jī) Socket 交互以將您所請(qǐng)求的文件的內(nèi)容發(fā)送到客戶機(jī)
??? }
}
???
???? main() 方法的實(shí)現(xiàn):
???? public static void main(String[] args) {
??? RemoteFileServer server = new RemoteFileServer();
??? server.acceptConnections();
}
??? acceptConnections()方法的實(shí)現(xiàn):
??? 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);
??? }
}
??? 我們通過調(diào)用該 ServerSocket 的 accept() 來告訴它開始偵聽。accept() 方法將造成阻塞直到來了一個(gè)連接請(qǐng)求。此時(shí),accept()???? 返回一個(gè)新的 Socket,這個(gè) Socket 綁定到服務(wù)器上一個(gè)隨機(jī)指定的端口,返回的 Socket 被傳遞給 handleConnection()。
??? handleConnection() 方法的實(shí)現(xiàn):
??? 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);
??? }
}
???? 如果您在關(guān)閉 streamWriter 之前關(guān)閉 streamReader,則您可以往 Socket 寫任何東西,但卻沒有任何數(shù)據(jù)能通過通道(通道被關(guān)?????? 閉了)。
???
三.使服務(wù)器支持多線程
?? 一般步驟:
?? 1)修改 acceptConnections() 以用缺省為 50(或任何您想要的大于 1 的指定數(shù)字)實(shí)例化 ServerSocket。
?? 2)修改 ServerSocket 的 handleConnection() 以用 ConnectionHandler 的一個(gè)實(shí)例生成一個(gè)新的 Thread。
?? 3)借用 RemoteFileServer 的 handleConnection() 方法的代碼實(shí)現(xiàn) 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);
??? }
}
??? 假設(shè)我們指定待發(fā)數(shù)(backlog 值)是 5 并且有五臺(tái)客戶機(jī)請(qǐng)求連接到我們的服務(wù)器。我們的服務(wù)器將著手處理第一個(gè)連接,但處理該連接需要很長(zhǎng)時(shí)間。由于我們的待發(fā)值是 5,所以我們一次可以放五個(gè)請(qǐng)求到隊(duì)列中。我們正在處理一個(gè),所以這意味著還有其它五個(gè)正在等待。等待的和正在處理的一共有六個(gè)。當(dāng)我們的服務(wù)器仍忙于接受一號(hào)連接(記住隊(duì)列中還有 2—6 號(hào))時(shí),如果有第七個(gè)客戶機(jī)提出連接申請(qǐng),那么,該第七個(gè)客戶機(jī)將遭到拒絕。
???
?? public void handleConnection(Socket connectionToHandle) {
???? new Thread(new ConnectionHandler(connectionToHandle)).start();
}
??? 我們對(duì) RemoteFileServer 所做的大改動(dòng)就體現(xiàn)在這個(gè)方法上。我們?nèi)匀辉诜?wù)器接受一個(gè)連接之后調(diào)用 handleConnection(),但現(xiàn)在我們把該 Socket 傳遞給 ConnectionHandler 的一個(gè)實(shí)例,它是 Runnable 的。我們用 ConnectionHandler 創(chuàng)建一個(gè)新 Thread 并啟動(dòng)它。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() 方法的實(shí)現(xiàn),同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);
??????? }
??? }
四.更高效地管理服務(wù)器端
??? 我們可以維護(hù)一個(gè)進(jìn)入的連接池,一定數(shù)量的 ConnectionHandler 將為它提供服務(wù)。這種設(shè)計(jì)能帶來以下好處:
??? 1)它限定了允許同時(shí)連接的數(shù)目。
??? 2)我們只需啟動(dòng) ConnectionHandler Thread 一次。
??? 步驟:
??? 1)創(chuàng)建一個(gè)新種類的連接處理程序(我們稱之為 PooledConnectionHandler)來處理池中的連接。
??? 2)修改服務(wù)器以創(chuàng)建和使用一組 PooledConnectionHandler
??? 在服務(wù)器端,我們?cè)诜?wù)器啟動(dòng)時(shí)創(chuàng)建一定數(shù)量的 ConnectionHandler,我們把進(jìn)入的連接放入“池”中并讓 ConnectionHandler 打理剩下的事情。這種設(shè)計(jì)中有很多我們不打算討論的可能存在的技巧。例如,我們可以通過限定允許在“池”中建立的連接的數(shù)目來拒絕客戶機(jī)。
??? PooledRemoteFileServer類
??? import java.io.*;
??? import java.net.*;
??? import java.util.*;
??? public class PooledRemoteFileServer {
??? protected int maxConnections;???????? //我們的服務(wù)器能同時(shí)處理的活動(dòng)客戶機(jī)連接的最大數(shù)目
??? protected int listenPort;???????????? //進(jìn)入的連接的偵聽端口
??? protected ServerSocket serverSocket;? //接受客戶機(jī)連接請(qǐng)求的 ServerSocket
??? public PooledRemoteFileServer(int aListenPort, int maxConnections) {
??????? listenPort = aListenPort;
??????? this.maxConnections = maxConnections;
??? }
??? public static void main(String[] args) {
??? }
??? public void setUpHandlers() {??????? //創(chuàng)建數(shù)目為 maxConnections 的大量 PooledConnectionHandler
??? }
??? public void acceptConnections() {
??? }
??? protected void handleConnection(Socket incomingConnection) {
??? }
}
?? main() 方法的實(shí)現(xiàn):
?? public static void main(String[] args) {
??? PooledRemoteFileServer server = new PooledRemoteFileServer(3000, 3);
??? server.setUpHandlers();
??? server.acceptConnections();
}
?? 我們實(shí)例化一個(gè)新的 PooledRemoteFileServer,它將通過調(diào)用 setUpHandlers() 來建立三個(gè) PooledConnectionHandler。一旦服務(wù)器就緒,我們就告訴它 acceptConnections()。
??
?? public void setUpHandlers() {
??? for (int i = 0; i < maxConnections; i++) {
??????? PooledConnectionHandler currentHandler = new PooledConnectionHandler();
??????? new Thread(currentHandler, "Handler " + i).start();
??? }
}
??? setUpHandlers() 方法創(chuàng)建 maxConnections(例如 3)個(gè) PooledConnectionHandler 并在新 Thread 中激活它們。用實(shí)現(xiàn)了 Runnable 的對(duì)象來創(chuàng)建 Thread 使我們可以在 Thread 調(diào)用 start() 并且可以期望在 Runnable 上調(diào)用了 run()。換句話說,我們的 PooledConnectionHandler 將等著處理進(jìn)入的連接,每個(gè)都在它自己的 Thread 中進(jìn)行。我們?cè)谑纠兄粍?chuàng)建三個(gè) Thread,而且一旦服務(wù)器運(yùn)行,這就不能被改變。
???
??? 我們實(shí)現(xiàn)需作改動(dòng)的 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;????????????????????? //當(dāng)前正在處理的 Socket
??? protected static List pool = new LinkedList();??? //名為 pool 的靜態(tài) LinkedList 保存需被處理的連接
??? public PooledConnectionHandler() {
??? }
??? public void handleConnection() {
??? }
??? public static void processRequest(Socket requestToHandle) {
??? }
??? public void run() {
??? }
}
??
?? processRequest() 方法,它將把傳入請(qǐng)求添加到池中,并告訴其它正在等待的對(duì)象該池已經(jīng)有一些內(nèi)容:
?? public static void processRequest(Socket requestToHandle) {
??? synchronized (pool) {
??????? pool.add(pool.size(), requestToHandle);
??????? pool.notifyAll();
??? }
}
?? 實(shí)現(xiàn) PooledConnectionHandler 上需作改動(dòng)的 run()方法,它將在連接池上等待,并且池中一有連接就處理它:
?? public void run() {
??????? while (true) {
???????????? synchronized (pool) {
????????????????? while (pool.isEmpty()) {
?????????????????????? try {
??????????????????????????? pool.wait();
?????????????????????? } catch (InterruptedException e) {
??????????????????????????? return;
?????????????????????? }
?????????????????? }
?????????????????? connection = (Socket) pool.remove(0);
???????????? }
???????????? handleConnection();
??????? }
}
???
??? 實(shí)現(xiàn)需做改動(dòng)的 handleConnection() 方法,該方法將攫取連接的流,使用它們,并在任務(wù)完成之后清除它們:
??? 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);
??? }
}
??? 總結(jié):在現(xiàn)實(shí)生活中使用套接字只是這樣一件事,即通過貫徹優(yōu)秀的 OO 設(shè)計(jì)原則來保護(hù)應(yīng)用程序中各層間的封裝。