Concurrent Chat Application in Java

Summary

One of the most basic starters program for demonstrating the proper use of Sockets in Java.
The following code makes use of Swing components to illustrate the Client GUI side and the Server side plays the role in Console mode.

How it works?

  1. Firstly, you need to start the server.
  2. Then start Client, to get connected with the server.
  3. A thread for that particular Client is created onto the server, which is maintained by the Conversation class.
  4. Client can only start communication after setting the username.
  5. Once done, when the client sends message to server, he adds his own username to it, so other people chatting with him can identify from whom the message arrived.
  6. On the server end, whatever message coming from client are added onto the MessageQueue.
  7. Server has a MessageDispatcher whose job is to dispatch message to all other users from the MessageQueue
  8. When server echo’s the message, its being received by all the clients connected to the server, which makes it a concurrent chat server
  9. The current code server can accept upto 5 clients at once, because its being run in a for loop to iterate upto 5 times, run it for infinite times wherein the server will be able to accept more than 100 clients at once.
  10. This can also be said as Conference Chatting Application.
  11. Client’s can stop chatting by just pressing the logout button.
  12. Once client is logged out, his socket is also closed on server side.

Classes Present

  1. Client Side
    • ClientMain
  2. Server Side
    • ServerMain
    • Conversation
    • MessageDispatcher
    • MessageQueue

Screenshots

Concurrent Chat Application screenshot 1 Concurrent Chat Application screenshot 2 Concurrent Chat Application screenshot 3 Concurrent Chat Application screenshot 4

Code

ClientMain.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
package com.rohansakhale.chatapp.client;
/**
*
* @author RohanSakhale
*/
import java.awt.BorderLayout;
import javax.swing.*;
import java.awt.event.*;
import java.io.*;
import java.net.Socket;
public class ClientMain {
static JTextField tf = new JTextField(25);
static PrintWriter nos = null;
static String username;
public static void main(String[] args) throws Exception {
// Obtain server connection
final Socket soc = new Socket("127.0.0.1", 9081);
System.out.println("Client Signing on");
// Create output channel to server
// With Autoflush enabled
nos = new PrintWriter(new BufferedWriter(new OutputStreamWriter(soc.getOutputStream())), true);
// Create input channel from server
final BufferedReader nis = new BufferedReader(new InputStreamReader(soc.getInputStream()));
// Initialise Swing Components
JFrame chatFrame = new JFrame("Client Chat Window");
final JTextArea msgBox = new JTextArea(50, 50);
final JTextField tusername = new JTextField(10);
final JButton send = new JButton("SEND");
final JButton setUsername = new JButton("Set Username");
final JLabel userNameDetails = new JLabel("Enter username:");
final JButton logout = new JButton("Logout");
JPanel jUsername = new JPanel();
JPanel msgPanel = new JPanel();
msgBox.setText("Enter username to start chatting");
// By default disable chatting features
send.setEnabled(false);
msgBox.setEditable(false);
tf.setEditable(false);
logout.setVisible(false);
// Set size of frames
msgPanel.setSize(500, 100);
chatFrame.setSize(500, 400);
// Add Components on Swing container
jUsername.add(userNameDetails);
jUsername.add(tusername);
jUsername.add(setUsername);
jUsername.add(logout);
msgPanel.add(tf);
msgPanel.add(send);
chatFrame.add(msgPanel, BorderLayout.SOUTH);
chatFrame.add(msgBox, BorderLayout.CENTER);
chatFrame.add(jUsername, BorderLayout.NORTH);
// Create Listener object
MyMsgSendActionListener l = new MyMsgSendActionListener();
// Set various action listeners
send.addActionListener(l);
tf.addActionListener(l);
// Set action listener for setting username
setUsername.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (!tusername.getText().equals(") && tusername.getText().length() > 3) {
username = tusername.getText();
tusername.setEditable(false);
tusername.setEnabled(false);
tf.setEditable(true);
msgBox.setText(");
send.setEnabled(true);
setUsername.setEnabled(false);
setUsername.setVisible(false);
logout.setVisible(true);
nos.println(username + " Signed in");
} else {
JOptionPane.showMessageDialog(null, "Please enter username more than 3 letters", "Error", JOptionPane.ERROR_MESSAGE);
}
}
});
// Set Logout action
logout.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
try {
nos.println(username + " : " + "Signed out");
nos.println("end");
nos.close();
nis.close();
soc.close();
System.exit(0);
} catch (Exception ex) {
}
}
});
// Give default close operation
chatFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Set frame visible
chatFrame.setVisible(true);
// take continous input from server
try {
String str = nis.readLine();
if (str.startsWith(username + " : ")) {
str = str.replace(username + " : ", "me : ");
}
while (!str.equals("end")) {
msgBox.append(str + "\n");
str = nis.readLine();
if (str.startsWith(username + " : ")) {
str = str.replace(username + " : ", "me : ");
}
}
} catch (Exception e) {
}
}
}

MyMsgSendActionListener.java

1
2
3
4
5
6
7
8
9
10
class MyMsgSendActionListener implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
// Add username with the message
String str = ClientMain.username + " : " + ClientMain.tf.getText();
ClientMain.nos.println(str);
ClientMain.tf.setText(");
}
}

ServerMain.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.rohansakhale.chatapp.server;
/**
*
* @author RohanSakhale
*/
import java.io.*;
import java.net.*;
import java.util.*;
public class ServerMain {
static ArrayList<PrintWriter> pwl = new ArrayList<PrintWriter>();
static MessageQueue<String> mQ = new MessageQueue<String>();
public static void main(String[] args) throws Exception {
System.out.println("Server Signing on");
MessageDispatcher md = new MessageDispatcher();
md.setDaemon(true);
md.start();
ServerSocket ss = new ServerSocket(9081);
Socket soc = null;
Thread th = null;
Conversation c = null;
for (int i = 0; i < 5; i++) {
soc = ss.accept();
System.out.println("Client No. " + (i + 1) + " connected");
c = new Conversation(soc);
th = new Thread(c);
th.start();
}
}
}

Conversation.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package com.rohansakhale.chatapp.server;
import java.io.*;
import java.net.Socket;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* @author RohanSakhale
*/
public class Conversation implements Runnable {
private Socket soc;
private PrintWriter nos;
private BufferedReader nis;
public Conversation(Socket soc) throws Exception {
this.soc = soc;
this.nos = new PrintWriter(new BufferedWriter(new OutputStreamWriter(soc.getOutputStream())), true);
ServerMain.pwl.add(this.nos);
this.nis = new BufferedReader(new InputStreamReader(this.soc.getInputStream()));
}
@Override
public void run() {
try {
try {
String str = this.nis.readLine();
while (!str.equals("end")) {
ServerMain.mQ.enqueue(str);
str = this.nis.readLine();
}
} catch (Exception e) {
}
ServerMain.pwl.remove(this.soc);
} catch (Exception ex) {
Logger.getLogger(Conversation.class.getName()).log(Level.SEVERE, null, ex);
} finally {
try {
this.nis.close();
this.nos.close();
this.soc.close();
} catch (Exception e) {
}
}
}
}

MessageDispatcher.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.rohansakhale.chatapp.server;
import java.io.PrintWriter;
/**
*
* @author RohanSakhale
*/
public class MessageDispatcher extends Thread {
@Override
public void run() {
System.out.println("Message Dispatcher Started");
while (true) {
String str = ServerMain.mQ.dequeue();
for (PrintWriter pw : ServerMain.pwl) {
System.out.println("Sent Message to All");
pw.println(str);
}
}
}
}

MessageQueue.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.rohansakhale.chatapp.server;
import java.util.ArrayList;
/**
*
* @author RohanSakhale
*/
public class MessageQueue<T> {
ArrayList<T> queue = new ArrayList<T>();
public synchronized void enqueue(T msg) {
System.out.println("Enqueued the message");
this.queue.add(msg);
notify();
}
public synchronized T dequeue() {
System.out.println("Inside Dequeue");
while (this.queue.isEmpty()) {
try {
System.out.println("Inside Dequeue -- Waiting");
wait();
} catch (Exception ex) {
System.out.println("Exception occured in Dequeue");
}
}
System.out.println("Dequeue -- Completed");
return this.queue.remove(0);
}
}