If you want two network devices to talk to each other, then developing a TCP server and client is as low as you can get without blowing your brains off. It is really helpful in many scenarios.
- Come in handy when all other application layer protocols like HTTP, FTP etc doesn’t suit your needs
- If you wish to develop your own application protocol for server/client communication
- Lets you really optimize the communication at every bit level
- It could be faster and consumes less memory as you will tailor it to your own needs and constraints
In this article we will see how we can develop our own TCP client and server.
Server
We will create a TCP endpoint and then a TCP listener that keeps listening on the endpoint. Since the program must never terminate and should keep listening, we will use an infinite while loop. As soon as we receive a new message on the listener, we process that and send the appropriate response.
// server private static ServerSocket server; //socket server port on which it will listen private static int port = 1234; public static void main(String args[]) throws IOException, ClassNotFoundException { server = new ServerSocket(port); System.out.println("Server listening on port: " + port); //keep running while (true) { Socket socket = server.accept(); InputStream reqStream = socket.getInputStream(); String message = streamToMessage(reqStream); OutputStream resStream = socket.getOutputStream(); String responseMsg = messageHandler(message); byte[] completeMsg = messageToByteArray(responseMsg); //write object to Socket resStream.write(completeMsg); //close resources reqStream.close(); resStream.close(); socket.close(); } } private static String messageHandler(String message) { System.out.println("Received message: " + message); return "Message has been received by server!"; }
Client
Let us now turn our attention to the client. The aim on the client side is to be able to send message to and get the response from the TcpListener listening on the server side. So, we need to create a TcpClient the binds to the server’s IP address and port. We get the client stream and then write to it and then wait for the response data to arrive on the stream. Check it out below in action.
// client public static void main(String[] args) { String res = sendMessage("Hello server!"); System.out.println(res); } public static String sendMessage(String message) { String responseMsg = null; try { Socket client = new Socket("localhost", 1234); byte[] messageBytes = messageToByteArray(message); OutputStream outToServer = client.getOutputStream(); DataOutputStream out = new DataOutputStream(outToServer); out.write(messageBytes, 0, messageBytes.length); InputStream inFromServer = client.getInputStream(); DataInputStream in = new DataInputStream(inFromServer); responseMsg = streamToMessage(in); client.close(); } catch (Exception ex) { System.out.println(ex.getMessage()); } return responseMsg; }
In both server and in the client side, we are yet to implement 2 methods namely:
- messageToByteArray: to convert our message string into a byte array that can be written on the network stream.
- streamToMessage: to read message from the stream and convert it to a string message.
But before we do that, we need to turn our attention to the content length of the message.
Content length
To read from the stream, we either need to read byte by byte or we can read the entire message in one go. We would prefer the later and for that we need to have a buffer. Now buffer can be fixed to a size bigger than the longest message we expect to receive. But that would be sub optimal way as it increases the reading time and thus the communication time. Therefore, we need to device a way of letting the other party know what is the message length so the other party can accordingly set the buffer size.
Since both server and client are our own, we can device our own protocol to send content length data. Let us say that the first 4 bytes (32 bits) would contain the length of the message. Thus we are setting that all messages would at least be 4 bytes in length so that the other party can be assured that they can safely read 4 bytes to get the message length. Once the message length is received, the buffer can be sized accordingly and the rest of the message stream be read. While on the sender side, the rest of the message will be appended after the first 4 bytes. Let us see this in practice by defining the 2 methods we listed above:
private static final Charset charset = StandardCharsets.UTF_8; private static byte[] messageToByteArray(String message) { byte[] messageBytes = message.getBytes(charset); int messageSize = messageBytes.length; int completeSize = messageSize + 4; byte[] completemsg = new byte[completeSize]; byte[] sizeBytes = BigInteger.valueOf(messageSize).toByteArray(); System.arraycopy(sizeBytes, 0, completemsg, 0, sizeBytes.length); System.arraycopy(messageBytes, 0, completemsg, 4, messageSize); return completemsg; } private static String streamToMessage(InputStream inStream) throws IOException { byte[] sizeBytes = new byte[4]; inStream.read(sizeBytes, 0, 4); int messageSize = new BigInteger(reverseArray(sizeBytes)).intValue(); byte[] messageBytes = new byte[messageSize]; inStream.read(messageBytes, 0, messageSize); return new String(messageBytes, charset); } private static byte[] reverseArray(byte[] arr) { byte[] newArr = new byte[arr.length]; for (int i = 0; i < arr.length / 2; i++) { newArr[i] = arr[arr.length - i - 1]; newArr[arr.length - i - 1] = arr[i]; } return newArr; }
This should be it. Implement the above 2 functions on both client and server side and both should be ready to run.
0 Comments