Simple chat using sockets in Java
Saverio683
Posted on December 6, 2022
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:
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:
🔎 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.
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
November 29, 2024