0
点赞
收藏
分享

微信扫一扫

Java如何实现不同局域网TCP通信+群聊+私聊(云服务器实现)

编程练习生J 2022-04-06 阅读 58
eclipsejava

目录

一、实现思路

1.实现思路一

2.实现思路二

二、实现代码

1.思路一(服务端代码)

2.思路二(服务端代码)

三、运行效果


一、实现思路

用户输入@name的方式发送消息(类似于QQ你@别人),但是想通过服务器端从信息中解析出这个@的人的名字不太可能,因为服务器端不知道截多长才是用户的名字。所以只能判断信息中是否包含了@name。String类的contains()方法可以做到。

服务端有以下两种思路去实现私聊

1.实现思路一

先判断用户是否@人了。

如果@人了,则发送消息的用户的服务套接字让其他的服务套接字判断这个@的名字是不是自己服务的用户的名字,如果是就转发,不是就不转发。

如果没@,则全部转发。

2.实现思路二

先判断用户是否@人了。

如果@人了,发送消息的用户的服务套接字根据@的名字让该名字的服务套接字转发。(哈希表)

如果没@,则全部转发。

二、实现代码

我们的用户端只负责输入,因此只需要改变服务端的代码。(这里也只记录服务器端的代码,用户端的代码和工具类代码在上一篇)。

1.思路一(服务端代码)

import java.io.Closeable;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

import com.csi.qunliaoTest.OpenClient.ReceiveMsgThread;



public class OpenServer {
	//一个列表存储服务Socket
	List<ClientSocket> clientSockets =new ArrayList<>();   
	
	ServerSocket serverSocket = null;   
	 
	 //构造方法中创建服务器ServerSocket
	public  OpenServer() {
		try {
			//因为本代码是在云服务器运行,直接用InetAddress.getLocalHost()方法绑定云服务器的IP地址及端口号
			serverSocket = new ServerSocket(7777, 50, InetAddress.getLocalHost()); 
			System.out.println("----------服务器----------");
			
		} catch (IOException e) {
			//发生异常。调用自己写的Utils类关闭服务器
			Utils.close(serverSocket);
		}
		
		   //ServerSocket绑定成功,开始等待用户接入
		if(serverSocket.isBound())
			acceptClient();
	}
	
	//等待用户,并为他生成服务Socket
	private void acceptClient(){
		//while循环让服务器一直可以接入用户,一个用户接入服务器,服务器就生成一个为该用户服务的Socket
		while(true) {
			Socket	socket=null;
			try {
				
				//ServerScoket的accept()方法是一个阻塞方法,他会在这里等用户接入,直到有用户接入,才会运行下面的代码
				socket=serverSocket.accept();
				System.out.println("一个用户接入.....");
				//用ClientSocket类包装Scoket类,ClientSocket类是自己写的内部线程类,该类实现了接收和转发用户消息
				ClientSocket clientSocket=new ClientSocket(socket);
				//开启服务线程
				clientSocket.start();
				
				//将这个包装了服务Scoket的对象添加进列表
				clientSockets.add(clientSocket);
			} catch (IOException e) {
				Utils.close(socket);
			}     
			
	    }
	}
	
	public static void main(String[] args) {
		//执行服务器代码
		new OpenServer();
	}
	
	class ClientSocket extends Thread{
		Socket socket=null;
		DataInputStream dataInputStream=null;
		DataOutputStream dataOutputStream=null;
		String name = null;

		public ClientSocket(Socket socket) {
			this.socket = socket;
			//包装服务Socket的输入输出流,异常就调用closeScoket()方法
			try {
				dataInputStream=new DataInputStream(socket.getInputStream());
			} catch (IOException e) {
				closeSocket(dataInputStream,socket);
			}
			try {
				dataOutputStream=new DataOutputStream(socket.getOutputStream());
			} catch (IOException e) {
				closeSocket(dataOutputStream,socket);
			}
		}
		
		
		//上面的代码我们关闭的都是装饰流,因为关闭装饰流会将内部流也关闭,Socket也会因此关闭,同时我们也要将列表里对应Socket的删除
		public void closeSocket(Closeable...closeables) {
			Utils.close(closeables);
			System.out.println("一位用户退出");
			clientSockets.remove(this);
		}

		@Override
		public void run() {
			

			//读取用户姓名,并让列表所有用户转发欢迎信息
			//数据流DataIn/OutputStream的readUTF()和writeUTF(String data)要一起用,是将数据以UTF-8的编码方式发出或者接收
			
	    	try {
	    		//readUTF()方法也是阻塞方法,读取用户发来的名字
				name=dataInputStream.readUTF();
			} catch (IOException e1) {
				closeSocket(dataInputStream);
			}
	    	//循环列表,除了自己,其他服务Scoket全部转发消息
	    	for(ClientSocket clientSocket:clientSockets) {
				if(clientSocket!=this) {
					try {
						clientSocket.dataOutputStream.writeUTF("欢迎"+name+"进入聊天室");
						clientSocket.dataOutputStream.flush();//清空缓存区,让缓存区的数据全部出来
					} catch (IOException e) {
						clientSocket.closeSocket(clientSocket.dataOutputStream);
					}
					
				}
			}
	    	
	    	 //知道名字后就一直等待接收用户端发的消息,异常就关闭Scoket并跳出循环
	    	while(true) {        
				String msg = null;
				//在读取到的消息前加上姓名
				try {
					msg = name+":"+dataInputStream.readUTF();
					
				} catch (IOException e) {
					closeSocket(dataInputStream);
					break;
				}
				//判断信息中是否@人了,如果@人了,其他每个服务Socket判断@的名字里是不是自己服务的用户名字;如果没有@,则全部转发
				if(msg.contains("@")) {
					for(ClientSocket clientSocket:clientSockets) {
						if(clientSocket!=this) {
							try {
								
								if(msg.contains("@"+clientSocket.name)) {   //找到了是自己的用户端名字
									//将@名字替换为空,replace返回一个心得字符串
									clientSocket.dataOutputStream.writeUTF(msg.replace("@"+clientSocket.name, ""));
									clientSocket.dataOutputStream.flush();
								}
							} catch (IOException e) {
								clientSocket.closeSocket(clientSocket.dataOutputStream);
							}
						}
					}
				}
				else {
					for(ClientSocket clientSocket:clientSockets) {
						if(clientSocket!=this) {
							try {
								clientSocket.dataOutputStream.writeUTF(msg);
								clientSocket.dataOutputStream.flush();
							} catch (IOException e) {
								clientSocket.closeSocket(clientSocket.dataOutputStream);
							}
						}
					}
				}
					
						
					
			}
			
		}
	}


}

2.思路二(服务端代码)

用户名不要写一样的名字,他是根据名字找套接字,当查找到这个名字就去哈希表找对应的套接字,如果名字相同,他只会找第一个与这个名字相同的套接字。

package com.csi.siliaoTest;

import java.io.Closeable;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import com.csi.siliaoTest.OpenServer.ClientSocket;



public class OpenServer2 {
	//一个哈希表存储用户名字和服务套接字
	HashMap<String, ClientSocket> map= new HashMap<>();
	//一个列表存储服务Socket
	List<ClientSocket> clientSockets =new ArrayList<>();
	//一个列表存储用户名字
	List<String> names = new ArrayList<>();
	ServerSocket serverSocket = null;   
	 
	 //构造方法中创建服务器ServerSocket
	public  OpenServer2() {
		try {
			//因为本代码是在云服务器运行,直接用InetAddress.getLocalHost()方法绑定云服务器的IP地址及端口号
			serverSocket = new ServerSocket(7777, 50, InetAddress.getLocalHost()); 
			System.out.println("----------服务器----------");
			
		} catch (IOException e) {
			//发生异常。调用自己写的Utils类关闭服务器
			Utils.close(serverSocket);
		}
		
		   //ServerSocket绑定成功,开始等待用户接入
		if(serverSocket.isBound())
			acceptClient();
	}
	
	//等待用户,并为他生成服务Socket
	private void acceptClient(){
		//while循环让服务器一直可以接入用户,一个用户接入服务器,服务器就生成一个为该用户服务的Socket
		while(true) {
			Socket	socket=null;
			try {
				
				//ServerScoket的accept()方法是一个阻塞方法,他会在这里等用户接入,直到有用户接入,才会运行下面的代码
				socket=serverSocket.accept();
				System.out.println("一个用户接入.....");
				//用ClientSocket类包装Socket类,ClientSocket类是自己写的内部线程类,该类实现了接收和转发用户消息
				ClientSocket clientSocket=new ClientSocket(socket);
				//开启服务线程
				clientSocket.start();

				//将这个包装了服务Socket的对象添加进列表
				clientSockets.add(clientSocket);
			} catch (IOException e) {
				Utils.close(socket);
			}     
			
	    }
	}
	
	public static void main(String[] args) {
		//执行服务器代码
		new OpenServer2();
	}
	
	class ClientSocket extends Thread{
		Socket socket=null;
		DataInputStream dataInputStream=null;
		DataOutputStream dataOutputStream=null;
		String name = null;

		public ClientSocket(Socket socket) {
			this.socket = socket;
			//包装服务Socket的输入输出流,异常就调用closeScoket()方法
			try {
				dataInputStream=new DataInputStream(socket.getInputStream());
			} catch (IOException e) {
				closeSocket(dataInputStream,socket);
			}
			try {
				dataOutputStream=new DataOutputStream(socket.getOutputStream());
			} catch (IOException e) {
				closeSocket(dataOutputStream,socket);
			}
		}
		
		
		//上面的代码我们关闭的都是装饰流,因为关闭装饰流会将内部流也关闭,Socket也会因此关闭,同时我们也要将列表里对应Socket的删除
		public void closeSocket(Closeable...closeables) {
			Utils.close(closeables);
			System.out.println("一位用户退出");
			clientSockets.remove(this);
			names.remove(name);
			map.remove(name);
		}

		@Override
		public void run() {
			

			//读取用户姓名,并让列表所有用户转发欢迎信息
			//数据流DataIn/OutputStream的readUTF()和writeUTF(String data)要一起用,是将数据以UTF-8的编码方式发出或者接收
			
	    	try {
	    		//readUTF()方法也是阻塞方法,读取用户发来的名字并添加哈希表和列表
				name=dataInputStream.readUTF();
				names.add(name);
				map.put(name, this);
			} catch (IOException e1) {
				closeSocket(dataInputStream);
			}
	    	//循环列表,除了自己,其他服务Socket全部转发消息
	    	for(ClientSocket clientSocket:clientSockets) {
				if(clientSocket!=this) {
					try {
						clientSocket.dataOutputStream.writeUTF("欢迎"+name+"进入聊天室");
						clientSocket.dataOutputStream.flush();//清空缓存区,让缓存区的数据全部出来
					} catch (IOException e) {
						clientSocket.closeSocket(clientSocket.dataOutputStream);
					}
					
				}
			}
	    	
	    	 //知道名字后就一直等待接收用户端发的消息,异常就关闭Scoket并跳出循环
	    	while(true) {        
				String msg = null;
				//在读取到的消息前加上姓名
				try {
					msg = name+":"+dataInputStream.readUTF();
					
				} catch (IOException e) {
					closeSocket(dataInputStream);
					break;
				}
				//判断信息中是否@人了,如果@人了,查找是否有@的这个名字的用户
				if(msg.contains("@")) {
					ClientSocket clientSocket = null;
					for(String aname: names) {
						if (msg.contains("@"+aname)) {
							try {clientSocket =map.get(aname);
								clientSocket.dataOutputStream.writeUTF(msg.replace("@"+aname, ""));
								clientSocket.dataOutputStream.flush();
							} catch (IOException e) {
								clientSocket.closeSocket(clientSocket.dataOutputStream);

							}
							
						}
					}
				}
				else {
					for(ClientSocket clientSocket:clientSockets) {
						if(clientSocket!=this) {
							try {
								clientSocket.dataOutputStream.writeUTF(msg);
								clientSocket.dataOutputStream.flush();
							} catch (IOException e) {
								clientSocket.closeSocket(clientSocket.dataOutputStream);
							}
						}
					}
				}
					
						
					
			}
			
		}
	}


}

注意:不要写一样的用户名字,他是在哈希表里根据名字找套接字,如果名字相同,他只会找第一个与这个名字相同的套接字。如果写一样得名字就只有一个用户会收到@的信息,并且会收到好几条(有几个重名就有几条)。如下图,我写了两个张三。


三、运行效果

举报

相关推荐

0 条评论