Java Socket实现多线程聊天室:代码解析与运行流程

拾柒
拾柒
发布时间:2026-10-16 | 4人浏览过
原创

本文详细介绍如何使用Java原生Socket类实现一个简单的多线程聊天室。通过服务端监听端口并利用CopyOnWriteArrayList管理多个客户端连接通道(Channel),配合独立的线程处理读写操作,实现了群聊、私聊及系统通知等功能。

整体架构设计

本聊天室基于TCP协议构建,核心依赖Java的java.net.Socket和ServerSocket类。为了实现多用户并发通信,服务端采用“一客户端一通道”的设计模式,并使用多线程技术隔离每个用户的读写逻辑。

通信机制主要依靠数据流进行封装:
- 发送端:使用DataOutputStream.writeUTF()写入字符串数据。
- 接收端:使用DataInputStream.readUTF()读取数据。
这种机制天然处理了字节流的定界问题,简化了文本传输的实现。

准备工作与工具类

在进行具体的业务逻辑开发前,我们需要定义一个资源释放的工具类,确保在程序关闭或异常发生时,Socket和输入输出流能够正确断开连接,避免内存泄漏。

/**
 * @类名 CloseUtils
 * @描述 TODO 工具类,统一释放资源
 */
public class CloseUtils {
    public static void close(Closeable... targets ) {
        for(Closeable target:targets) {
            try {
                if(null!=target) {
                    target.close();
                }
            }catch(Exception e) {
                // 静默处理或记录日志
            }
        }
    }
}

第一步:创建客户端(Client)

客户端的主要职责是发起连接请求,并开辟两个独立的线程分别负责消息的输入(发送)和消息的输出(接收)。由于Socket通信中读和写互不阻塞,必须采用多线程模型。

第1步:建立Socket连接与初始化线程

  • 入口:main方法。
  • 动作:
    1. 获取用户控制台输入的昵称。
    2. 实例化Socket连接本地地址localhost和端口8888。
    3. 启动一个Send线程用于读取控制台输入并发送给服务器。
    4. 启动一个Receive线程用于从服务器流中读取数据并打印。
public class Client {
   	public static void main(String[] args) throws UnknownHostException, IOException {
   		System.out.println("-----Client-----");
   		BufferedReader br =new BufferedReader(new InputStreamReader(System.in));
   		System.out.println("请输入用户名:");
   		String name =br.readLine();
   		// 1、建立连接: 使用Socket创建客户端 +服务的地址和端口
   		Socket client =new Socket("localhost",8888);
   		// 2、开启多线程分别处理收发
   		new Thread(new Send(client,name)).start();  
   		new Thread(new Receive(client)).start();
   	}
}

第2步:实现发送端(Send Runnable)

  • 参数:包含目标client、name及控制台缓冲对象。
  • 完成状态:进入死循环等待键盘输入,一旦获得非空消息,便通过writeUTF发送至服务端并刷新缓冲区。
public class Send implements Runnable {
	private BufferedReader console ;
	private DataOutputStream dos;
	private Socket client;
	private boolean isRunning;
	private String name;
	
	public Send(Socket client,String name) {
		this.client =client;
		console =new BufferedReader(new InputStreamReader(System.in));
		this.isRunning = true;
		this.name = name;
		try {
			dos =new DataOutputStream(client.getOutputStream());
			// 连接成功后先发送名称告知服务器
			send(name);
		} catch (IOException e) {
			release();
		}	  
	}
	
	@Override
	public void run() {
		while(isRunning) {
			String msg = getStrFromConsole();
			if(!msg.equals("")) {
				send(msg);
			}
		}
	}	  
	
	private void send(String msg) {
		try {
			dos.writeUTF(msg);
			dos.flush();
		} catch (IOException e) {
			release();
		}
	}
	
	private String getStrFromConsole() {
		try {
			return  console.readLine();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return "";
	}
	
	private void release() {
		this.isRunning = false;
		CloseUtils.close(dos,client);
	}
}

第3步:实现接收端(Receive Runnable)

  • 动作:在run方法中持续调用readUTF()。如果抛出异常说明连接断开,否则将收到的消息打印到控制台。
public class Receive implements Runnable {
	private DataInputStream dis ;
	private Socket client;
	private boolean isRunning;
	
	public Receive(Socket client) {
		this.client = client;
		this.isRunning = true;
		try {
			dis =new DataInputStream(client.getInputStream());
		} catch (IOException e) {
			release();
		}
	}
	
	private String receive() {
		String msg ="";
		try {
			msg =dis.readUTF();
		} catch (IOException e) {
			release();
		}
		return msg;
	}
	
	@Override
	public void run() {   	  
		while(isRunning) {
			String msg =receive();
			if(!msg.equals("")) {
				System.out.println(msg);
			}
		}
	}
	
	private void release() {
		this.isRunning = false;
		CloseUtils.close(dis,client);
	}
}

第二步:创建服务端(Chat Server)

服务端是整个系统的枢纽。它使用ServerSocket在指定端口监听请求。当有新的客户端接入时,服务端并不直接处理其业务逻辑,而是将其封装为一个Channel对象并加入列表管理,随后启动该Channel对应的线程。

线程安全容器:
考虑到多个线程需要同时访问“当前所有在线用户列表”,我们选用java.util.concurrent.CopyOnWriteArrayList。它是一种线程安全的可变列表,适用于读多写少或并发访问的场景。

public class Chat {

	// 使用线程安全的动态数组存放所有在线通道
	private static CopyOnWriteArrayList<Channel> all =new CopyOnWriteArrayList<Channel>();

	public static void main(String[] args) throws IOException {
		System.out.println("-----Server-----");
		// 1、指定端口 使用ServerSocket创建服务器
		ServerSocket server =new ServerSocket(8888);
		// 2、主线程无限循环阻塞等待连接
		while(true) {
					Socket  client =server.accept(); 	
					System.out.println("一个客户端建立了连接");
					// 为每个连接创建一个专门的Channel处理
					Channel c =new Channel(client);
					all.add(c); // 管理所有的成员
					new Thread(c).start();   		
				}  	  
		}
	}
	// 内部静态类:代表一个独立的通信通道
	static class Channel implements Runnable{
		// ... (此处省略部分属性与构造方法见下文分析)
	}
}

第三步:Channel通道的逻辑实现

Channel类是实现核心通信逻辑的地方。每个Channel实例拥有自己的输入输出流。

1. 初始化与身份注册

当Channel被构造函数激活时:
- 获取客户端发来的第一帧数据(即用户名)。
- 向该客户端发送欢迎语。
- 遍历all列表,将“XXX加入了聊天室”的系统消息群发给其他人。

2. 消息分发策略(群聊与私聊)

服务端接收到某用户的消息后,通过sendOthers方法判断转发规则:
- 私聊规则:如果消息以@开头(格式如@targetName:msg),解析出目标用户名,仅在all列表中查找匹配者,并只将该消息发送给目标用户。
- 群聊规则:如果不是私聊,则遍历列表排除自己,将所有普通消息转发给其余在线用户。

	private void sendOthers(String msg,boolean isSys) {
		boolean isPrivate = msg.startsWith("@");
		if(isPrivate) { // 私聊处理
			int idx =msg.indexOf(":");
			String targetName = msg.substring(1,idx);
			msg = msg.substring(idx+1);
			for(Channel other: all) {
				if(other.name.equals(targetName)) {// 找到目标
					other.send(this.name +"悄悄地对您说:"+msg);
					break;
				}
			}
		}else {   		  
			for(Channel other: all) {
				if(other==this) continue; // 跳过自己
				if(!isSys) {
					other.send(this.name +"对所有人说:"+msg);
				}else {
					other.send(msg); // 系统消息
				}
			}
		}
	}

3. 退出处理

当某通道发生IO异常或手动关闭时,触发release()方法:
- 将自身从all列表中移除。
- 向剩余用户广播“XXX离开大家庭”的消息。
- 关闭自身的dis, dos及client。

效果演示与验证

按以下步骤运行即可看到完整效果:

第4步:启动服务端

编译并运行Chat.java(或主类Chat)。控制台应显示“-----Server-----”及“一个客户端建立了连接”等日志。此时服务端处于accept阻塞等待状态。

第5步:并行启动多个客户端

依次编译并运行Client.java至少两次,在每次运行时输入不同的用户名(例如:Alice, Bob)。

第6步:执行测试交互

  1. 查看入群通知:新加入的Bob控制台上会收到“Alice来了XX聊天室”的通知;同时Alice的控制台也会收到“Bob来了XX聊天室”的提示。
  2. 群发消息:Alice在控制台输入文字,Bob及其他人都能立刻在各自的屏幕上看到带有前缀的群聊消息。
  3. 私聊测试:Alice尝试输入@Bob:Secret Message。只有Bob能在其屏幕上看到“Alice悄悄地对您说:Secret Message”。而其他人不会受到影响。

总结

通过这个案例,我们可以清晰看到Java Socket网络编程的基础结构。利用ServerSocket监听、Socket通信以及Thread调度,就能搭建出一个基本的即时通讯框架。虽然在实际生产环境中我们会选择Netty等高性能框架来处理复杂的并发与编解码问题,但理解这一底层原语对于掌握分布式通信原理至关重要。

以上就是Java Socket实现简单聊天室功能的详细内容,更多关于Java Socket教程的资料请关注本站其它相关文章!

本站声明

本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。

Python 相关产品和应用

全部下载

专题列表