Implementing a Real-Time Network Chatroom in Java Using TCP and Multithreading
This article explains how to build a real‑time network chatroom in Java using TCP sockets, multithreading, and a ConcurrentHashMap to manage user connections, detailing command handling for listing users, group chat, private messages, exiting, and server shutdown, with full source code examples.
The article demonstrates how to create a TCP‑based real‑time chatroom using a client‑server (C/S) architecture in Java. It first discusses the need for a data structure to store online users' sockets and usernames, choosing Map (specifically ConcurrentHashMap<Socket, String> ) for thread‑safe access.
To ensure thread safety, the author recommends using collections from java.util.concurrent , such as ConcurrentHashMap and CopyOnWriteArraySet , instead of non‑thread‑safe HashMap or HashSet . The article also mentions the JDK 1.8 optimization where a HashMap converts to a red‑black tree after a certain threshold.
The core group‑chat method sendToMembers iterates over the users map and forwards a message to every socket except the sender:
private void sendToMembers(String msg,String hostAddress,Socket mySocket) throws IOException{
PrintWriter pw;
OutputStream out;
Iterator iterator=users.entrySet().iterator();
while (iterator.hasNext()){
Map.Entry entry=(Map.Entry) iterator.next();
Socket tempSocket = (Socket) entry.getKey();
String name = (String) entry.getValue();
if (!tempSocket.equals(mySocket)){
out=tempSocket.getOutputStream();
pw=new PrintWriter(new OutputStreamWriter(out,"utf-8"),true);
pw.println(hostAddress+":"+msg);
}
}
}A similar method sendToOne handles private messages by locating the target socket in the map and sending the message only to that client:
private void sendToOne(String msg,String hostAddress,Socket another) throws IOException{
PrintWriter pw;
OutputStream out;
Iterator iterator=users.entrySet().iterator();
while (iterator.hasNext()){
Map.Entry entry=(Map.Entry) iterator.next();
Socket tempSocket = (Socket) entry.getKey();
String name = (String) entry.getValue();
if (tempSocket.equals(another)){
out=tempSocket.getOutputStream();
pw=new PrintWriter(new OutputStreamWriter(out,"utf-8"),true);
pw.println(hostAddress+"私信了你:"+msg);
}
}
}The server stores online users with:
private ConcurrentHashMap
users = new ConcurrentHashMap();When a client connects, the server prompts for a unique username, checks for duplicates, and registers the socket‑username pair:
pw.println("From 服务器:欢迎使用服务!");
pw.println("请输入用户名:");
String localName = null;
while ((hostName=br.readLine())!=null){
users.forEach((k,v)->{ if (v.equals(hostName)) flag=true; });
if (!flag){
localName=hostName;
users.put(socket,hostName);
flag=false;
break;
} else {
flag=false;
pw.println("该用户名已存在,请修改!");
}
}Command handling includes:
L – list online users:
if (msg.trim().equalsIgnoreCase("L")){
users.forEach((k,v)->{ pw.println("用户:"+v); });
continue;
}G – enter group chat, broadcasting messages until E is received.
else if (msg.trim().equals("G")){
pw.println("您已进入群聊。");
while ((msg=br.readLine())!=null){
if (!msg.equals("E") && users.size()!=1)
sendToMembers(msg,localName,socket);
else if (users.size()==1){
pw.println("当前群聊无其他用户在线,已自动退出!");
break;
} else {
pw.println("您已退出群组聊天室!");
break;
}
}
}O – private chat, prompting for the recipient's username and then sending messages via sendToOne .
else if (msg.trim().equalsIgnoreCase("O")){
pw.println("请输入私信人的用户名:");
String name=br.readLine();
// find socket
users.forEach((k,v)->{ if (v.equals(name)) isExist=true; });
Socket temp=null;
for (Map.Entry
mapEntry : users.entrySet()){
if (mapEntry.getValue().equals(name)) temp = mapEntry.getKey();
}
if (isExist){
isExist=false;
while ((msg=br.readLine())!=null){
if (!msg.equals("E") && !isLeaved(temp))
sendToOne(msg,localName,temp);
else if (isLeaved(temp)){
pw.println("对方已经离开,已断开连接!");
break;
} else {
pw.println("您已退出私信模式!");
break;
}
}
} else {
pw.println("用户不存在!");
}
}A helper method determines whether a socket has already left:
private Boolean isLeaved(Socket temp){
Boolean leave=true;
for (Map.Entry
mapEntry : users.entrySet()) {
if (mapEntry.getKey().equals(temp))
leave=false;
}
return leave;
}The complete server logic is encapsulated in the Handler class, which manages the connection lifecycle, command parsing, and clean‑up:
class Handler implements Runnable {
private Socket socket;
public Handler(Socket socket) { this.socket = socket; }
public void run() {
System.out.println("New connection accept:" + socket.getInetAddress().getHostAddress());
try {
BufferedReader br = getReader(socket);
PrintWriter pw = getWriter(socket);
// ... (login, command loop as described above) ...
} catch (IOException e) { e.printStackTrace(); }
finally { try { if (socket != null) socket.close(); } catch (IOException e) { e.printStackTrace(); } }
}
}The article concludes with a demonstration screenshot showing multiple clients communicating simultaneously, confirming that the implementation supports real‑time group and private messaging, and encourages readers to explore and extend the functionality.
Java Captain
Focused on Java technologies: SSM, the Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading; occasionally covers DevOps tools like Jenkins, Nexus, Docker, ELK; shares practical tech insights and is dedicated to full‑stack Java development.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.