在前面的博客(简单的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查找keypublic 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 blockSystem.out.println("服务器启动失败,是否端口被占用?");}}public static void main(String[] args) {// TODO Auto-generated method stubServer 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;}@Overridepublic void run() {// TODO Auto-generated method stubtry {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 blockServer.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 stubreturn 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 stubtry{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 stubClient 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;}@Overridepublic void run() {// TODO Auto-generated method stubString 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疯狂讲义

实现了私聊和群聊功能的聊天工具相关推荐

  1. 基于Vue+springboot+websocket实现的简短仿微信web聊天室(私聊和群聊功能)(可在线预览)

    写目录 一.界面展示 二.介绍 一.界面展示 之前闲着有空就给自己的个人博客搭了一些附加功能,聊天室也是其中之一,简单的实现了私聊.群聊功能,可以发送emoji表情和图片等,项目已经部署在www.tc ...

  2. 用C++/MFC实现P2P和群聊功能的聊天小软件

    final edit 2015-01-03 · 实现平台: Window 8.1,Visual Studio 2013 Window 7, Visual Studio 2010 · 所用框架: 是基于 ...

  3. 360搜索、UC浏览器等被3·15点名应用已下架;马斯克宣布通过NFT卖歌;美团App再发力社交,内测 “群聊”功能 |极客头条...

    「极客头条」-- 技术人员的新闻圈! CSDN 的读者朋友们早上好哇,「极客头条」来啦,快来看今天都有哪些值得我们技术人关注的重要新闻吧. 整理 | 丁恩华 出品 | CSDN(ID:CSDNnews ...

  4. 360搜索、UC浏览器等被3·15点名应用已下架;马斯克宣布通过NFT卖歌;美团App再发力社交,内测 “群聊”功能...

    凌云时刻 一分钟速览新闻点! 猎聘.前程无忧为大量简历流向黑市而致歉 医疗广告竞价排名,360 搜索.UC 纷纷上榜 315 晚会 315 晚会曝光人脸识别乱象:海量人脸信息已被搜集 安兔兔曝光 re ...

  5. java网络程序设计 聊天室之私聊、群聊和清屏功能的实现

    TCP聊天室实现了私聊.群聊和清屏的功能,简陋的UI界面,一个服务器端,支持多个客户端之间的通信. 项目代码:https://pan.baidu.com/s/17iegRam4KnWvcWHw3mvp ...

  6. Asp.net SignalR 应用并实现群聊功能 开源代码

    ASP.NET SignalR 是为 ASP.NET 开发人员提供的一个库,可以简化开发人员将实时 Web 功能添加到应用程序的过程.实时 Web 功能是指这样一种功能:当所连接的客户端变得可用时服务 ...

  7. 互联网早报 | 3月16日 星期二 | 微信AI直播助理开放内测;汽车之家港交所挂牌上市;美团App内测“群聊”功能...

    今日看点 ✦ 汽车之家港交所成功挂牌,成年内首家回港二次上市中概股 ✦ 恒大汽车与腾讯旗下梧桐车联成立合资公司 ,共同开发车载智能操作系统 ✦ 微信AI直播助理开放内测,助力电商带货 ✦ 美团App内 ...

  8. ASP.NET SignalR 与LayIM配合,轻松实现网站客服聊天室(四) 添加表情、群聊功能...

    休息了两天,还是决定把这个尾巴给收了.本篇是最后一篇,也算是草草收尾吧.今天要加上表情功能和群聊.基本上就差不多了,其他功能,读者可以自行扩展或者优化.至于我写的代码方面,自己也没去重构.好的,我们开 ...

  9. C++搭建集群聊天室(十四):群聊功能

    文章目录 群聊功能思路 放码过来 groupuser.hpp group.hpp groupmodel.hpp groupmodel.cpp 群聊功能思路 1.创建群聊,提交群信息,返回群号 2.拉取 ...

最新文章

  1. python minimize_简单三步实现Python脚本超参数调优(附代码)
  2. cac会议投稿难度大吗_注册化工师考试难度大吗?
  3. [网站链接]Debbie博客上的链接: [求职网站][博客链接][信息资源]……
  4. 数字图像处理:第二十章 视频编码与压缩
  5. NBA表格_多伦多猛龙,向NBA大结局说不!猛龙夺冠创下了哪些记录?
  6. Nacos源码Notifier异步更新
  7. 一个工作13年的SAP开发人员的回忆:电子科技大学2000级新生入学指南
  8. 【CCFCSP- 201312-4】有趣的数(线性dp)
  9. 基于wemos d1 按键开锁
  10. 计算机网络路由计算,计算机网络中的多播路由算法
  11. C# Socket/TCPClient断线重连/不断重连的简单思路+代码,海量注释
  12. 云计算机和云储存,云计算和云存储是什么关系?
  13. 湖北5G继续加码!今年投资64亿元,新建5G基站5万个
  14. oracle怎么使用Xmanager,Xmanager使用总结
  15. 菜鸟关于SpringBoot配置MinIo的一些疑惑问题的记录
  16. 2022,itbird的年终总结报告
  17. 通达信程序化交易接口的设计方案
  18. sudo: unable to resolove host iZ2ze7gg2o6tplktzc1le0Z问题的解决方法
  19. android开发工具箱下载,android开发工具箱APP
  20. Microsoft Virtual Earth 浅谈!

热门文章

  1. 强化学习综述(机器学习角度)
  2. CLAHE算法代码详解
  3. CodeReview中常见缩写
  4. 2022年G2电站锅炉司炉复习题及模拟考试
  5. 我的世界服务器怎么弄无限矿物,《我的世界》无限存储空间设备如何制作?
  6. idea 使用自动注解时候红色警告的消除办法
  7. 终极指南:家庭安防监控摄像机安装位置
  8. untiy 串口通信
  9. 计算机专业游戏本后悔,毕业了,到底要不要买游戏笔记本电脑?好多人买了都后悔了?...
  10. python实现m3u8转mp4