JAVA多人聊天室(多线程基础聊天室可以私聊/群聊)

2023-11-17

创建一个类定义聊天的规则

package com.test.tcpmap;
/**
 * 该程序定义了聊天的规则
 * */
public interface CrazyitProtocol {
	//定义协议字符串的长度
	int PROTOCOL_LEN =2;
	//下面是一些协议字符串,服务器端和客户端交换的信息
	//都应该在前、后添加这种特殊字符串
	String MSG_ROUND ="$Y";
	String USER_ROUDN = "I#";
	String PRIVATE_ROUND="☆【";
	String NAME_REP = "-1";
	String SPLIT_SIGN ="%】";
	String LOGIN_SUCCESS="1";
}

创建Map类编写需要的方法

package com.test.tcpmap;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

import javax.management.RuntimeErrorException;
/**
 * 本类重写了map方法将value不能重复输入,此外集合是用hash值来做对比的
 * */
public class CrazyitMap<K,V> extends HashMap<K,V>{
	//根据value来删除指定项
	public void removeByValue(Object value){
		for(Object key:keySet()){
			if(get(key)==value){
				remove(key);
			}
		}
	}
	
	//获取所有value组成的Set集合
	public Set<V> valueSet(){
		Set<V> result = new HashSet<V>();
		//遍历所有key组成的集合
		for(K key : keySet()){
			//将每个key对应的value添加到result集合中
			result.add(get(key));
		}
		return result;
	}
	
	/**
	 *	根据value查找key 
	 * */
	public K getKeyByValue(V val){
		//遍历所有key组成的集合
		for(K key : keySet()){
			//如果指定key对应的value与搜索的value相同
			//则返回对应的key
			if(get(key).equals(val)&&get(key) == val){
				return key;
			}
		}
		return null;
	}
	
	//重写HashMap的put的方法,该方法不允许value重复
	public V put(K key,V value){
		//遍历所有value组成的集合
		for(V val:valueSet()){
			//如果指定value与试图放入集合的value相同、
			//则抛出一个RuntimeException异常
			if(val.equals(value) && val.hashCode()==value.hashCode()){
				throw new RuntimeErrorException(null, "Mymap实例中不允许有重复的value");
			}
		}
		return super.put(key, value);
	}
}

这个是服务端的代码

package com.test.tcpmap;

import java.io.IOException;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
/**
 * 该程序主要是创建一个全局的Map集合用于存放监听到的client用户对象,并通过创建线程来不断的转发给其他用户
 * */
public class Server {
	private static final int SERVER_PORT =30000;
	//使用CrazyitMap对象来保存每个客户名字和对应输出流之间的对应关系
	public static CrazyitMap<String,PrintStream> clients = new CrazyitMap<>();
	public void init() throws IOException{
		//建立监听的ServerSocket
		ServerSocket ss =new ServerSocket(SERVER_PORT);
		//采用死循环来不断地接收来自客户端的请求
		while(true){
			Socket socket =ss.accept();
			new ServerThread(socket).start();
		}
	}
	
	public static void main(String[] args) throws IOException {
		new Server().init();
		
	}
}

创建Thread类用于转发给Clinet客户 以/斜杠开头并带有:号则视为私聊

package com.test.tcpmap;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;

public class ServerThread extends Thread{
	private Socket socket;
	BufferedReader br =null;
	PrintStream ps = null;
	
	//定义一个构造器,用于接收一个socket来创建ServerThread线程
	public ServerThread(Socket socket){
		this.socket  = socket;
	}
	
	public void run(){
		//获取该Socket对应的输入流
		try {
			br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			//获取该Socket对应的输出流
			ps = new PrintStream(socket.getOutputStream());
			String line = null;
			while((line =br.readLine())!= null){
				//如果读到的行以CrazyitProtocol.USER_ROUND开始,并以其结束
				//则可以确定读到的是用户登录的用户名
				if(line.startsWith(CrazyitProtocol.USER_ROUDN)&&line.endsWith(CrazyitProtocol.USER_ROUDN)){
					//得到真实的消息
					String userName = getRealMsg(line);
					//如果用户名重复
					if(Server.clients.containsKey(userName)){
						System.out.println("重复");
						ps.println(CrazyitProtocol.NAME_REP);
					}else{
						System.out.println("成功");
						ps.println(CrazyitProtocol.LOGIN_SUCCESS);
						Server.clients.put(userName, ps);
					}
				}
				//如果读到的行以CrazyitProtocol.PRIVATE_ROUND开始,并以其结束
				//则可以确定是私聊信息,私聊信息只向定向的输出流发送
				else if(line.startsWith(CrazyitProtocol.PRIVATE_ROUND)&&line.endsWith(CrazyitProtocol.SPLIT_SIGN)){
					System.out.println("私聊");
					//得到真实消息
					String userAndMsg = getRealMsg(line);
					//以SPLIT_SIGN 分割字符串,前半是私聊用户,后半是聊天信息
					String user = userAndMsg.split(":")[0];
					String msg = userAndMsg.split(":")[1];
					//获取私聊用户对于的输出流,并发送私聊信息
					Server.clients.get(user).println(Server.clients.getKeyByValue(ps)+"悄悄对你说:"+msg);
				//公聊要向每个Socket发送
				}else{
					System.out.println("公开");
					//得到真实消息
					String msg = getRealMsg(line);
					//遍历clinets中每个输出流
					for(PrintStream clientPs :Server.clients.valueSet()){
						clientPs.println(Server.clients.getKeyByValue(ps)+"说"+msg);
					}
				}
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			Server.clients.removeByValue(ps);
			System.out.println("剩余的客户数量"+Server.clients.size());
			try{
				if(br != null){
					br.close();
				}
				if(ps !=null){
					ps.close();
				}
				if(socket !=null){
					socket.close();
				}
			}catch(IOException ex){
				System.out.println("客户退出了"+ex.getMessage());
			}
			System.out.println("客户退出了"+e.getMessage());
		}
	}

	private String getRealMsg(String line) {
		
		return line.substring(CrazyitProtocol.PROTOCOL_LEN,line.length()-CrazyitProtocol.PROTOCOL_LEN);
	}
}

创建Client客户端访问服务端

package com.test.tcpmap;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.net.Socket;

import javax.swing.JOptionPane;

/**
 * 该程序是创建一个Client客户端,并通过登录的方式想服务端发送数据,通过服务端转发给其他的用户
 * */
public class Client {
	private static final int SERVER_PORT =30000;
	private Socket socket;
	private PrintStream ps;
	private BufferedReader brServer;
	private BufferedReader keyIn;
	
	public static void main(String[] args) {
		Client client=new Client();
		client.init();
		client.readAndSend();
	}
	public void init(){
		try {
			//初始化代表键盘的输入流
			keyIn = new BufferedReader(new InputStreamReader(System.in));
			//连接到服务器
			socket  = new Socket("127.0.0.1",SERVER_PORT);
			//获取该Socket对应的输入流和输出流
			ps = new PrintStream(socket.getOutputStream());
			brServer = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			String tip ="";
			//采用循环不断的弹出对话框要求输入用户名
			while(true){
				String userName = JOptionPane.showInputDialog(tip+"输入用户名");
				//在用户输入的用户名前后增加协议字符串后发送
				ps.println(CrazyitProtocol.USER_ROUDN+userName+CrazyitProtocol.USER_ROUDN);
				//读取服务器端的响应
				String result = brServer.readLine();
				//如果用户名重复,则开始下次循环
				if(result.equals(CrazyitProtocol.NAME_REP)){
					tip="用户名重复!请重写";
					continue;
				}
				//如果服务器端返回登录成功,则结束循环
				if(result.equals(CrazyitProtocol.LOGIN_SUCCESS)){
					break;
				}
			}
		} catch (Exception e) {
			// TODO: handle exception
		}
		//以该Soket对应的输入流启动ClientThread线程
		new ClientThread(brServer).start();
	}
	
	//定义一个读取键盘输出,并向网络发送的方法
	private void readAndSend(){
		try{
			//不断的读取键盘输入
			String line = null;
			while((line =keyIn.readLine())!=null){
				//如果发送的信息中有冒号,且以/开头,则认为想发送私聊信息
				if(line.indexOf(":")>0&&line.startsWith("/")){
					line  = line.substring(1);
					ps.println(CrazyitProtocol.PRIVATE_ROUND+line+CrazyitProtocol.SPLIT_SIGN);
					
				}else{
					ps.println(CrazyitProtocol.MSG_ROUND+line+CrazyitProtocol.MSG_ROUND);
				}
			}
		}catch(IOException ex){
			System.out.println("网络通信异常!请重新登录");
		}
		
	}
	
	//关闭Socket、输入流、输出流的方法
	private void closeRs(){
		try{
			if(keyIn !=null){
				ps.close();
			}
			if(brServer !=null){
				ps.close();
			}
			if(ps !=null){
				ps.close();
			}
			if(socket !=null){
				keyIn.close();
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	
}

ClientThread线程类

package com.test.tcpmap;

import java.io.BufferedReader;
import java.io.IOException;
/**
 * 本例仅打印了从服务器读到的内容。实例上,此处的情况可以更复杂;如果我们希望客户端能看到聊天室的用户列表,则可以让服务端在每次有用户登录
 * 用户退出时,将所有的用户列表信息都向客户端发送一遍。为了区分服务端发送的是聊天信息,还是用户列表,服务器端也应该在发送的信息前、后都添加
 * 一定的协议字符串,客户端则根据协议字符串的不同而进行不同的处理!更复杂的情况:如果两端进行游戏,则还有可能发送游戏消息,例如两端进行⑤子棋游戏, 
 * 则需要发送下棋坐标信息等,服务器端同样在这些下棋坐标信息前、后添加则如要发送下棋坐标等信息知道对手的下次坐标
 * */
public class ClientThread extends Thread{
	//该客户端线程负责处理的输入流
	BufferedReader br =null;
	//使用一个网络输入流来创建客户端线程
	public ClientThread(BufferedReader br){
		this.br = br;
	}
	public void run(){
		String line =null;
		//不断的输入流中读取数据,并将这些数据打印输出
		try {
			while((line = br.readLine())!=null){
				System.out.println(line);
				
			}
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

接下来就是运行结果第一步先启动服务端

在这里插入图片描述

再是启动客户端请先输入用户名
![在这里插入在这里插入图片描述

这个时候服务端还监听到你连接成功了
在这里插入图片描述
再启动一个客户端程序
在这里插入图片描述

接下来就是聊天在客户端程序输入
在这里插入图片描述

另一个客户端则接受到
在这里插入图片描述
再发送私聊以/开头:冒号隔开(发送人:想说的话)记得冒号是中文的
在这里插入图片描述
在这里插入图片描述

欢迎加微信一起探讨Java a1582406829

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JAVA多人聊天室(多线程基础聊天室可以私聊/群聊) 的相关文章

随机推荐