Java使用多线程进行群聊大体思路

开发工具与关键技术:MyEclipse 10、JAVA
作者:曾浩源
撰写时间:2019年05月27日

多人进行群聊,肯定分服务端和客户端。(这里是java的窗体应用JFrame)

一、群聊服务端怎么写,有什么作用。

群聊服务端相当于一个中转站,负责不断的接收客户端的请求,然后经过指定的处理,最后为所有客户转发信息(也就是响应)
以下所有变量皆为成员变量,在这里写成员变量

(1)启动服务器
1、创建在线用户集合,用于显示所有的在线用户
	ArrayList<ClientThread> clients = new ArrayList<ClientThread>();
2、创建服务器
	ServerSocket serverSocket = new ServerSocket(port);
3、创建线程
	Thread serverThread = new ServerThread(serverSocket, max);
4、启动线程
	serverThread.start();
5、设置服务器启动标识
	boolean isStart = true;
6、启动服务器的公告消息(聊天室)
	contentArea.append("服务器已成功启动!人数上限:" + max + ",端口:" + port + "\r\n");
7、给服务器的账号状态设置为在线
	Serveruser.setState(true);
(2)服务器线程
继承线程,重写run方法
	class ServerThread extends Thread 
1、使用死循环,不断接收客户端请求登录
	while (TheradServer) 
2、侦听并接收此套接字的连接,如果没有客户端连接那么会阻塞

socket = serverSocket.accept();

3、设置对此客户服务的线程
	ClientThread client = new ClientThread(socket);
	client.start();// 开启对此客户端服务的线程
	clients.add(client);// 添加进用户集合
	listModel.addElement(client.getUser().getUsername());// 更新在线列表
	// 在公告聊天面板 写入某某上线
	contentArea.append(client.getUser().getUsername() + client.getUser().getIp() + 		"上线!\r\n");
(3)为客户(客户发送消息) 服务的线程
编写构造器和重写run方法
class ClientThread extends Thread 		
1、带参数Socket客户端套接字的构造器
		// 给类的socket赋值
		this.socket = socket;
		// 设置线程启动的标识,用于关闭线程
		this.clientServer = true;
		// 获取用户信息
		reader = new BufferedReader(new InputStreamReader(
						socket.getInputStream()));
		writer = new PrintWriter(socket.getOutputStream());
		// 接收客户端的基本用户信息
		String inf = reader.readLine();
		// 根据传过来的信息,使用@进行分割(信息看客户端)
		StringTokenizer st = new StringTokenizer(inf, "@");
		// 使用User实体类的构造器传参对其进行 赋值
		user = new User(st.nextToken(), st.nextToken());
		// 反馈连接成功信息
		writer.println(user.getUsername() + user.getIp() + "与服务器连接成功!");
		writer.flush();
		// 反馈当前在线用户信息
		if (clients.size() > 0) {
			String temp = "";
			// 遍历用户集合
			for (int i = clients.size() - 1; i >= 0; i--) {
				// 获取用户信息
				temp += (clients.get(i).getUser().getUsername() + "/" + clients
						.get(i).getUser().getIp()) + "@";
			}
			// 写入流
			writer.println("USERLIST@" + clients.size() + "@" + temp);
			writer.flush();
		}
		// 向所有在线用户发送该用户上线命令 根据@XXX传参到客户端,执行事件
		for (int i = clients.size() - 1; i >= 0; i--) {
			clients.get(i).getWriter().println("ADD@" + user.getUsername() + 					user.getIp());
			clients.get(i).getWriter().flush();
	}
2、重写run方法
//用于存放客户端传来的消息
	String message = null;
	while (clientServer) {// 死循环接收客户端的请求
		try {
			// 接收客户端的基本用户信息
			message = reader.readLine();
			if (message == null) {
				continue;//为空跳出本次循环
			}
			if (message.equals("CLOSE"))// 下线命令
			{
				contentArea.append(this.getUser().getUsername()
						+ this.getUser().getIp() + "下线!\r\n");
				// 断开连接释放资源
				reader.close();
				writer.close();
				socket.close();
				// 向所有在线用户发送该用户的下线命令
				for (int i = clients.size() - 1; i >= 0; i--) {
					clients.get(i).getWriter()
							.println("DELETE@" + user.getUsername());
					clients.get(i).getWriter().flush();
				}
				listModel.removeElement(user.getUsername());// 更新在线列表
				// 删除此条客户端服务线程
				for (int i = clients.size() - 1; i >= 0; i--) {
					if (clients.get(i).getUser() == user) {
						ClientThread temp = clients.get(i);
						clients.remove(i);// 删除此用户的服务线程
						clientServer = false;
						return;
					}
				}
			} else {
				// 转发消息 代码跳转到dispatcherMessage继续执行
				dispatcherMessage(message);
		}
3、转发消息方法
	// 转发消息
	public void dispatcherMessage(String message) {
	// 根据传过来的信息,使用@进行分割(信息看客户端) 例如:姓名@ALL(执行方案)@消息内容
		StringTokenizer stringTokenizer = new StringTokenizer(message, "@");
		// 获取分割开后的下一个source来源 并标记(来自谁 即:姓名) 仙某人
		String source = stringTokenizer.nextToken();
		// 获取标记的下一个 owner 业务 并标记(执行方案)
		String owner = stringTokenizer.nextToken();
		// 获取标记的下一个 content 内容 并标记
		String content = stringTokenizer.nextToken();
		// 消息为:仙某人说:你好!
		message = source + "说:" + content;
		// 在服务器公告消息(聊天室)输出
		contentArea.append(message + "\r\n");
		if (owner.equals("ALL")) {// 群发 客户端发送过来的业务(执行方案)之后的私聊也可以实现了
			for (int i = clients.size() - 1; i >= 0; i--) {
				// 遍历所有用户,将信息写入流
				clients.get(i).getWriter().println(message);
				clients.get(i).getWriter().flush();
			}
		}
	}

二、客户端

(1)连接服务端
1、根据端口号和服务器ip建立连接
	socket = new Socket(user.getIp(), Constant.SERVER_PORT);
2、写入连接信息
	writer = new PrintWriter(socket.getOutputStream());
3、读取连接信息
	reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
4、发送客户端用户基本信息(用户名和ip地址)
	sendMessage(user.getUsername() + "@" + socket.getLocalAddress().toString());
5、开启接收消息的线程
	messageThread = new MessageThread(reader, textArea);
6、启用接受信息线程
	messageThread.start();
7、设置窗体标题为用户名称
	frame.setTitle(user.getUsername());
	isConnected = true;// 已经连接上了
8、设置用户登录状态
	user.setState(true);
(2)客户端接收服务端信息线程
	class MessageThread extends Thread
1、接收消息线程的构造方法
	public MessageThread(BufferedReader reader, JTextArea textArea) {
		// 获取读取到的客户连接信息 和 文本域(到时候需要使用它进行在聊天室	输出【它是私有的】)
		this.reader = reader;
		this.textArea = textArea;
		ClientThread = true;
	}
2、重写run方法
public void run() {
	String message = "";
	while (ClientThread) {
		try {
			// 读取接受到的信息
			message = reader.readLine();
			// 根据传过来的信息,使用@进行分割( 例如:姓名@ALL(执行方案)@消息内容
			if (message != null) {
				StringTokenizer stringTokenizer = new StringTokenizer(message, "/@");
				// 获取到命令 即:业务(执行方案)
				String command = stringTokenizer.nextToken();
				if (command.equals("CLOSE"))// 服务器已关闭命令
				{
					ClientThread = false;
					textArea.append("系统公告:服务器已关闭!\r\n");
					closeCon();// 被动的关闭连接
					return;// 结束线程
				} 
				//有用户上线更新在线列表
				else if (command.equals("ADD")) {
					String username = "";
					String userIp = "";
					// 判断是否为空并获取用户名,IP地址进行赋值
					if ((username = stringTokenizer.nextToken()) != null
							&& (userIp = stringTokenizer.nextToken()) != null) {
						User user = new User(username, userIp);
						onLineUsers.put(username, user);
						listModel.addElement(username);
						textArea.append("系统公告:" + username + "上	 线!\r\n");
					}
				} 
				// 有用户下线更新在线列表
				else if (command.equals("DELETE")) {
					String username = stringTokenizer.nextToken();
					User user = (User) onLineUsers.get(username);
					textArea.append("系统公告:" + username + "下线!\r\n");
					onLineUsers.remove(user);
					listModel.removeElement(username);
				}
				// 加载在线用户列表
				else if (command.equals("USERLIST")) {
					int size = Integer.parseInt(stringTokenizer
								.nextToken());
						String username = null;
						String userIp = null;
						for (int i = 0; i < size; i++) {
							username = stringTokenizer.nextToken();
							userIp = stringTokenizer.nextToken();
							User user = new User(username, userIp);
							onLineUsers.put(username, user);
							listModel.addElement(username);
					}
				} else {// 普通消息
					textArea.append(message + "\r\n");
				}
			else {
				continue;
			}
		} catch (IOException e) {
			e.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
(3)、客户端群发请求

可以封装一个send()方法,将信息发送到服务器

	// !!!!!群发请求!!!!!!
	writer.println(frame.getTitle() + "@" + "ALL" + "@" + message);
	writer.flush();

样式:
在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值