zoukankan      html  css  js  c++  java
  • 实现了私聊和群聊功能的聊天工具


    前面的博客(简单的C/S聊天室)中。我们已经提到了,採用的是多线程的方法。server端主线程负责不断的侦听port。子线程负责接收和发送消息。client主线程须要接收键盘消息,将其发送到server端,子线程须要接收server端发过来的消息。在这个简易的C/S聊天室的实现中。只实现了群聊的功能。没有实现私聊。那么,本文就讲实现私聊和群聊。


    首先我们想到的是,消息发过来,我怎么知道是公聊消息还是私聊消息呢。所以。这里须要对消息进行处理,比方说在消息前后都加上一些特殊的字符,我们称为协议字符。

    为此,我们能够定义一个接口,专门来定义协议字符。

    第二个问题就是,假设是私聊信息,client会将目的用户(私聊对象)发给server端,那么server端是怎样将找到那个目的用户的呢。这里,非常明显。我们须要建立一个用户和Socket的映射关系。所以我们採用了map,可是这里的map我们须要改进一下。由于事实上我们这里不不过key不能反复,并且value也不能反复,我们也须要通过value可以查找到key,所以我们进行了改进。

    另一点针对本实现须要指出的是,server子线程负责接收和发送消息,这里面也包含client首次建立连接的时候。须要推断username是否反复,也就是要保证key不反复,于此想相应的,client在首次建立连接时,其须要进行不断的尝试。直到提供的名字不反复为止。

    代码例如以下:


    public interface CrazyitProtocol {
    	public static final int PROTOCOL_LEN=2; //默认的类型就是public static final。不加也是能够的
    	
    	public static final String MSG_ROUND="△▽";
    	public static final String USR_ROUND="□☆";
    	public static final String LOGIN_SUCCESS="☆▷";
    	public static final String NAME_REP="-1";
    	public static final String PRAVITE_ROUND="◆★";
    	public static final String SPLIT_SIGN="☀";
    
    }
    

    import java.util.HashMap;
    import java.util.HashSet;
    import java.util.Set;
    
    
    public class CrazyitMap<K,V> extends HashMap<K,V> {
    
    	// 依据value来删除指定项
    	public void removeByValue(Object value)
    	{
    		for(Object key :keySet())
    		{
    			if(get(key)==value||get(key).equals(value))
    			{
    				remove(key);
    				break;
    			}
    		}
    	}
    	
    	// 获取value集合
    	public Set<V> valueSet()
    	{
    		Set<V> result=new HashSet<V>();
    		for(Object key : keySet())
    		{
    			result.add(get(key));
    		}
    		return result;
    	}
    	
    	// 重写HashMap的put方法,该方法不同意value反复
    	public V put(K key,V value)
    	{
    		for(V val : valueSet())
    		{
    			if(val==value||val.equals(value))
    			{
    				throw new RuntimeException("MyMap实例中不同意有反复value");
    			}
    		}
    		return super.put(key, value);	
    	}
    	
    	// 通过value查找key
    	public K getKeyByValue(Object value)
    	{
    		for(K key : keySet())
    		{
    			if(get(key)==value||get(key).equals(value))
    			{
    				return key;
    			}
    		}
    		return null;
    	}
    }
    

    import java.io.IOException;
    import java.io.PrintStream;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    
    public class Server {
    	
    	private static final int PORT=30000;
    	public static CrazyitMap<String,PrintStream> clients=new CrazyitMap<>();
    	
    	void init()
    	{
    		try (
    			ServerSocket ss=new ServerSocket(PORT);
    		)
    		{
    			while(true)
    			{
    				Socket s=ss.accept();
    				new Thread(new ServerThread(s)).start();
    			}
    			
    		}catch (IOException e) {
    			// TODO Auto-generated catch block
    			System.out.println("server启动失败。是否端口被占用?");
    		}
    	}
    
    	public static void main(String[] args) {
    		// TODO Auto-generated method stub
    		Server s=new Server();
    		s.init();
    
    	}
    
    }
    

    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.PrintStream;
    import java.net.Socket;
    
    
    public class ServerThread implements Runnable {
    	
    	private Socket s;
    	private BufferedReader br=null;
    	private PrintStream ps=null;
    	
    	public ServerThread(Socket s)
    	{
    		this.s=s;
    		
    	}
    
    	@Override
    	public void run() {
    		// TODO Auto-generated method stub
    		
    		try {
    			br=new BufferedReader(new InputStreamReader(s.getInputStream()));
    			ps=new PrintStream(s.getOutputStream());
    			String content=null;
    			while((content=br.readLine())!=null)
    			{
    				if(content.startsWith(CrazyitProtocol.USR_ROUND) //发过来的是名字信息
    						&&content.startsWith(CrazyitProtocol.USR_ROUND))
    				{
    					String userName=getRealMsg(content);
    					if(Server.clients.containsKey(userName)) // 姓名反复
    					{
    						System.out.println("反复");
    						ps.println(CrazyitProtocol.NAME_REP);
    						
    					}
    					else // 姓名不反复
    					{
    						System.out.println("成功");
    						Server.clients.put(userName, ps);
    						ps.println(CrazyitProtocol.LOGIN_SUCCESS);
    					}
    				}
    				else if(content.startsWith(CrazyitProtocol.PRAVITE_ROUND)
    						&&content.startsWith(CrazyitProtocol.PRAVITE_ROUND))// 发过来的是实际的消息,且为私聊消息
    				{
    					String userAndMsg=getRealMsg(content);
    					String userName=userAndMsg.split(CrazyitProtocol.SPLIT_SIGN)[0];
    					String Msg=userAndMsg.split(CrazyitProtocol.SPLIT_SIGN)[1];
    					// 获取私聊用户的输出流
    					Server.clients.get(userName).println(Server.clients.getKeyByValue(ps)
    							+"悄悄的对你说"+Msg);
    					
    				}
    				else // 公聊信息
    				{
    					String Msg=getRealMsg(content);
    					for(PrintStream ps : Server.clients.valueSet())
    					{
    						ps.println(Server.clients.getKeyByValue(this.ps)
    								+"说:"+Msg);
    					}
    				}
    			}
    		} 
    		// 捕获异常,表明该Socket相应的客户端已出现故障。
    		// 所以客户端将其相应的输出流从Map中删除
    		catch (IOException e) {
    			// TODO Auto-generated catch block
    			Server.clients.removeByValue(ps);
    			try{
    				if(br!=null)
    				{
    					br.close();
    				}
    				if(ps!=null)
    				{
    					ps.close();
    				}
    				if(s!=null)
    				{
    					s.close();
    				}
    			}
    			catch(IOException ex)
    			{
    				ex.printStackTrace();
    			}
    		}
    
    	}
    	// 讲读到的内容去掉前后的协议字符,恢复为真实数据
    	private String getRealMsg(String content) {
    		// TODO Auto-generated method stub
    		
    		return content.substring(CrazyitProtocol.PROTOCOL_LEN, 
    				content.length()-CrazyitProtocol.PROTOCOL_LEN);
    	}
    
    }
    

    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.PrintStream;
    import java.net.Socket;
    import java.net.UnknownHostException;
    
    import javax.swing.JOptionPane;
    
    
    public class Client {
    	
    	private static final int PORT=30000;
    	private Socket s=null;
    	private PrintStream ps=null;
    	private BufferedReader brServer=null; //server发送过来的内容
    	private BufferedReader keyIn=null; // 键盘输入内容
    	public void init()
    	{
    		try
    		{
    		s=new Socket("127.0.0.1",PORT);
    		ps=new PrintStream(s.getOutputStream());
    		keyIn=new BufferedReader(new InputStreamReader(System.in));
    		brServer=new BufferedReader(new InputStreamReader(s.getInputStream()));
    		
    		// 用于在server端登录,由于名字有可能反复
    		String tip="";
    		while(true)
    		{
    			String userName=JOptionPane.showInputDialog(tip
    					+"输入username");
    			
    			// 在用户输入的username前后添加协议字符串后发送
    			ps.println(CrazyitProtocol.USR_ROUND+userName
    					+CrazyitProtocol.USR_ROUND);
    			String result=brServer.readLine();
    			if(result.equals(CrazyitProtocol.NAME_REP))
    			{
    				tip="username反复。请又一次";
    				continue;
    			}
    			// 登录成功
    			if(result.equals(CrazyitProtocol.LOGIN_SUCCESS))
    			{
    				break;
    			}
    		}
    		}
    		catch(UnknownHostException ex)
    		{
    			System.out.println("找不到远程server。请确定server已启动!");
    			closeRs();
    			System.exit(1);
    		}
    		catch(IOException ex)
    		{
    			System.out.println("网络异常。请又一次登录!

    "); closeRs(); System.exit(1); } new Thread(new ClientThread(brServer)); // 子线程负责接收server端传过来的消息 } private void closeRs() { // TODO Auto-generated method stub try{ if(keyIn!=null) { keyIn.close(); } if(brServer!=null) { brServer.close(); } if(ps!=null) { ps.close(); } if(s!=null) { s.close(); } } catch(IOException e) { e.printStackTrace(); } } // 主线程的接收键盘消息函数 public void readAndSend() { String content=null; try { while((content=keyIn.readLine())!=null) { // 所发消息中以/开头,且有:则觉得是是私聊信息 if(content.startsWith("/")&&content.indexOf(":")>0) { content=content.substring(1); //消息中不须要带开头的/ content=CrazyitProtocol.PRAVITE_ROUND+content.split(":")[0]+CrazyitProtocol.SPLIT_SIGN +content.split(":")[1]+CrazyitProtocol.PRAVITE_ROUND; ps.println(content); } else // 群聊信息 { content=CrazyitProtocol.MSG_ROUND+content+CrazyitProtocol.MSG_ROUND; ps.println(content); } } } catch(IOException e) { System.out.println("网络通信异常!

    请又一次登录!

    "); closeRs(); System.exit(1); } } public static void main(String[] args) { // TODO Auto-generated method stub Client client=new Client(); client.init(); client.readAndSend(); } }


    import java.io.BufferedReader;
    import java.io.IOException;
    
    
    public class ClientThread implements Runnable {
    	
    	private BufferedReader brServer=null;
    	public ClientThread(BufferedReader brServer)
    	{
    		this.brServer=brServer;
    	}
    	@Override
    	public void run() {
    		// TODO Auto-generated method stub
    		
    		String content=null;
    		
    		try
    		{
    		while((content=brServer.readLine())!=null)
    		{
    			System.out.println(content);
    		}
    		}
    		catch(IOException e)
    		{
    			e.printStackTrace();
    		}
    		finally
    		{
    			try{
    			if(brServer!=null)
    			{
    				brServer.close();
    			}
    			}
    			catch(IOException e)
    			{
    				e.printStackTrace();
    			}
    		}
    
    	}
    
    }
    

    參考资料:JAVA疯狂讲义


  • 相关阅读:
    移动端的dl
    以resnet作为前置网络的ssd目标提取检测
    MobileNet V2
    axis
    后RCNN时代的物体检测及实例分割进展
    RuntimeError: module compiled against API version 0xb but this version of numpy is 0xa
    caffe2安装
    git学习
    各种各样的卷积核
    数字图像处理_基础知识
  • 原文地址:https://www.cnblogs.com/wgwyanfs/p/7340388.html
Copyright © 2011-2022 走看看