After posts on several server libraries, including Sun’s HTTP; Simple Framework’s HTTP; and Websocket, it seems appropriate to include a plain TCP socket server / client example. Without any application protocol to adhere to, it’s straight forward to setup and test. There are two essential classes involved: On the server side there’s a ServerSocket, while the client has a Socket. The only difference is that the ServerSocket accepts one or more incoming connections, and provide a Socket handle for each. After that, both client and server are the same, in that the Socket object provides input and output streams once established.

In the code below, a ServerSocket is set to listen to a specific port, and to accept a single incoming connection. It grabs the IO streams, makes these available to the rest of the test, and finally releases a semaphore lock to allow the test to continue. Normally, the server thread would wait for further incoming requests, and would probably spawn or assign pooled threads to handle the request. Here we focus only on the basic IO parts.

...
    ServerSocket server = new ServerSocket(PORT);
    listen(server);
...

  private void listen(ServerSocket server) {
    new Thread(() -> {
      try {
        Socket socket = server.accept();
        System.out.println("Incoming connection: " + socket);

        serverOut = socket.getOutputStream();
        serverIn = socket.getInputStream();

        lock.release();
        System.out.println("Released lock");
      } catch (IOException e) {
        e.printStackTrace();
      }
    }).start();
  }

On the client side, it’s just as simple: Establish a connection to the severer host and port, and get the IO streams.

    Socket client = new Socket("localhost", PORT);
    OutputStream clientOut = client.getOutputStream();
    InputStream clientIn = client.getInputStream();

The rest of the test code below asserts that messages are received correctly both on the client and the server, using the raw streams and wrapped PrinterWriter helpers. Again, he semaphore is used to wait for the server thread to establish its connection before the rest of the test continues. Without it, using the IO streams will results in NullPointerExceptions, since they are not initialized yet.

SocketTest.java
GitHub Raw
/* Copyright rememberjava.com. Licensed under GPL 3. See http://rememberjava.com/license */
package com.rememberjava.net;

import static org.junit.Assert.assertEquals;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.Semaphore;

import org.junit.Test;

/**
 * Simple client / server Socket tests, including a Buffered PrintWriter which
 * has to be flushed.
 */
public class SocketTest {

  private static final int PORT = 8887;

  private OutputStream serverOut;
  private InputStream serverIn;

  /**
   * Shared lock between the "client" and "server" code, to make the test case
   * synchronous.
   */
  private Semaphore lock = new Semaphore(0);

  /**
   * Tests server and client side sockets in one flow. A lock object is used for
   * synchronous between the two sides.
   */
  @Test
  public void testClientServer() throws IOException, InterruptedException {
    ServerSocket server = new ServerSocket(PORT);
    listen(server);

    Socket client = new Socket("localhost", PORT);
    OutputStream clientOut = client.getOutputStream();
    InputStream clientIn = client.getInputStream();

    System.out.println("Waiting for lock");
    lock.acquire();
    System.out.println("Acquired lock");

    write(clientOut, "Hi");
    assertRead(serverIn, "Hi");

    write(serverOut, "Hello");
    assertRead(clientIn, "Hello");

    printWrite(clientOut, "Test printWrite");
    assertRead(serverIn, "Test printWrite");

    printWrite(serverOut, "Test printWrite again");
    assertRead(clientIn, "Test printWrite again");

    client.close();
    server.close();
  }

  /**
   * Writes to an OutputStream. Used for both server and client output streams.
   */
  private void write(OutputStream out, String str) throws IOException {
    out.write(str.getBytes());
    out.flush();
  }

  /**
   * Writes to an OutputStream. Used for both server and client output streams.
   */
  private void printWrite(OutputStream out, String str) throws IOException {
    PrintWriter pw = new PrintWriter(out);
    pw.print(str);
    pw.flush();
  }

  /**
   * Reads from an InputStream. Used for both server and client input streams.
   */
  private void assertRead(InputStream in, String expected) throws IOException {
    assertEquals("Too few bytes available for reading: ", expected.length(), in.available());

    byte[] buf = new byte[expected.length()];
    in.read(buf);
    assertEquals(expected, new String(buf));
  }

  /**
   * Listens for and accepts one incoming request server side on a separate
   * thread. When a request is received, grabs its IO streams and "signals" to
   * the client side above through the shared lock object.
   */
  private void listen(ServerSocket server) {
    new Thread(() -> {
      try {
        Socket socket = server.accept();
        System.out.println("Incoming connection: " + socket);

        serverOut = socket.getOutputStream();
        serverIn = socket.getInputStream();

        lock.release();
        System.out.println("Released lock");
      } catch (IOException e) {
        e.printStackTrace();
      }
    }).start();
  }
}