JavaSocket API提供了一個很方便的對象接口進行網(wǎng)絡(luò)編程。本文用一個簡單的TCP Echo Server做例子,演示了如何使用Java完成一個網(wǎng)絡(luò)服務(wù)器。

 

用作例子的TCP Echo Server是按以下方式工作的:

當(dāng)一個客戶端通過TCP連接到服務(wù)器后,客戶端可以通過這個連接發(fā)送數(shù)據(jù)到服務(wù)端,而服務(wù)端接收到數(shù)據(jù)后會把這些數(shù)據(jù)用同一個TCP連接發(fā)送回客戶端。服務(wù)端會一直保持這個連接直到客戶端關(guān)閉它為止。

 

因為服務(wù)器需要能同時處理多個客戶端,我們先選用一個常見的多線程服務(wù)模型:

讓一個Thread負(fù)責(zé)監(jiān)聽服務(wù)端口,當(dāng)有新的連接建立的時候,這個監(jiān)聽的Thread會為這個連接創(chuàng)建一個新的Thread來處理它。這樣,服務(wù)器可以接受多個連接,并讓多個Thread來分別處理它們。

 

以下是相應(yīng)的服務(wù)端程序:

public class EchoServer implements Runnable {

   

    public void run() {

       try {

           ServerSocket svr = new ServerSocket(7);

           while (true) {

              Socket sock = svr.accept();

              new Thread(new EchoSession(sock)).start();

           }

       } catch (IOException ex) {

           throw new ExceptionAdapter(ex);

       }

    }

}

 

這段代碼先創(chuàng)建了一個ServerSocket的對象并讓其監(jiān)聽在TCP端口7上,然后在一個循環(huán)中用accept()方法接收新的連接,并創(chuàng)建處理這一連接的Thread。實際處理每個客戶端連接的邏輯包含在EchoSession這個類里面。

 

在以上代碼中使用了ExceptionAdapter這個類,它的作用是把一個checked Exception包裝成RuntimeException。詳細(xì)的說明可以參考避免在Java中使用Checked Exception 一文。

 

以下是EchoSession的代碼:

public class EchoSession implements Runnable {

   

    public EchoSession(Socket s) {

       _sock = s;

    }

   

    public void run() {

       try {

           try {

              InputStream input = _sock.getInputStream();

              OutputStream output = _sock.getOutputStream();

              byte [] buf = new byte [128];            

              while (true) {

                  int count = input.read(buf);

                  if (count == -1)

                     break;

                  output.write(buf, 0 , count);

              }

           } finally {

              _sock.close();

           }

       } catch (IOException ex) {

           throw new ExceptionAdapter(ex);   

       }

    }

   

    protected Socket _sock = null;

}

 

EchoSession接受一個Socket對象作為構(gòu)造參數(shù),在其run()方法中,它不停的從這個Socket對象的InputStream里面讀數(shù)據(jù)并寫回到該SocketOutputStream中去,直到這個連接被客戶端關(guān)閉為止(InputStreamread方法返回-1)。

 

EchoSession需要一個線程來執(zhí)行,這容易讓人聯(lián)想到用Thread來作為EchoSession的父類。不過,這樣做不夠靈活,開銷也比較大。而選擇讓EchoSession實現(xiàn)Runnable接口就靈活得多。在接下來的使用Thread PoolEcho Server中可以看到這一點。

 

以上已經(jīng)是一個完整的TCP Echo Server,不過隨著客戶不停的連接和斷開,這個服務(wù)器會不停的產(chǎn)生和消除線程,而這兩個都是比較‘昂貴’的操作。為了避免這種消耗,可以考慮采用Thread Pool的機制。

 

使用在一個簡單的Thread緩沖池的實現(xiàn)一文中Thread Pool的實現(xiàn),可以對EchoServer作如下修改(EchoSession無需做修改):

public class EchoServer implements Runnable {

   

    public void run() {

       try {

           ServerSocket svr = new ServerSocket(7);

          

           // 初始化Thread Pool

           SyncQueue queue = new SyncQueue(10);

           for (int i = 0; i < 10; i ++) {

              new Thread(new Worker(queue)).start();

           }

 

           while (true) {

              Socket sock = svr.accept();

              // 把任務(wù)放入Thread Pool

              queue.put(new EchoSession(sock));

           }

       } catch (IOException ex) {

           throw new ExceptionAdapter(ex);

       }

    }

}

 

這里可以看出讓EchoSession實現(xiàn)Runnable接口的靈活性,無需修改它就可以在Thread Pool里使用。

 

在這個例子里使用的Thread Pool比較簡單,沒有動態(tài)調(diào)整Thread數(shù)量的功能,所以這個Echo Server最多只能同時服務(wù)10個客戶端。然而通過重載SyncQueue,我們可以很方便地加入這個功能以突破這個限制。

 

在對網(wǎng)絡(luò)服務(wù)器的性能以及并發(fā)度要求很高的時候,讓每個客戶端由一個專門的Thread來處理有可能不能滿足我們的要求(想象一下同時有數(shù)千個客戶端的情況)。這時可以考慮使用JavaNIO API來構(gòu)建服務(wù)器架構(gòu),因為NIOIO操作都是非阻塞的,我們只需要很少的Thread就可以充分地利用CPU來處理多個客戶端的請求。關(guān)于NIO的話題,在這篇文章就不再贅述,希望以后能有機會討論。 :)