Simple chat using sockets in Java

saverio683

Saverio683

Posted on December 6, 2022

Simple chat using sockets in Java

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");
        }
    }   
}
Enter fullscreen mode Exit fullscreen mode

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");
        } 
    }
}
Enter fullscreen mode Exit fullscreen mode

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();
    }
}
Enter fullscreen mode Exit fullscreen mode

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");
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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");
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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

Client

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();
    }
}
Enter fullscreen mode Exit fullscreen mode

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");
                        }   
                    });
            }
        }
   }
}
Enter fullscreen mode Exit fullscreen mode

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();
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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();

    }
}
Enter fullscreen mode Exit fullscreen mode

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");
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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");
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Here is an example from the console:

Screenshot

Screenshot

Screenshot

🔎 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.

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

💖 💪 🙅 🚩
saverio683
Saverio683

Posted on December 6, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related