1. 程式人生 > >java網路程式設計(二)複用Socket連線以及使用多執行緒完成多個客戶端的連線

java網路程式設計(二)複用Socket連線以及使用多執行緒完成多個客戶端的連線

在前面的示例中,客戶端中建立了一次連線,只發送一次資料就關閉了,這就相當於撥打電話時,電話打通了只對話一次就關閉了,其實更加常用的應該是撥通一次電話以後多次對話,這就是複用客戶端連線。

那 麼如何實現建立一次連線,進行多次資料交換呢?其實很簡單,建立連線以後,將資料交換的邏輯寫到一個迴圈中就可以了。這樣只要迴圈不結束則連線就不會被關 閉。按照這種思路,可以改造一下上面的程式碼,讓該程式可以在建立連線一次以後,傳送三次資料,當然這裡的次數也可以是多次,示例程式碼如下:

public class SocketClient {
    public static void main(String[] args) {
        Socket socket = null
; InputStream inputStream = null; OutputStream outputStream = null; String data[] = { "hello world1", "hello world2", "hello world3" }; try { // 建立Socket例項 socket = new Socket("hadoop", 10000); // 獲取輸出流,向伺服器發生資料 outputStream = socket.getOutputStream(); ///
獲取輸入流,獲取伺服器的響應
inputStream = socket.getInputStream(); byte[] buff = new byte[1024]; // 迴圈傳送 for (int i = 0; i < data.length; i++) { // 傳送資料 outputStream.write(data[i].getBytes()); int len = inputStream.read(buff); // 列印服務端的相應
System.out.println(new String(buff, 0, len)); } } catch (Exception e) { e.printStackTrace(); } finally { // 釋放資源 try { inputStream.close(); outputStream.close(); socket.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }

現在客戶端可以向伺服器發生三次請求且獲取三次相應,而我們伺服器端是對話一次資料以後就關閉了連線,如果伺服器端程式關閉了,客戶端繼續傳送資料肯定會出現異常
按照客戶端實現的邏輯,也可以複用伺服器端的連線,實現的原理也是將伺服器端的資料交換邏輯寫在迴圈中即可,按照該種思路改造以後的伺服器端程式碼為:

public class SocketService {

    public static void main(String[] args) {
         InputStream inputStream =null;
         OutputStream outputStream =null;
       Socket socket = null;
       ServerSocket serviceSocket =null;
        try {
            //伺服器端接收訊息的類。定製埠號為8888
             serviceSocket = new ServerSocket(10000);
            //獲取socket。這個方法是阻塞式的
            socket = serviceSocket.accept();
          inputStream = socket.getInputStream();
          byte buf[] = new byte[1024];
          //向客戶端生成響應
            outputStream = socket.getOutputStream();
          for(int i=0;i<3;i++){
              int len =inputStream.read(buf);
            //列印客戶端的訊息
                System.out.println(new String(buf,0,len));
                outputStream.write("收到".getBytes());
          }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }finally {
             //釋放資源
              try {
                     outputStream.close();
                     inputStream.close();
                socket.close();
                serviceSocket.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

在該示例程式碼中,也將資料傳送和接收的邏輯寫在了一個for迴圈內部,只是在實現時硬性的將迴圈次數規定成了3次,這樣程式碼雖然比較簡單,但是通用性比較差,客戶端和伺服器端的次數控制還不夠靈活,如果客戶端的次數不固定怎麼辦呢?不著急,這個問題會一一進行解答。

現在我們學習另外一個網路程式設計的突出問題。
如何使伺服器端支援多個客戶端同時工作?
前面介紹的伺服器端程式,只是實現了概念上的伺服器端,離實際的伺服器端程式結構距離還很遙遠,如果需要讓伺服器端能夠實際使用,那麼最需要解決的問題就是——如何支援多個客戶端同時工作。

一個伺服器端一般都需要同時為多個客戶端提供通訊,如果需要同時支援多個客戶端,則必須使用執行緒的來解決。簡單來說,也就是當伺服器端接收到一個連線時,啟動一個專門的執行緒處理和該客戶端的通訊。

按照這個思路改寫的服務端示例程式將由兩個部分組成,SocketService類實現伺服器端控制,實現接收客戶端連線,然後開啟專門的邏輯執行緒處理該連線,LogicThread類實現對於一個客戶端連線的邏輯處理,將處理的邏輯放置在該類的run方法中。該服務端示例的程式碼實現為:

public class SocketService {

    public static void main(String[] args) {
       Socket socket = null;
       ServerSocket serviceSocket =null;
        try {
            //伺服器端接收訊息的類。定製埠號為8888
             serviceSocket = new ServerSocket(10000);
             System.out.println("伺服器已經啟動");
             while(true){
                    //獲取socket。這個方法是阻塞式的
                    socket = serviceSocket.accept();
                    //一但獲取連線後就開子執行緒來處理
                  new LogicThread(socket);
             }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }finally {
             //釋放資源
              try {
                serviceSocket.close();
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }
public class LogicThread extends Thread {

    Socket socket;
    InputStream inputStream;
    OutputStream outputStream;

    public LogicThread(Socket socket) {
        this.socket = socket;
        start(); // 啟動執行緒
    }
    public void run() {
        byte[] buf = new byte[1024];
        try {
            // 初始化流
            outputStream = socket.getOutputStream();
            inputStream = socket.getInputStream();
            for (int i = 0; i < 3; i++) {
                // 讀取資料
                int len = inputStream.read(buf);
                System.out.println(new String(buf,0,len));
                // 反饋資料
                outputStream.write("收到".getBytes());
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            close();
        }
    }
    /**
     * 
     * 關閉流和連線
     * 
     */
    private void close() {
        try {
            // 關閉流和連線
            outputStream.close();
            inputStream.close();
            socket.close();
        } catch (Exception e) {
        }
    }
}

每次使用一個連線物件構造該執行緒,該連線物件就是該執行緒需要處理的連線,線上程構造完成以後,該執行緒就被啟動起來了,然後在run方法內部對客戶端連線進行處理,資料交換的邏輯和前面的示例程式碼一致,都是IO流處理。
這裡的示例還只是基礎的伺服器端實現,在實際的伺服器端實現中,由於硬體和埠數的限制,所以不能無限制的建立執行緒物件,而且頻繁的建立執行緒物件效率也比較低,所以程式中都實現了執行緒池來提高程式的執行效率。