Survey
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
* Your assessment is very important for improving the workof artificial intelligence, which forms the content of this project
Sockets Client-server applications were covered in our discussion of applets. How do client and server programs communicate over the Internet? They use a connected pair of abstract communication channels called sockets. A socket provides an input stream for receiving data, and an output stream for sending data. The input stream of one socket is connected to the output stream of the socket it is connected to: :Client :Socket (client-side) :InputStream :OutputStream machine/network boundary :InputStream :OutputStream :Socket (server-side) connection :Server How are connected sockets created? A server socket is an abstract listening device associated with an IP address and a port number. (The IP address is associated with the computer that runs the server. Most computers provide 64,000 port numbers to choose from.) Here is how a server socket is created by the server: try { ServerSocket mySocket = new ServerSocket(4444); // port # = 4444 System.out.println(mySocket.getInetAddress()); prints IP address // etc. } catch(IOException ioe) { ... } A server socket provides a blocking accept() method that returns a server-side socket when a client tries to connect with the server. This socket has associated input and output streams, which can be used to exchange data between client and server: // server control loop: try { // ... while(true) { Socket request = mySocket.accept(); BufferedReader in = Tools.makeReader(request.getInputStream()); PrintWriter out = Tools.makeWriter(request.getOutputStream()); // handle request } } catch(IOException ioe) { ... } The client-side socket is created by the constructor from the Internet address and port number of the server socket. The constructor blocks until the connection is accepted by the server: Socket sock = new Socket("jupiter.sjsu.edu", 4444); BufferedReader in = Tools.makeReader(sock.getInputStream())); PrintWriter out = Tools.makeWriter(sock.getOutputStream()); // communicate with server A Client-Server Framework Java now provides a web-based, client-server framework where applets are the clients and servlets are the servers. An applet is run by a Java virtual machine embedded in a browser. A servlet is run by a servlet engine embedded in a web server. Before the servlet framework was available, I used the server framework described below. A Generic Server A generic server instantiates the Master-Slave pattern. The server (i.e., the master) perpetually listens at a server socket for an incoming client request. When a request arrives, the server constructs and starts a handler thread (i.e. the slave) to service the request. Each slave is provided with a unique identification number for debugging and a socket connection to the client. The server constructs the handler using a handler factory object, which is passed to the server's constructor. Thus, creating a specific type of server requires programmers to write a handler and a handler factory object. ServerSocket 1 Server 1 Thread listen() 1 1 1 "HandlerFactory" * "RequestHandler" creates 1 1 Socket run() processRequest() makeRequestHandler() framework customization ConcreteHandlerFactory makeRequestHandler() creates ConcreteHandler processRequest() Notice that the attributes of Server are protected so they can be freely accessed by concrete server class extensions. class Server { public Server(int port, HandlerFactory h) { try { myPort = port; mySocket = new ServerSocket(myPort); myHandlerFactory = h; myAddr = mySocket.getInetAddress(); } // try catch(IOException ioe) { System.err.println("Failed to create socket; " + ioe); System.exit(1); } // catch } // Server Constructor // listens for a request, then creates & starts a handler public void listen() { int id = 0; try { while(true) { System.out.println("Server: listening"); Socket request = mySocket.accept(); // blocks RequestHandler handler = myHandlerFactory(request, nextID++); handler.start(); } // while System.out.println("Server: halting"); } // try catch(IOException ioe) { System.err.println("Failed to accept socket, " + ioe); System.exit(1); } // catch } // listen protected protected protected protected protected ServerSocket mySocket; int myPort; InetAddress myAddr; HandlerFactory myHandlerFactory; static int nextID = 0; } // Server Request Handler A request handler instantiates a class derived from the abstract RequestHandler class, which is derived from the Thread class. An abstract request handler creates reader and writer character streams from the input and output byte streams associated with the client socket (this seems to be what JDK 1.1 wants us to do, although data streams would be another logical candidate). Notice that these character streams, together with the client socket and the id number, are declared protected, hence are available to the concrete handler. The run() method of the abstract request handler calls an abstract processRequest() method. This must be defined in the concrete request handler. class RequestHandler extends Thread { public RequestHandler(Socket s, int i) { request = s; myId = i; try { in = Tools.makeReader(request.getInputStream()); out = Tools.makeWriter(request.getOutputStream()); } // try catch(IOException ioe) { System.err.println("failed to create streams; " + ioe); System.exit(1); } // catch } public void run() { boolean more = true; while(more) more = processRequest(); } public abstract boolean processRequest(); protected protected protected protected } Socket request; BufferedReader in; PrintWriter out; int myId; // RequestHandler Server Factories The only requirement of a handler factory is that it provide a method called makeHandler(), which expects a socket and id number as input, and returns a request handler as output. We can enforce this with a handler factory interface: interface HandlerFactory { public RequestHandler makeHandler(Socket s, int id); } // HandlerFactory Example: Reflectors Applets can only communicate with their host servers. Reflectors are a Java idiom for working around this restriction. A reflector is a server running on the host server and listening to a socket for messages coming from applets. When a message arrives, the reflector forwards the message to a fixed destination object. Web Client Applet Web Server Reflector Remote Computer Remote To use the server framework defined above, we need to extend the RequestHandler class with a reflector handler class that provides a processRequest() method. In this case processRequest() reads an applet message (a String) on its inherited BufferedReader, in, then writes the message to a PrintWriter connected by a socket to the destination object. The IP address and port number of the destination object is specified in the reflector handler's constructor: class ReflectorHandler extends RequestHandler { public ReflectorHandler(Socket s, String loc, int port) { super(s, 0); Socket dest = new Socket(loc, port); destIn = Tools.makeReader(dest.getInputStream())); destOut = Tools.makeWriter(sock.getOutputStream()); } public boolean processRequest() { try { String msg = in.readLine(); destOut.println(cmmd); } catch(IOException ioe) { System.err.println("Request read failure; " + ioe.toString()); stop(); } // catch } return false; // processRequest private BufferedReader destIn; private PrintWriter destOut; } // ReflectorHandler We also need to specify a reflector handler factory, which is used by the server (the master) to generate request handlers (slaves). The makeHandler() factor method (C++ programmers call factories virtual constructors) ignores the id number and uses the ReflectorHandler constructor to create a new reflector handler, which, as required by the framework, is returned as a RequestHandler instance. The IP location and port number of the destination object are also parameters to the reflector handler's constructor: class ReflectorFactory implements HandlerFactory { private String destLoc; private int destPort; public ReflectorFactory(String loc, int port) { destLoc = loc; destPort = port; } public RequestHandler makeHandler(Socket s, int id) { return new ReflectorHandler(s, destLoc, destPort); } } // ReflectorFactory A reflector is simply an instance of the Server class: ReflectorHandlerFactory factory = new ReflectorFactory("jupiter.sjsu.edu", 42); Server reflector = new Server(4444, factory); Example: A Database Server A database server is a special type of server that provides database access to clients. The handler for a database server (called an SQL handler) reads an SQL command from the client, executes the command using the execute method provided by Database instances, then sends a formatted string representing the result back to the client: class SQLHandler extends RequestHandler { private Database myDbase; public SQLHandler(Socket s, int id, Database db) { super(s, id); myDbase = db; } public boolean processRequest() { try { String sql = in.readLine(); String result = myDbase.execute(sql); out.writeBytes(result); request.close(); } // try catch(IOException ioe) {} // catch return false; } // processRequest } // SQLHandler The SQL handler factory creates the database object, then passes it to the SQL handlers it creates: class SQLHandlerFactory implements HandlerFactory { Database myDbase; // see Interfacing with Databases section public SQLHandlerFactory(String dm, String url, String user, String pswd) { myDbase = new Database(dm, url, user, pswd); } public RequestHandler makeHandler(Socket s, int id) { return new SQLHandler(s, id, myDbase); } } // SQLHandlerFactory The following instructions create and start a database server at port 2222 on jupiter.sjsu.edu: HandlerFactory factory = new SQLHandlerFactory("sun.jdbc.odbc.JdbcOdbcDriver", "jdbc:odbc:ODBC_Addresses", "smith", "foobar"); Server dbaseServer = new Server("jupiter.sjsu.edu", 2222, factory);