Establishing a Simple Server Using Stream Sockets
The two examples discussed so far use high-level Java networking capabilities to communicate between applications. In the examples, it was not the Java programmer's responsibility to establish the connection between a client and a server. The first program relied on the Web browser to communicate with a Web server. The second program relied on a JEditorPane to perform the connection. This section begins our discussion of creating your own applications that can communicate with one another.
Establishing a simple server in Java requires five steps. Step 1 is to create a ServerSocket object. A call to the ServerSocket constructor, such as
ServerSocket server = new ServerSocket( portNumber, queueLength );
registers an available TCP port number and specifies a maximum number of clients that can wait to connect to the server (i.e., the queue length). The port number is used by clients to locate the server application on the server computer. This is often called the handshake point. If the queue is full, the server refuses client connections. The constructor establishes the port where the server waits for connections from clientsa process known as binding the server to the port. Each client will ask to connect to the server on this port. Only one application at a time can be bound to a specific port on the server.
Software Engineering Observation 24.1
Port numbers can be between 0 and 65,535. Most operating systems reserve port numbers below 1024 for system services (e.g., e-mail and World Wide Web servers). Generally, these ports should not be specified as connection ports in user programs. In fact, some operating systems require special access privileges to bind to port numbers below 1024. |
Programs manage each client connection with a Socket object. In Step 2, the server listens indefinitely (or blocks) for an attempt by a client to connect. To listen for a client connection, the program calls ServerSocket method accept, as in
Socket connection = server.accept();
which returns a Socket when a connection with a client is established. The Socket allows the server to interact with the client. The interactions with the client actually occur at a different server port from the handshake point. This allows the port specified in Step 1 to be used again in a multithreaded server to accept another client connection. We demonstrate this concept in Section 24.8.
Step 3 is to get the OutputStream and InputStream objects that enable the server to communicate with the client by sending and receiving bytes. The server sends information to the client via an OutputStream and receives information from the client via an InputStream. The server invokes method getOutputStream on the Socket to get a reference to the Socket's OutputStream and invokes method getInputStream on the Socket to get a reference to the Socket's InputStream.
The stream objects can be used to send or receive individual bytes or sequences of bytes with the OutputStream's method write and the InputStream's method read, respectively. Often it is useful to send or receive values of primitive types (e.g., int and double) or Serializable objects (e.g., Strings or other serializable types) rather than sending bytes. In this case, we can use the techniques discussed in Chapter 14 to wrap other stream types (e.g., ObjectOutputStream and ObjectInputStream) around the OutputStream and InputStream associated with the Socket. For example,
ObjectInputStream input = new ObjectInputStream( connection.getInputStream() ); ObjectOutputStream output = new ObjectOutputStream( connection.getOutputStream() );
The beauty of establishing these relationships is that whatever the server writes to the ObjectOutputStream is sent via the OutputStream and is available at the client's InputStream, and whatever the client writes to its OutputStream (with a corresponding ObjectOutputStream) is available via the server's InputStream. The transmission of the data over the network is seamless and is handled completely by Java.
Step 4 is the processing phase, in which the server and the client communicate via the OutputStream and InputStream objects. In Step 5, when the transmission is complete, the server closes the connection by invoking the close method on the streams and on the Socket.
Software Engineering Observation 24.2
With sockets, network I/O appears to Java programs to be similar to sequential file I/O. Sockets hide much of the complexity of network programming from the programmer. |
Software Engineering Observation 24.3
With Java's multithreading, we can create multithreaded servers that can manage many simultaneous connections with many clients. This multithreaded-server architecture is precisely what popular network servers use. |
Software Engineering Observation 24.4
A multithreaded server can take the Socket returned by each call to accept and create a new thread that manages network I/O across that Socket. Alternatively, a multithreaded server can maintain a pool of threads (a set of already existing threads) ready to manage network I/O across the new Sockets as they are created. See Chapter 23 for more information on multithreading. |
Performance Tip 24.2
In high-performance systems in which memory is abundant, a multithreaded server can be implemented to create a pool of threads that can be assigned quickly to handle network I/O across each new Socket as it is created. Thus, when the server receives a connection, it need not incur the overhead of thread creation. When the connection is closed, the thread is returned to the pool for reuse. |