跳转到内容

客户端服务器

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

待办事项
添加一些类似于变量中的练习


华夏公益教科书