Simple chat using sockets in Java
Published: 2022-12-06 18:01:16

Hello everyone 👋, I hope you are doing well.
In this post I'll talk about how to use sockets in Java.

#📘 INTRODUCTION

In order to make it possible to exchange messages between two nodes, we make use of **sockets**.
In simple terms, a socket is the pairing of an *IP* and a *network port* where packets will be sent/received by hosts.

Sockets are divided into 2 groups, based on the transport protocol implemented: *tcp sockets* and *udp sockets*.

In short, **TCP** (Transmission Control Protocol) is a connection-oriented protocol that ensures that packets are sent correctly, which is useful for **one-to-one connections**.
On the other hand, **UDP** (User Datagram Protocol) is a non-connection-oriented protocol used when it is acceptable to lose a few packets in favor of speed (such as in video games or streaming platforms), useful for **one-to-many connections**.
 
In Java there are classes that implement both types of sockets.

#SOCKET and SERVERSOCKET

In a basic TCP connection we need a **server** (ServerSocket class) and a **client** (Socket class).
In the example below, I have implemented a simple network where the server is constantly listening to client requests and assigning it a thread (so as to allow multi-user):

```
package test;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class TCPServer {
	static final int PORT = 1234;
	
	public static void main(String args[]) {	    
	    System.out.println("Server listening on port "+PORT+"...");
	    
	    try {
	    	ServerSocket server = new ServerSocket(PORT);  	 
	    	
		    while(true) {//listening for client connections
		        try {
		        	Socket socket = server.accept();//Listens for a connection to be made to this socket and accepts it. The method blocks until a connection is made.
		        	
		        	ServerThread st = new ServerThread(socket);//assigning a thread to communicate with the client
		        	st.start();
		        	
		            System.out.println("Connection established, thread assigned: "+st.getName());
		        }
			    catch(Exception e) {
			        e.printStackTrace();
			        System.out.println("Connection error");
			        server.close();	      
			    }
		    }
	    }
	    catch(IOException e) {
		    e.printStackTrace();
		    System.out.println("Server error");
	    }
	}	
}
```

The thread that will be assigned to connect with that client will simply respond to its messages:

```
package test;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

public class ServerThread extends Thread {
	private Socket socket;
	
	public ServerThread(Socket s) {
		super();
		this.socket = s;
	}
		
 	//override of the run method
	public void run() {
    	try {         
            BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));//for reading client messages
            PrintWriter pw = new PrintWriter(socket.getOutputStream(), true);//to send messages to client

            while(true) {
                String incoming = br.readLine();//listening for the client messages    
                
                pw.println("You sent me this: "+incoming);//responding to the client
            }  
        } catch (IOException e) {
        	System.out.println(this.getName()+": connection ended");
        } 
    }
}
```

On the client instead there will be 2 threads, one writing and one reading, so the user can send multiple messages while waiting for the response:

```
package test;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;

public class TCPClient {
	static final int PORT = 1234;
	
	public static void main(String[] args) throws UnknownHostException, IOException {
		Socket socket = new Socket(InetAddress.getLocalHost(), PORT);//creates the socket, then connects to the server
		
        BufferedReader serverReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));//for reading server messages
        BufferedReader userReader = new BufferedReader(new InputStreamReader(System.in));//for reading user inputs
        PrintWriter pw = new PrintWriter(socket.getOutputStream(), true);//to send messages to the server
        
        ClientReader cr = new ClientReader(serverReader);
        ClientWriter cw = new ClientWriter(pw, userReader);
        
        cr.start();
        cw.start();
	}
}
```
*Write thread*:
```
package test;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.PrintWriter;

public class ClientWriter extends Thread {
	private PrintWriter pw;
	private BufferedReader stdin;
	
	public ClientWriter(PrintWriter pw, BufferedReader stdin) {
		this.pw = pw;
		this.stdin = stdin;
	}
	
	public void run() {
		
        while (true) {//l'utente invia messaggi finchè la comunicazione col server non si interrompe        	
        	try {
        		String clientInput = stdin.readLine();//reading user input
        		
        		pw.println(clientInput);//sending user input to the server
        	} catch(IOException e) {
                System.out.println("Client i/o error");
            }
        }
	}
}
```

*Read thread*:
```
package test;

import java.io.BufferedReader;
import java.io.IOException;

public class ClientReader extends Thread {
	private BufferedReader stdin;
	
	public ClientReader(BufferedReader stdin) {
		this.stdin = stdin;
	}
	
	public void run() {
		
        while (true) {
        	try {
        		String incoming = stdin.readLine();//reading server messages
        		
        		System.out.println(incoming);
        	} catch(IOException e) {
                System.out.println("Client i/o error");
            }
        }
	}
}
```
In this example of communication, the client can only communicate with the server and not with other clients.

*Here is an example from the console*:

![Server](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6d4r3cdtpv82x46zvub0.png)

![Client](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vt13bn8mjbumqm1uy77o.png)
 
#DATAGRAMSOCKET and MULTICASTSOCKET

On Java there is the **DatagramSocket** class aimed at implementing this type of socket. Messages are exchanged in the form of bytes through datagram packets, implemented with the **DatagramPacket** class, through the *send* and *receive* functions.

The one below is an example of a broadcast connection where each host can send/receive messages to/from **all** hosts on the network, without passing through a server:

```
package udp;

import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketException;
import java.net.UnknownHostException;

public class UDPHost {
	static final int PORT = 8290;
	
	public static void main(String[] args) throws SocketException, UnknownHostException {
		DatagramSocket senderSocket = new DatagramSocket();
		senderSocket.setBroadcast(true);
		
		DatagramSocket receiveSocket = new DatagramSocket(null);//all read sockets will be listening on the same port
		receiveSocket.setReuseAddress(true);
		receiveSocket.bind(new InetSocketAddress(PORT));
		
		UDPReader r = new UDPReader(receiveSocket);
		UDPWriter w = new UDPWriter(senderSocket, PORT);
		
		r.start();
		w.start();
	}
}
```
In the code above, we need to invoke the **setBroadcast** method to let the operating system know that the package will be broadcasted.

Now, to send a broadcast message it is not necessary to know the ip of the destination target; rather, you only need to send to the **broadcast address** of the **network interface**, which will take care of sending the packet then to the hosts on it.

*Write thread*:

```
package udp;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.NetworkInterface;
import java.nio.charset.StandardCharsets;
import java.util.Enumeration;
import java.util.Objects;

public class UDPWriter extends Thread {
	private DatagramSocket socket;
	private int port;
	
	public UDPWriter(DatagramSocket s, int p) {
		super();		
		this.socket = s;
		this.port = p;
	}
	
	public void run() {
		try {	
			BufferedReader userReader = new BufferedReader(new InputStreamReader(System.in));//reading user inputs
			
			while(true) {		
				byte[] sendData = userReader.readLine().getBytes(StandardCharsets.UTF_8);//saves the user input to the buffer
				broadcast(sendData);//sends the packet to all sockets on the network
			} 
		} catch (IOException e) {
			System.out.println(this.getName()+": error sending message");
		}
	}
	
    public void broadcast(byte[] buffer) throws IOException {
    	Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
        while(interfaces.hasMoreElements()) {//iterates all network interfaces on the pc to get the ips of the other hosts where to send the broadcast message
            NetworkInterface networkInterface = interfaces.nextElement();

            if (networkInterface.isUp() || !networkInterface.isLoopback()) {//verifies the integrity of the network interface
                networkInterface.getInterfaceAddresses().stream() 
	                .map(interfaceAddress -> interfaceAddress.getBroadcast())
	                .filter(Objects::nonNull)
	                .forEach(ip -> {//iterate over the ips
						try {
							socket.send(new DatagramPacket(buffer, buffer.length, ip, port));//sending the packet
						} catch (IOException e) {
							System.out.println(this.getName()+": error sending message");
						}	
					});
            }
        }
   }
}
```

*Read thread*:

```
package udp;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class UDPReader extends Thread {
	private DatagramSocket socket;
	
	public UDPReader(DatagramSocket s) {
		super();
		this.socket = s;
	}
	
	public void run() {		
		while(true) {			
			try {
				byte[] receiveData = new byte[1024];
				DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);//messages travel as bytes through datagram packets
				socket.receive(receivePacket);//The socket remains listening for the datagram packet and then saves it to the buffer
				
				String incoming = new String(receiveData);
				System.out.println(incoming);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
}
```

Broadcasting messages, however, is often inefficient because packets do not arrive only at the desired hosts but rather at all of them.

To remedy this, there are **multicast sockets** that allow a **one-to-many** connection instead of one-to-all.
The concepts are the same as for datagram sockets. In fact, the MulticastSocket class is nothing more than an extension of the DatagramSocket class; the packets sent are implemented with the DatagramPacket class.

It is possible to send packets only to the desired hosts due to **multicast groups**: a portion of network traffic, associated only with sockets joining it, that is identified by a *multicast address* (any address between 224.0.0.0 to 239.255.255.255).
Sockets can *join* and *leave* groups via the same-named functions.
To send packets, the *send* function is already implemented since you pass the name of the network card in the joinGroup method.

Here an example:

```
package multicast;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MulticastSocket;
import java.net.NetworkInterface;

public class MulticastHost {
	static final int PORT = 1234; 
	
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));//reading user inputs
		MulticastSocket socket = new MulticastSocket(PORT);
		InetAddress ip = null;
		
		boolean wrongUserInput = true;
		System.out.println("Select a room: a) gossib b) brainstorming c) soccer");
		
		while(wrongUserInput) {
			wrongUserInput = false;			
			
			switch(br.readLine()) {
				case "a":
					ip = InetAddress.getByName("230.0.0.1");
					break;
				case "b":
					ip = InetAddress.getByName("230.0.0.2");
					break;
				case "c":
					ip = InetAddress.getByName("230.0.0.3");					
					break;
				default:
					System.out.println("wrong room entered, please retry");
					wrongUserInput = true;
					break;
			}
		}
		
		InetSocketAddress address = new InetSocketAddress(ip, PORT);
		NetworkInterface netIf = NetworkInterface.getByName("wlo1");//obtained by typing from terminal the command "ifconfig" (linux)
		
		socket.joinGroup(address, netIf);
		socket.setBroadcast(true);
		
		MulticastWriter w = new MulticastWriter(socket, br, ip);
		MulticastReader r = new MulticastReader(socket, ip);
		
		w.start();
		r.start();
		
	}
}
```

*Write thread*:

```
package multicast;

import java.io.BufferedReader;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.nio.charset.StandardCharsets;

public class MulticastWriter extends Thread {
	private MulticastSocket socket;
	private BufferedReader br;
	private InetAddress ip;
	
	public MulticastWriter(MulticastSocket s, BufferedReader b, InetAddress i) {
		this.socket = s;
		this.br = b;
		this.ip = i;
	}
	
    private void send(String msg) throws IOException {   
        
		byte[] msgBytes = msg.getBytes(StandardCharsets.UTF_8);
		
		DatagramPacket ds = new DatagramPacket(msgBytes, msgBytes.length, ip, socket.getLocalPort());
		socket.send(ds);	
    }
	
	public void run() {				
		while(true) {
			try {
				String userInput = br.readLine();
				send(userInput);//sending user message to other clients in the same group
			} catch (IOException e) {
				System.out.println(this.getName()+": error sending message");
			}
		}
	}
}
```

*Read thread*:

```
package multicast;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;

public class MulticastReader extends Thread {
	private MulticastSocket socket;
	private InetAddress ip;
	
	public MulticastReader(MulticastSocket s, InetAddress i) {
		this.socket = s;
		this.ip = i;
	}
	
    private void listen() throws IOException {		
		byte[] buf = new byte[1024];
		DatagramPacket recv = new DatagramPacket(buf, buf.length, ip, socket.getLocalPort());
		socket.receive(recv);
		 
		String incoming = new String(buf);
		System.out.println(incoming);
    }
	
	public void run() {
		while(true) {
			try {
				listen();
			} catch (IOException e) {
				System.out.println(this.getName()+": error listening incoming messages");
			}
		}
	}
}
```

*Here is an example from the console:*

![Screenshot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8c5ilqobbm3wy5pddf75.png)

![Screenshot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/64q66s2czxsfaxgf98fi.png)

![Screenshot](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/np27jxgxadesdsuutc6o.png)

#🔎 CONCLUSIONS

Thank you for reading 🙂! I hope you enjoyed the post and that it may have been helpful to you, if so please leave a like.

You can also see the code in this [github repo](https://github.com/Saverio683/Java-sockets).

You can follow me to stay updated on my new posts.