跳轉到內容

客戶端伺服器

75% developed
來自華夏公益教科書,開放的書籍,面向開放的世界

導航併發程式設計主題:v  d  e )


在 1990 年代,隨著 Unix 伺服器價格下降,趨勢從大型機計算轉向客戶端/伺服器。資料庫訪問和一些業務邏輯集中在後端伺服器上,收集來自使用者程式的資料安裝在前端使用者的“客戶端”計算機上。在 Java 世界中,前端和後端之間有三種主要通訊方式。

  • 客戶端應用程式使用 JDBC(Java 資料庫連線 API)連線到資料庫伺服器(後端上的業務邏輯有限,除非使用儲存過程)。
  • 客戶端應用程式使用 RMI (遠端方法呼叫) 與後端通訊。
  • 客戶端應用程式使用套接字連線與後端通訊。

套接字連線示例

[編輯 | 編輯原始碼]
圖 1:簡單的客戶端伺服器實現

此頁面顯示了套接字連線的示例。

建立伺服器

[編輯 | 編輯原始碼]

Java 語言的開發考慮了網路計算。因此,建立伺服器程式非常容易。伺服器是一段始終執行的程式碼,它在計算機上的特定埠上偵聽傳入請求。當請求到達時,它會啟動一個新執行緒來處理該請求。請檢視以下示例

偵聽埠

[編輯 | 編輯原始碼]
ComServer
類用於在埠上偵聽客戶端。
Computer code 程式碼清單 1.1:ComServer
import java.net.ServerSocket;
/**
 * -- Main Server Class; Listening on a port for client; If there is a client,
 * starts a new Thread and goes back to listening for further clients. --
 */
public class ComServer 
{
static boolean  GL_listening = true;
   /**
    * -- Main program to start the Server --
    */
   public static void main(String[] args) throws IOException
   {
      ComServer srv = new ComServer();
      srv.listen(); 
   } // --- End of Main Method ---

   /**
    * -- Server method; Listen for client --
    */
   public int listen() throws IOException
   {
    ServerSocket serverSocket = null;
    int iPortNumber = 9090;

       // --- Open the Server Socket where this should listen ---
       try {
           System.out.println( "*** Open the listening socket; at:"+ iPortNumber + " ***" );
           serverSocket = new ServerSocket( iPortNumber );
       } catch (IOException e) {
           System.err.println("Could not listen on port:"+iPortNumber );
           System.exit(1);
       }
       while ( GL_listening )
       {
        ComServerThread clientServ; 
           // --- Listening for client; If there is a client start a Thread -
           System.out.println( "*** Listen for a Client; at:"+ iPortNumber + " ***" );
           clientServ = new ComServerThread( serverSocket.accept() );
           // --- Service a Client ---
           System.out.println( "*** A Client came; Service it ***" );
           clientServ.start();   /* --- Use for multy Threaded --- */
      //     clientServ.run();    /* --- Use for Single Threaded --- */
       }

       // --- Close the Server socket;  Server exiting ---
       serverSocket.close();
    return 0;
   } // --- End of listen Method --- 
}  // --- End of ComServer Class ---
ServerSocket( iPortNumber )
建立一個伺服器套接字,繫結到指定的埠。
serverSocket.accept()
偵聽對該套接字的連線,並接受它。該方法會阻塞,直到建立連線。它會返回一個新的 Socket。

為一個客戶端提供服務

[編輯 | 編輯原始碼]
ComServerThread
此類擴充套件自 Thread,負責為一個客戶端提供服務。客戶端和伺服器之間將開啟 Socket 連線。客戶端和伺服器之間必須定義一個簡單的協議,伺服器必須理解客戶端想要從伺服器獲取什麼。客戶端將傳送一個終止命令,伺服器將終止 Socket 連線。ComServerThread 類負責處理所有客戶端請求,直到客戶端傳送一個終止命令。
Computer code 程式碼清單 1.2:ComServerThread
 /**
  * -- A class extended from a Thread; Responsible to service one client --
  */
 class '''ComServerThread''' extends Thread
 {
    private Socket clientSocket = null;
    COM_DATA tDataFromClient;
    COM_DATA tDataToClient; 
    ObjectInputStream oIn;
    ObjectOutputStream oOut;
    /**
     * -- Constructor --
     */
    public ComServerThread( Socket socket )
    {
       super( "ComServerThread" );
       this.clientSocket = socket;
    } // -- End of ComServerThread() constructor --
    /**
     * -- Overrun from the Thread (super) class --
     */
    public void run()
    {
       try {
          // --- Create the Writer; will be used to send data to client ---
          oOut = new ObjectOutputStream( clientSocket.getOutputStream() );
          // --- Create the Reader; will be used to get data from client ---
          oIn  = new ObjectInputStream( clientSocket.getInputStream() );
          // --- Create a new protocol object ---
          ComProtocol comp = new ComProtocol();
          // --- Send something to client to indicate that server is ready ---
          tDataToClient  = '''comp.processInput( null );'''
          '''sendDataToClient'''( tDataToClient, oOut );
          // --- Get the data from the client ---
          while ( true )
          {
             try {
                tDataFromClient = '''getDataFromClient( oIn )''';
                // --- Parse the request and get the reply ---
                tDataToClient = '''comp.processInput( tDataFromClient );'''
                // --- Send data to the Client ---
                '''sendDataToClient'''( tDataToClient, oOut );
             }
             catch ( EOFException e ) {
                System.out.println( "Client Disconnected, Bye, Bye" );
                break;
             }
             // --- See if the Client wanted to terminate the connection ---
             if ( tDataToClient.bExit )
             {
                System.out.println( "Client said Bye. Bye" );
                break;
             }
          }
          // --- Close resources;  This client is gone ---
          comp.Final();
          oOut.close();
          oIn.close();
          clientSocket.close();
       } catch ( IOException e ) {
        e.printStackTrace();
       }
    } // -- End of run() Method --
    /**
     * Get data from Client 
     */
    private static COM_DATA '''getDataFromClient'''( ObjectInputStream oIn ) throws IOException                                                                         
    {
        COM_DATA  tDataFromClient = null;         
        // --- Initialize variables ---
        //   tDataFromClient = new COM_DATA();
        while ( tDataFromClient == null )
        {
           try {
              // --- Read Line Number first --
              tDataFromClient = (COM_DATA) oIn.readObject();
           } catch ( ClassNotFoundException e ) {
               System.out.println( "ClassNotFound" );
           }
        }
        System.out.println( "Get: " + tDataFromClient.comData );
     return tDataFromClient;
    } // --- getDataFromClient() Method --- 
    /**
     * Send data to Client 
     */
    private static void '''sendDataToClient'''( COM_DATA tDataToClient,
                                           ObjectOutputStream  oOut ) throws IOException
    {         
        System.out.println( "Sent: " + tDataToClient.comData );
        oOut.writeObject( tDataToClient );
      return;
    } // -- End of sendDataToClient() Method --
 } // --- End of ComServerThread class ---
COM_DATA tDataFromClient
此變數將包含來自客戶端的資料物件。
COM_DATA tDataToClient
此變數將包含要傳送給客戶端的資料物件。
sendDataToClient
此方法將資料物件傳送給客戶端。
getDataFromClient
此方法從客戶端獲取資料物件。
processInput( tDataFromClient )
此方法屬於類ComProtocol,用於解釋客戶端命令並返回將傳送回客戶端的資料物件。

處理請求;實現通訊協議

[編輯 | 編輯原始碼]
ComProtocol
此類實現並封裝通訊邏輯(協議)。該協議如下所示
  1. 客戶端發起連線。
  2. 伺服器接受連線併發送確認,通知已準備好。
  3. 客戶端傳送請求。
  4. 伺服器根據請求做出響應。
...
  1. 客戶端傳送BYE請求。
  2. 伺服器確認BYE請求並斷開 Socket 連線。
  3. 客戶端收到對BYE的確認。
...
  1. 客戶端傳送SHUTDOWN請求。
  2. 伺服器確認SHUTDOWN請求並斷開連線,並停止偵聽其他客戶端。
  3. 客戶端收到對SHUTDOWN的確認。
Computer code 程式碼清單 1.3:ComProtocol
 class '''ComProtocol'''
 {
  private static final int COM_STATUS_WAITING    = 0; 
  private static final int COM_STATUS_READY_SENT = 1;
  private static final int COM_STATUS_DATA_SENT  = 2;
  private static final int COM_STATUS_WAITING_FOR_TERMINALID = 3;
  private int state = COM_STATUS_WAITING;
  
  // --- Reference to 'BACK-END' module ---  
  private MqTeAccess mqTe;
  ...
    /**
     * Create a protokol object; CAll MQ INI function
     */
    public ComProtocol()
    {
     int    iRet = 0;
        // --- Initialize 'BACK-END' modules  ---
        mqTe. ...
 ...
    }
    /**
     * --- Process the Input and Create the output to the Client ---
     */
    public COM_DATA processInput( COM_DATA theInput )
    {
     COM_DATA theOutput;
        // --- Initialize Variables ---
        theOutput = new COM_DATA();
        // --- Check if the Clients want to disconnect ---
        if ( theInput != null ) 
        {
            if ( theInput.comData.equals('''"!BYE.@"''') )
            {
                // --- The Client wants to terminate; Echo data back to client
                theOutput.comData = "BYE.";
                // --- Mark the communication to be terminated ---
                theOutput.bExit = true;
                // --- Set the internal state to wait for a new client ---
                state = COM_STATUS_WAITING;
                // --- Return Data object to be sent to the client ---
                return theOutput;
            }
            if ( theInput.comData.equals('''"!SHUTDOWN.@"''') )
            {
                // --- The Client wants to terminate; Echo data back to client
                theOutput.comData = "BYE.";
                // --- Mark the communication to be terminated ---
                theOutput.bExit = true;
                // --- Tell the server to stop listening for new clients ---
                ComServer.GL_listening = false;
                // --- Set the internal state to wait for a new client ---
                state = COM_STATUS_WAITING;
                // --- Return Data object to be sent to the client ---
                return theOutput;
            }
        }
        if ( state == COM_STATUS_WAITING )
        {
            // --- Send ready Message to the Client ---
            theOutput.comData = "Ready:";
            // --- Set the internal state ready; and wait for TerminalId ---
            state = COM_STATUS_WAITING_FOR_TERMINALID;
        }
        else if ( state == COM_STATUS_WAITING_FOR_TERMINALID )
        {
         int iRet;
            // --- Get the Terminal ID ---
            sTermId = theInput.comData; 
            // --- Call 'BACK-END' modules ...  ---
            mqTe. ...
 ...
            // --- Send ready Message with the Server Version to the Client ---
            theOutput.comData = "Ready;Server Version 1.0:";
            // --- Set the internal state raedy; and wait for TerminalId ---
            state = COM_STATUS_READY_SENT;
        }
        else if ( state == COM_STATUS_READY_SENT )
        {
         int iRet;
            String sCommand = theInput.comData;
            // --- Call 'BACK-END' modules ...
 ...
            /*
            ** --- Check if we should get Response data ---
            */
            if ( theInput.iRet == COM_DATA.NOWAIT_FOR_RESPONSE ) {
                // -- Set the Output Value ---
                theOutput.iRet = iRet;
                theOutput.comData = "";
            }
            else {
                // --- Call 'BACK-END' modules ---
                mqTe. ...
                // --- Set the Output Value ---
                theOutput.comData    = mqTe.sResponseBuffer; 
                theOutput.iRet       = iRet;
            }
        }
     return theOutput;
    }  // --- End of Method processInput() ---
 } // --- End of ComProtocol Class Definition ---

----

透過網路傳輸的資料物件

[編輯 | 編輯原始碼]
COM_DATA
是透過網路傳輸的資料結構類。該類只包含資料。
Computer code 程式碼清單 1.4:COM_DATA
 /**
  * COM_DATA data structure 
  */
 public class COM_DATA implements Serializable
 {
  public String  comData;
  public boolean bExit;
  public int     iRet;
    /**
     * --- Constants values can be passed in in iRet to the Server ---
     */
    static final int WAIT_FOR_RESPONSE    = 0;
    static final int NOWAIT_FOR_RESPONSE  = 1;
   /**
    * Initialize the data structure
    */
   public COM_DATA()
   {
      comData     = "";
      bExit       = false;
      iRet        = 0;
   } // -- End of COM_DATA() Constructor --   
   /**
    * Copy over it contents 
    */
   public void copy( COM_DATA tSrc )
   {
      this.comData     = tSrc.comData;
      this.bExit       = tSrc.bExit;
      this.iRet        = tSrc.iRet;
    return;
   } 
 } // -- End of COM_DATA class --

建立客戶端

[編輯 | 編輯原始碼]

用於伺服器/服務的客戶端程式碼通常是使用者應用程式用來與伺服器互動的 API。藉助客戶端 API,使用者應用程式無需瞭解如何連線到伺服器以獲取服務。

ComClient
此類是客戶端 API。應用程式使用此類與伺服器通訊。

以下是上述伺服器的客戶端類

Computer code 程式碼清單 1.5:ComClient
 public class ComClient
 {
  private Socket         comSocket;
  private ObjectOutputStream oOut;
  private ObjectInputStream  oIn;
  private boolean         IsItOpen = false;       
    /**
     * --- Open Socket ---
     */
    public void openCom( String sServerName,
                         int    iPortNumber ) throws UnknownHostException,
                                                              IOException  
    {
       try {
          // --- Open Socket for communication ---
          comSocket = new Socket( sServerName, iPortNumber );     
          // --- Get Stream to write request to the Server ---
          oOut = new ObjectOutputStream( comSocket.getOutputStream() );     
          // --- Get Stream// to read from the Server
          oIn = new ObjectInputStream( comSocket.getInputStream());
          // --- Set internal Member variable that the Communication opened ---
          IsItOpen = true;
       } catch ( java.net.UnknownHostException e ) {
          System.err.println( "(openCom:)Don't know about host: "+sServerName );
          IsItOpen = false;
          throw( e );                                         
       } catch ( java.io.IOException e ) {
          System.err.println("(openCom:)Couldn't get I/O for the connection to: "+ sServerName );
          IsItOpen = false;
          throw( e );         
       }               
    }
    /**
     * --- Check if Socket is open ---
     */
    public boolean isItOpen()
    {
      return IsItOpen;
    }     
    /**
     * --- Get data string from the Server ---
     */
    public void getServerData( COM_DATA tServData ) throws IOException
    {
        // --- Initialize Variables ---
        tServData.comData = "";
        // --- Get the Response from the Server ---              
        try {
           tServData.copy( (COM_DATA) oIn.readObject() );
        }   
        catch ( ClassNotFoundException e ) {
            System.out.println( "Class Not Found" );
        } 
        System.out.println( "Server: " + tServData.comData );
        if ( tServData.comData.equals("BYE.") )
        {
            tServData.bExit = true;
        }        
     return;
    }
    /**
     * --- Send data to the Server ---
     */
    public void sendDataToServer( COM_DATA tServData ) throws IOException
    {
        // --- Send the data string ---
        System.out.println( "Send: " + tServData.comData );
        oOut.writeObject( tServData );
     return;
    } 
    /**
     * --- Close Socket --- 
     */
    public void closeCom() throws IOException
    {
        oOut.close();
        oIn.close();
        comSocket.close();
        IsItOpen = false;
    }    
 }
getServerData( COM_DATA tServData )
此方法從伺服器讀取資料並將值複製到tServData 物件。
sendDataToServer( COM_DATA tServData )
此方法將tServData 物件透過網路傳送到伺服器。
oIn.readObject()
此方法返回伺服器傳送的資料物件。
oOut.writeObject( tServData )
此方法將資料物件傳送到伺服器。


Clipboard

待辦事項
新增一些類似於變數中的練習


華夏公益教科書