群聊内实现私聊功能

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

第二个问题就是,如果是私聊信息,客户端会将目的用户(私聊对象)发给服务器端,那么服务器端是如何将找到那个目的用户的呢。这里,很明显,我们需要建立一个用户和Socket的映射关系,所以我们采用了map,但是这里的map我们需要改进一下,因为其实我们这里不仅仅是key不能重复,而且value也不能重复,我们也需要通过value能够查找到key,所以我们进行了改进。

还有一点针对本实现需要指出的是,服务器子线程负责接收和发送消息,这里面也包括客户端首次建立连接的时候,需要判断用户名是否重复,也就是要保证key不重复,于此想对应的,客户端在首次建立连接时,其需要进行不断的尝试,直到提供的名字不重复为止。

代码如下:

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("服务器启动失败,是否端口被占用?");
    }
  }

  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; //服务器发送过来的内容
  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()));
    
    // 用于在服务器端登录,因为名字有可能重复
    String tip="";
    while(true)
    {
      String userName=JOptionPane.showInputDialog(tip
          +"输入用户名");
      
      // 在用户输入的用户名前后增加协议字符串后发送
      ps.println(CrazyitProtocol.USR_ROUND+userName
          +CrazyitProtocol.USR_ROUND);
      String result=brServer.readLine();
      if(result.equals(CrazyitProtocol.NAME_REP))
      {
        tip="用户名重复,请重新";
        continue;
      }
      // 登录成功
      if(result.equals(CrazyitProtocol.LOGIN_SUCCESS))
      {
        break;
      }
    }
    }
    catch(UnknownHostException ex)
    {
      System.out.println("找不到远程服务器,请确定服务器已启动!");
      closeRs();
      System.exit(1);
    }
    catch(IOException ex)
    {
      System.out.println("网络异常!请重新登录!");
      closeRs();
      System.exit(1);
    }
    
    new Thread(new ClientThread(brServer)); // 子线程负责接收服务器端传过来的消息
    
  }
  

  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();
      }
    }

  }

}
posted @ 2016-09-10 09:50  穷途末路丶  阅读(1862)  评论(0编辑  收藏  举报