Java网络编程之用TCP模拟多人聊天室

Java网络编程之用TCP模拟聊天室

最近在B站跟着尚学堂的高琪老师学Java,课程很不错,老师不光在讲知识点,更重要的是在讲涉及思路,推荐大家来学习!
B站传送门:点我进入
上一篇博客简单模拟的Java的TCP的基本步骤
这次将是充分对之前知识点的扩展
多人聊天室功能:
1.多个客户端可以同时进行聊天
2.可以自定义进入客户端的用户名
3.一个客户端发消息可被其他客户端收到
4.可以进行私聊@Zhangsan:Hello Zhangsan!

首先分析简单的功能需求,一点一点来看
1.多个客户端,必然要用到多线程,且客户端可以同时<接受消息&&发送消息>。
2.在每个客户端类里创建个字符串name,通过控制台输入即可。
3.一个客户端和服务端通信会先建立一条管道,我们把管道们存放在一个数组里,当给其他客户端发消息时,只需遍历一遍管道们,通过管道给所对应的客户端发消息即可。
4.当服务端接收到一个客户端发来的消息时,我们先进行解剖。私聊的格式:@用户名:消息内容,看看符不符合这个格式,符合的话,我们提取出用户名与消息内容,遍历管道 找到用户名对应的管道只给他发消息即可。不符合私聊格式的话,我们默认给全部人都发,符合第3点。

服务端

存储管道们的时候,要考虑到多个客户端的同时写与读
可以考虑使用CopyOnWriteArrayList,底层是用volatile transient声明的数组array,实现了读写分离,写时复制出一个新的数组,完成插入、修改或者移除操作后将新数组赋值给array。

CopyOnWriteArrayList<mxy> all=new CopyOnWriteArrayList<mxy>();   //用来存储管道们

建立连接

ServerSocket server=new ServerSocket(5655); 

开始不停的监听接受客户端们的连接

while(true){
		Socket client=server.accept();		//时刻准备着客户端们!
		mxy dog=new mxy(client);
		all.add(dog); 						//容器管理所有的客户端
		new Thread(dog).start();			//实现能够同时接受、发送的多线程
	}

发送和接受的多线程类

 class mxy implements Runnable{

	private Socket client;
	private DataInputStream dis;
	private DataOutputStream dos;
	private String namee;
	public mxy(Socket client) throws IOException {
		// TODO Auto-generated constructor stub
		this.client=client;
		try {
			dis=new DataInputStream(client.getInputStream()); 
			dos=new DataOutputStream(client.getOutputStream());
			
			//先获取输入的用户名 用于下一步操作
			namee=dis.readUTF(); 

			//发送XX来了到别的客户端
			for(mxy other:all) {
				if(other.equals(this)) {//如果是客户端时自己的话就不重复发送了
					continue;
				}
				else {
					other.dos.writeUTF(namee+"进入了聊天室!");
				}
			 }
			
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
			//释放资源
			dis.close();
			dos.close();
			client.close();
		}
	}
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		boolean flag=true;
		while(flag)
		{
			String datas = null;			//存储整个一条消息
			String[] siliaodata=null;		//存储私聊内容
			String[] siliaouser=null;		//存储私聊用户名
			try {
				datas = dis.readUTF();
				//System.out.println(datas.charAt(0));
				
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				//释放资源
				try {
					dis.close();
				} catch (IOException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
				try {
					dos.close();
				} catch (IOException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
				try {
					client.close();
				} catch (IOException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
			}
			
			
			try {
				//开始判断是不是私聊,是的话-----------------------------------------
				if(datas.charAt(0)=='@')
				{
					siliaodata=datas.split(":");			//用spilt分割冒号
					siliaouser=siliaodata[0].split("@");	//用spilt分割@
					for(mxy other:all) {					//遍历客户端开始寻找
						if(other.namee.equals(siliaouser[1])) {	
							other.dos.writeUTF(namee+"对你说"+siliaodata[1]);//开始给私聊的客户端发送消息
						}
					}
				}
				//下面是不是的话,代表给全体发的-------------------------------------
				else {
					for(mxy other:all) {
						if(other.equals(this)) {
							continue;
						}
						else {
							other.dos.writeUTF(namee+"对所有人说"+datas);
						}
					 }
				
				}
				//--------------------------------------------------------------
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				//释放资源
				try {
					dis.close();
				} catch (IOException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
				try {
					dos.close();
				} catch (IOException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
				try {
					client.close();
				} catch (IOException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
			}
		}
	}
	
}

客户端
客户端的话,需要用多线程实现接受和发送消息,每个客户端需要先输入自己的用户名,在new 发送类的时候顺便把用户名也传进去,首先用DataOutputStream先把用户名传给服务端,用于给别的客户端提示:XXX进入了聊天室。其他的也没什么了

建立连接

Socket client=new Socket("localhost",5655);
public class Client {
	static String namee;
	static Scanner sc=new Scanner(System.in);
	public static void main(String[] args) throws Exception, IOException {
		Socket client=new Socket("localhost",5655);
		System.out.println("请输入您的名字");
		namee=sc.next();
		new Thread(new Send(client,namee)).start();	     //在Send构造器里传进去用户名
		new Thread(new Receive(client)).start();
	//	client.close();  这句该死的代码!错了一晚上,不能把客户端关掉!
	}

发送类
发送的时候,创建构造器,先把用户名丢进去,服务端那边读出来发给其他客户端

class Send implements Runnable{
		Scanner sc=new Scanner(System.in);
		private DataOutputStream dos;
		private Socket client;
		private String namee;
		public Send(Socket client, String namee) {
			this.namee=namee;
			this.client=client;
			try {
				dos=new DataOutputStream(client.getOutputStream());
				dos.writeUTF(namee);       //先通过管道把用户名丢进去 用于提示XXX进入了聊天室
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}	
		}
		@Override
		public void run() {
			// TODO Auto-generated method stub
			boolean flag=true;	
			while(flag)
			{
				String msg;
				msg=sc.next();				  //键盘读入要发送的信息
				try {
					dos.writeUTF(msg);           
					dos.flush();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
					try {
						dos.close();
					} catch (IOException e1) {
						// TODO Auto-generated catch block
						e1.printStackTrace();
					}
				}
			}
		}
	}

接收类
没什么好说的,跟之前的一样

 class Receive implements Runnable{
		private DataInputStream dis;
		private Socket client;
		private String namee;
		public Receive(Socket client) {
			this.client=client;
			try {
				dis=new DataInputStream(client.getInputStream());
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				//释放资源
				try {
					dis.close();
				} catch (IOException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
				try {
					client.close();
				} catch (IOException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
			}
		}	
		@Override
		public void run() {
		boolean flag=true;	
		while(flag)
		{
			try {
				String res=dis.readUTF();
				System.out.println(res);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				try {
					dis.close();
				} catch (IOException e1) {
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
			}
		}
	}
}

代码效果图:在这里插入图片描述
我没有把IO流、资源释放用方法封装起来,所以看起来可能有点乱,其实道理都是一样的,还望大家见谅!
如有不妥的地方,欢迎大家在评论区指正!

发布了3 篇原创文章 · 获赞 0 · 访问量 92

猜你喜欢

转载自blog.csdn.net/weixin_43416532/article/details/104137762