Java socket programming intro

— 10 minute read

Here is a quick introduction to socket programming in Java – using a simple demonstration of client-server communication. For the purposes of the demonstration, both the client and the server will be on the same physical machine (“localhost”). However, this can be easily changed later on.

Create two classes, one named “SocketsDemoClient” for the client, and another named “SocketsDemoServer” for the server.

The server class permalink

Create some default values for the port number (no need to to define host name on the server – it is always the local machine). Also, define the main method which gets executed when running this class.

    private int DEFAULT_PORT = 9999;

public static void main(String[] args) { SocketsDemoServer server = new SocketsDemoServer(); server.serverLoop(); }

Create the server loop method that does all the heavy lifting in this class. Now, we simply define the objects that will be used as local variables and create an infinite while loop.

    public void serverLoop() {
        try {
            // create a server socket with the default port number
            ServerSocket serverSocket = new ServerSocket(DEFAULT_PORT);
            Socket sck;
            BufferedReader inStream;
            PrintWriter outStream;
            String input, reply;
            while (true) {
                //code will go here later
            }
        }
        catch (IOException ioex)
        {
            System.err.println("IO exception occurred in the server loop:");
            ioex.printStackTrace();
        }

Inside this loop, we want to do several things: accept a socket connection, read information from the socket (written by the client), do some processing, and finally write a return message or reply to the socket (to be read by the client).
Accept a socket connection. Note that ServerSocket#accept() cause the execution to pause until client communicates with the server. The accepted Socket provides the low-level IO streams for this client-server communication, which we then will need to wrap with our own high-level IO stream for more intuitive or meaningful manipulation.

                sck = serverSocket.accept();
                System.out.println("Client socket on server side: " + Integer.toHexString(sck.hashCode())); //print some debug info
                inStream = new BufferedReader(new InputStreamReader(sck.getInputStream()));
                outStream = new PrintWriter(sck.getOutputStream(), true);

We now read the input that was sent by the client from the socket and process it. In this case, the reply is merely some simple string manipulation performed on the input. However, in a real application this is likely to be something more useful, such as performing a database query.

                input = inStream.readLine();
                System.out.println("Server RCV: " + input);
                reply = "\"" + input.toUpperCase().replaceAll("[AEIOU]", "#") + "\"";

We then send the reply to the client through the same socket, and then close the socket. After this, the execution will loop back, starts over, and will wait for another socket connection.

                outStream.println(reply);
                System.out.println("Server SND: " + reply);
                sck.close();

The client class permalink

Now if you run the server, you will find that absolutely nothing happens. This is because the execution will stop at “serverSocket.accept()”, and then just wait there for an incoming socket connection. The client provides this incoming socket connection. While the client is usually a different machine than the server, in this demonstration they will be the same.

Declare the default values for the host name and the port number. The host name should match the host that the server class is running on. The port number must match the port number that is used by the ServerSocket in the server class. If you wish to run the client on a different machine than the server, simply change the value of “DEFAULT_HOST” from “localhost” to the relevant host name.
Also, create a main method to define what should be executed when this class is executed. Here the main method will attempt to send the server two messages and then exit.

    private String DEFAULT_HOST = "localhost";
    private int DEFAULT_PORT = 9999;
    public static void main (String[] args)
    {
        SocketsDemoClient client = new SocketsDemoClient();
        client.clientAction("Hello world");
        client.clientAction("The quick brown fox jumps over the lazy dog");
    }

We should now define the client action method used above. Here we want to do several things: create a socket for communicating with the server, send a message to the server, and then receive the reply from the server.
Note that using sockets or server sockets can potentially cause IO exceptions, so these must be caught and handled. Here, only a rudimentary handling is provided by printing the stack trace and allowing the rest of the execution to go on.

    public void clientAction(String message) {
        try {
            //rest of code goes here
        }
        catch (IOException ioex)
        {
            System.err.println("IO exception occured in the client action:");
            ioex.printStackTrace();
        }
    }

Create the socket. The socket will, as in the server class, provide the low-level IO streams, which we wrap in high-level IO streams.

            Socket socket = new Socket(InetAddress.getByName(DEFAULT_HOST), DEFAULT_PORT);
            System.out.println("Client socket on client side: " + Integer.toHexString(socket.hashCode())); //print debug info
            PrintWriter outStream = new PrintWriter(socket.getOutputStream(), true);
            BufferedReader inStream = new BufferedReader(new InputStreamReader(socket.getInputStream()));

Send the server a message via the socket.

            outStream.println(message);
            System.out.println("Client SND: " + message);

Read the reply from the server, and do something with it – in this case simply print it.

            String reply = inStream.readLine();
            if (reply != null) {
                System.out.println("Client RCV: " + reply);
            }

Close the socket once we are done with it. Note that after closing the socket that there is no loop back. This sequence of actions only gets executed once each time the method is called. This is the major difference between the client and the server: The server just executes continuously, waiting (listening) for the next connection right after processing the last, whereas the client connects as-and-when.

            socket.close();

Putting it all together permalink

Now, run the server class, and notice that still nothing happens. Leave it running, and in a separate shell run the client class. Then both of the process will do something:

You should see this as the output from the client:

Client socket on client side: 41e81f
Client SND: Hello world
Client RCV: "H#LL# W#RLD"
Client socket on client side: 8304c1
Client SND: The quick brown fox jumps over the lazy dog
Client RCV: "TH# Q##CK BR#WN F#X J#MPS #V#R TH# L#ZY D#G"

… and this output from the server:

Client socket on server side: 199dd1
Server RCV: Hello world
Server SND: "H#LL# W#RLD"
Client socket on server side: c90513
Server RCV: The quick brown fox jumps of the lazy dog
Server SND: "TH# Q##CK BR#WN F#X J#MPS #V#R TH# L#ZY D#G"

Notes permalink

Some things to note are that socket communication is a very low-level means of communicating across networks. There are other high-level means available as well, but somewhere down the line, they all eventually make use socket communication.
Additionally, these example have been simplified to the point that they have little real application, as several limitations have not been addressed. The most obvious one here is that these classes would work well for a single client. However, in actual applications, several clients may attempt to communicate with the server simultaneously. This can be achieved by processing each communication in a different thread, and may be taken further by creating a thread pool to limit resource usage.