在前面的博客(简单的C/S聊天室)中,我们已经提到了,采用的是多线程的方法。服务器端主线程负责不断的侦听端口,子线程负责接收和发送消息。客户端主线程需要接收键盘消息,将其发送到服务器端,子线程需要接收服务器端发过来的消息。在这个简易的C/S聊天室的实现中,仅仅实现了群聊的功能,没有实现私聊。那么,本文就讲实现私聊和群聊。
首先我们想到的是,消息发过来,我怎么知道是公聊消息还是私聊消息呢。所以,这里需要对消息进行处理,比如说在消息前后都加上一些特殊的字符,我们称为协议字符。为此,我们可以定义一个接口,专门来定义协议字符。
第二个问题就是,如果是私聊信息,客户端会将目的用户(私聊对象)发给服务器端,那么服务器端是如何将找到那个目的用户的呢。这里,很明显,我们需要建立一个用户和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(); } } } }
原文地址:http://blog.csdn.net/a0agd1x50/article/details/37969339