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
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现一个多人聊天室需要涉及到网络编程以及并发编程。在Python中,可以通过TCP协议来实现多人聊天室。 下面是一个跨主机的Python+TCP实现多人聊天室的简单示例代码: ```python import socket import threading # 服务器IP地址和端口号 SERVER_HOST = '0.0.0.0' SERVER_PORT = 8000 # 客户端列表 clients = [] def broadcast(message): """ 广播消息给所有客户端 """ for client in clients: client.send(message) def handle_client(client): """ 处理客户端连接 """ while True: try: message = client.recv(1024) if message: print(f"[{client.getpeername()}]: {message.decode('utf-8')}") message_to_broadcast = f"[{client.getpeername()}]: {message.decode('utf-8')}".encode('utf-8') broadcast(message_to_broadcast) else: remove(client) except: remove(client) def remove(client): """ 客户端断开连接 """ if client in clients: clients.remove(client) client.close() if __name__ == "__main__": # 创建服务器套接字 server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server_socket.bind((SERVER_HOST, SERVER_PORT)) server_socket.listen() print(f"[*] Listening on {SERVER_HOST}:{SERVER_PORT}") while True: client_socket, client_address = server_socket.accept() print(f"[*] Accepted connection from {client_address[0]}:{client_address[1]}") clients.append(client_socket) client_thread = threading.Thread(target=handle_client, args=(client_socket,)) client_thread.start() ``` 在上述代码中,我们使用了Python的socket模块来创建TCP服务器和客户端套接字,使用了Python的threading模块来进行并发处理。我们定义了一个`broadcast`函数来广播消息给所有客户端,定义了一个`handle_client`函数来处理客户端连接,定义了一个`remove`函数来处理客户端断开连接。 在`handle_client`函数中,我们使用了一个死循环来持续处理客户端发送的消息。当接收到消息时,我们使用`broadcast`函数将该消息广播给所有客户端。当客户端断开连接时,我们使用`remove`函数将该客户端从客户端列表中移除。 在`__main__`函数中,我们创建服务器套接字,监听客户端连接。当有新的客户端连接时,我们将其添加到客户端列表中,并使用一个新的线程来处理该客户端连接。 这是一个简单的跨主机Python+TCP实现多人聊天室的示例代码,你可以在此基础上进行扩展和优化。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值