目录

开门见山

一、数据结构Map

二、保证线程安全

三、群聊核心方法

四、聊天室具体设计

0、用户登录服务器

1、查看当前上线用户

2、群聊

3、私信

4、退出当前聊天状态

5、离线

6、查看帮助

五、聊天室服务完整代码

六、效果演示:基于TCP的网络实时聊天室

结语


开门见山

!!!本项目上传微信公众号,可获取啦:关注我的博客下方微信公众号,发送“实时聊天室”获取

最近一个月记录了学习Socket网络编程的知识和实战案例,相对来说,比较系统地学习了基于TCP协议实现网络通信,也是计算机网络中重中之重,TCP/IP属于网络层,在java中,对该层的工作实现了封装,在编程中,就更加容易地去实现通信,而不用顾及底层的实现。当然,我们需要熟悉五层协议,在实践中体会其中的原理,理解更加深刻。

所以,系列文章从入门开始,不断完善C/S架构的Socket通信,回忆一下,首先是实现了Server和Client的互相通信,在这个过程发现问题,接着就使用多线程技术解决客户端实时接收信息的问题,后来到了服务器端,发现多用户连接服务器的“先到先得”问题,“后到者”无法正常通信,所以再使用线程池技术解决了多用户服务器的问题。

到此,基本实现了一个简单的客户端-服务器应用,因此,本篇将基于前面全部内容,使用客户端-服务器(C/S架构),结合多线程技术,模拟类似QQ、微信聊天功能,实现一个网络实时聊天室,目前的功能包括:

(1)L(list):查看当前上线用户;
(2)G(group):群聊;
(3)O(one-one):私信;
(4)E(exit):退出当前聊天状态;
(5)bye:离线;
(6)H(help):查看帮助

本篇将详细记录网络实时聊天室的实现步骤,下面的系列文章为前提基础。

Java实现socket通信网络编程系列文章:

  1. 基于UDP协议网络Socket编程(java实现C/S通信案例)【基于UDP协议网络Socket编程(java实现C/S通信案例)_陆海潘江小C的博客-CSDN博客_基于udp的socket编程。编写一个基于udp协议的socket网络通信应用程序,实现如下功】
  2. 基于TCP协议网络socket编程(java实现C/S通信)【基于UDP协议网络Socket编程(java实现C/S通信案例)_陆海潘江小C的博客-CSDN博客_基于udp的socket编程。编写一个基于udp协议的socket网络通信应用程序,实现如下功】
  3. Java多线程实现TCP网络Socket编程(C/S通信)【Java多线程实现TCP网络Socket编程(C/S通信)_陆海潘江小C的博客-CSDN博客_socket tcp 多线程发送】
  4. Java多线程实现多用户与服务端Socket通信【Java多线程实现多用户与服务端Socket通信_陆海潘江小C的博客-CSDN博客_java socket 多用户】

一、数据结构Map

前两篇的TCPClientThreadFX和TCPThreadServer实现了多线程的通信,但也只是客户端和服务器的聊天,如何做到群组的聊天?想法就是客户A的聊天信息通过服务器转发到同时在线的所有客户。

具体做法是需要在服务器端新增记录登陆客户信息的功能,每个用户都有自己的标识。本篇将使用简单的“在线方式”记录客户套接字,即采用集合来保存用户登陆的套接字信息,来跟踪用户连接。

所以,我们需要选择一种合适的数据结构来保存用户的Socket和用户名信息,那在java中,提供了哪些数据结构呢?

Java常用的集合类型有:Map、List和Set。Map是保存Key-Value对,List类似数组,可保存可重复的值,而Set只保存不重复的值,相当于是只保存key,不保存value的Map。

如果是有用户名、学号登录的操作,就可以采用Map类型的集合来存储,例如可使用key记录用户名+学号,value保存套接字。对于本篇的网络聊天室的需求,需要采用Map,用来保存不同用户的socket和登录名。用户套接字socket作为key来标识一个在线用户是比较方便的选择,因为每一个客户端的IP地址+端口组合是不一样的。

二、保证线程安全

很明显,我们需要使用到多线程技术,而在多线程环境中,对共享资源的读写存在线程并发安全的问题,例如HashMap、HaspSet等都不是线程安全的,可以通过synchronized关键字进行加锁,但还有更方便的方案:可以直接使用Java标准库的java.util.concurrent包提供的线程安全的集合。例如HashMap的线程安全是 ConcurrentHashMap,HashSet的线程安全Set是CopyOnWriteArraySet

如图,Map继承体系:

在JDK1.8中,对HashMap进行了改进,当结点数量超过TREEIFY_THRESHOLD 则要转换为红黑树,这样很大优化了查询的效率,但仍然不是线程安全的。

这里简单了解一下,具体学习可以查询相关资料。有了以上的基本知识,下面开始进入网络实时聊天室的具体实现。

三、群聊核心方法

基于前面这样的想法:实现群聊就是客户A的聊天信息通过服务器转发到同时在线的所有客户,服务器端根据HashMap记录登陆用户的socket,向所有用户转发信息。

核心的群组发送方法sendToAllMembers,用于给所有在线客服发送信息。

private void sendToMembers(String msg,String hostAddress,Socket mySocket) throws IOException{PrintWriter pw;OutputStream out;Iterator iterator=users.entrySet().iterator();while (iterator.hasNext()){Map.Entry entry=(Map.Entry) iterator.next();Socket tempSocket = (Socket) entry.getKey();String name = (String) entry.getValue();if (!tempSocket.equals(mySocket)){out=tempSocket.getOutputStream();pw=new PrintWriter(new OutputStreamWriter(out,"utf-8"),true);pw.println(hostAddress+":"+msg);}}}

使用到了Map的遍历,对其他所有用户发送信息。

相同的原理,我们实现私聊的功能,转化为实现的思想,也就是当前用户和指定用户Socket之间的通信,所以我写了一个sendToOne的方法。

private void sendToOne(String msg,String hostAddress,Socket another) throws IOException{PrintWriter pw;OutputStream out;Iterator iterator=users.entrySet().iterator();while (iterator.hasNext()){Map.Entry entry=(Map.Entry) iterator.next();Socket tempSocket = (Socket) entry.getKey();String name = (String) entry.getValue();if (tempSocket.equals(another)){out=tempSocket.getOutputStream();pw=new PrintWriter(new OutputStreamWriter(out,"utf-8"),true);pw.println(hostAddress+"私信了你:"+msg);}}
}

以上两个方法是本网络聊天室的关键,后面实现的功能将是对这两个方法的灵活运用。

四、聊天室具体设计

目前聊天室的功能定位包括:1)查看当前上线用户;2):群聊;3)私信;4)退出当前聊天状态;5)离线;6)查看帮助。

首先,初始化最关键的数据结构,作为类成员变量,HashMap用来保存Socket和用户名:

private ConcurrentHashMap<Socket,String> users=new ConcurrentHashMap();

每个功能具体实现如下:

0、用户登录服务器

这里是最开始的服务器端的信息处理,需要记录每个用户的登录信息,包括连接的套接字和自定义昵称,方便后续使用。我采用的方法是当用户连接服务器时候,提醒用户输入用户名来进一步操作,也实现了不重名的判断逻辑。代码如下:

pw.println("From 服务器:欢迎使用服务!");
pw.println("请输入用户名:");
String localName = null;
while ((hostName=br.readLine())!=null){users.forEach((k,v)->{if (v.equals(hostName))flag=true;//线程修改了全局变量});if (!flag){localName=hostName;users.put(socket,hostName);flag=false;break;}else{flag=false;pw.println("该用户名已存在,请修改!");}
}

登录成功之后会向所有在线用户告知上线信息。

1、查看当前上线用户

其实就是将服务器端记录在HashMap中的信息返回给请求用户,通过约定的命令L来查看:

if (msg.trim().equalsIgnoreCase("L")){users.forEach((k,v)->{pw.println("用户:"+v);});continue;
}

2、群聊

else if (msg.trim().equals("G")){pw.println("您已进入群聊。");while ((msg=br.readLine())!=null){if (!msg.equals("E")&&users.size()!=1)sendToMembers(msg,localName,socket);else if (users.size()==1){pw.println("当前群聊无其他用户在线,已自动退出!");break;}else {pw.println("您已退出群组聊天室!");break;}}}

3、私信

同理,处理逻辑变为一对一的通信,与之前服务器-客户端一对一类似,但是这里需要更多的处理,保证逻辑正确,包括被私聊人的在线状态,被私聊人用户名是否正确等。

//一对一私聊
else if (msg.trim().equalsIgnoreCase("O")){pw.println("请输入私信人的用户名:");String name=br.readLine();//查找map中匹配的socket,与之建立通信//有待改进,处理输入的用户名不存在的情况users.forEach((k, v)->{if (v.equals(name)) {isExist=true;//全局变量与线程修改问题}});//已修复用户不存在的处理逻辑Socket temp=null;for(Map.Entry<Socket,String> mapEntry : users.entrySet()){if(mapEntry.getValue().equals(name))temp = mapEntry.getKey();
//                            System.out.println(mapEntry.getKey()+":"+mapEntry.getValue()+'\n');}if (isExist){isExist=false;//私信后有一方用户离开,另一方未知,仍然发信息而未收到回复,未处理这种情况while ((msg=br.readLine())!=null){if (!msg.equals("E")&&!isLeaved(temp))sendToOne(msg,localName,temp);else if (isLeaved(temp)){pw.println("对方已经离开,已断开连接!");break;}else{pw.println("您已退出私信模式!");break;}}}elsepw.println("用户不存在!");
}

4、退出当前聊天状态

这个功能主要融入到群聊和私聊中,可见上面两个功能实现的内部调用,定义了一个方法isLeaved,判断用户是否已经下线。

//判断用户是否已经下线
private Boolean isLeaved(Socket temp){Boolean leave=true;for(Map.Entry<Socket,String> mapEntry : users.entrySet()) {if (mapEntry.getKey().equals(temp))leave = false;}return leave;
}

5、离线

这个功能比较简单,通过约定的命令执行。

if (msg.trim().equalsIgnoreCase("bye")) {pw.println("From 服务器:服务器已断开连接,结束服务!");users.remove(socket,localName);sendToMembers("我下线了",localName,socket);System.out.println("客户端离开。");//加当前用户名break;
}

6、查看帮助

通过命令H请求服务器的帮助,是指用户查看哪些命令对应的功能,来进行选择。

else if (msg.trim().equalsIgnoreCase("H")){pw.println("输入命令功能:(1)L(list):查看当前上线用户;(2)G(group):进入群聊;(3)O(one-one):私信;(4)E(exit):退出当前聊天状态;(5)bye:离线;(6)H(help):帮助");continue;//返回循环
}

五、聊天室服务完整代码

聊天室实现主要工作在于服务端,聚焦于服务器线程处理的内部类Hanler,上面是各个功能具体介绍,下面完整给出代码,只需要在前面文章的基础上,见Java多线程实现多用户与服务端Socket通信。

修改服务器端线程处理代码:

class Handler implements Runnable {private Socket socket;public Handler(Socket socket) {this.socket = socket;}public void run() {//本地服务器控制台显示客户端连接的用户信息System.out.println("New connection accept:" + socket.getInetAddress().getHostAddress());try {BufferedReader br = getReader(socket);PrintWriter pw = getWriter(socket);pw.println("From 服务器:欢迎使用服务!");pw.println("请输入用户名:");String localName = null;while ((hostName=br.readLine())!=null){users.forEach((k,v)->{if (v.equals(hostName))flag=true;//线程修改了全局变量});if (!flag){localName=hostName;users.put(socket,hostName);flag=false;//可能找出不一致问题break;}else{flag=false;pw.println("该用户名已存在,请修改!");}}//                System.out.println(hostName+": "+socket);sendToMembers("我已上线",localName,socket);pw.println("输入命令功能:(1)L(list):查看当前上线用户;(2)G(group):进入群聊;(3)O(one-one):私信;(4)E(exit):退出当前聊天状态;(5)bye:离线;(6)H(help):帮助");String msg = null;//用户连接服务器上线,进入聊天选择状态while ((msg = br.readLine()) != null) {if (msg.trim().equalsIgnoreCase("bye")) {pw.println("From 服务器:服务器已断开连接,结束服务!");users.remove(socket,localName);sendToMembers("我下线了",localName,socket);System.out.println("客户端离开。");//加当前用户名break;}else if (msg.trim().equalsIgnoreCase("H")){pw.println("输入命令功能:(1)L(list):查看当前上线用户;(2)G(group):进入群聊;(3)O(one-one):私信;(4)E(exit):退出当前聊天状态;(5)bye:离线;(6)H(help):帮助");continue;//返回循环}else if (msg.trim().equalsIgnoreCase("L")){users.forEach((k,v)->{pw.println("用户:"+v);});continue;}//一对一私聊else if (msg.trim().equalsIgnoreCase("O")){pw.println("请输入私信人的用户名:");String name=br.readLine();//查找map中匹配的socket,与之建立通信users.forEach((k, v)->{if (v.equals(name)) {isExist=true;//全局变量与线程修改问题}});//已修复用户不存在的处理逻辑Socket temp=null;for(Map.Entry<Socket,String> mapEntry : users.entrySet()){if(mapEntry.getValue().equals(name))temp = mapEntry.getKey();}if (isExist){isExist=false;//私信后有一方用户离开,另一方未知,仍然发信息而未收到回复,未处理这种情况while ((msg=br.readLine())!=null){if (!msg.equals("E")&&!isLeaved(temp))sendToOne(msg,localName,temp);else if (isLeaved(temp)){pw.println("对方已经离开,已断开连接!");break;}else{pw.println("您已退出私信模式!");break;}}}elsepw.println("用户不存在!");}//选择群聊else if (msg.trim().equals("G")){pw.println("您已进入群聊。");while ((msg=br.readLine())!=null){if (!msg.equals("E")&&users.size()!=1)sendToMembers(msg,localName,socket);else if (users.size()==1){pw.println("当前群聊无其他用户在线,已自动退出!");break;}else {pw.println("您已退出群组聊天室!");break;}}}elsepw.println("请选择聊天状态!");}} catch (IOException e) {e.printStackTrace();} finally {try {if (socket != null)socket.close();} catch (IOException e) {e.printStackTrace();}}}
}

六、效果演示:基于TCP的网络实时聊天室

首先,开启多个客户端,连接服务器开始进入通信状态。

下面动图演示了几个基本功能,可以看到三个用户实现了实时通信聊天,包括群聊和私聊功能。其他功能就留给大家去探索。

结语

系列文章从入门开始,不断完善C/S架构的Socket通信,回忆一下,首先是实现了Server和Client的互相通信,在这个过程发现问题,接着就使用多线程技术解决客户端实时接收信息的问题,后来到了服务器端,发现多用户连接服务器的“先到先得”问题,“后到者”无法正常通信,所以再使用线程池技术解决了多用户服务器的问题。

本篇基本实现了一个简单的客户端-服务器应用,使用客户端-服务器(C/S架构),结合多线程技术,模拟类似QQ、微信聊天功能,实现一个网络实时聊天室。

学习到的知识有:多线程、线程池、Socket通信、TCP协议、HashMap、JavaFX等,所有知识的结合运用,并通过实战练习,一步步理解知识!

!!!本项目上传微信公众号,可获取啦:关注我的博客下方微信公众号,发送“实时聊天室”获取

如果觉得不错欢迎“一键三连”哦,点赞收藏关注,有问题直接评论,交流学习!

Java实现socket通信网络编程系列文章:

  1. 基于UDP协议网络Socket编程(java实现C/S通信案例)【基于UDP协议网络Socket编程(java实现C/S通信案例)_陆海潘江小C的博客-CSDN博客_基于udp的socket编程。编写一个基于udp协议的socket网络通信应用程序,实现如下功】
  2. 基于TCP协议网络socket编程(java实现C/S通信)【基于UDP协议网络Socket编程(java实现C/S通信案例)_陆海潘江小C的博客-CSDN博客_基于udp的socket编程。编写一个基于udp协议的socket网络通信应用程序,实现如下功】
  3. Java多线程实现TCP网络Socket编程(C/S通信)【Java多线程实现TCP网络Socket编程(C/S通信)_陆海潘江小C的博客-CSDN博客_socket tcp 多线程发送】
  4. Java多线程实现多用户与服务端Socket通信【Java多线程实现多用户与服务端Socket通信_陆海潘江小C的博客-CSDN博客_java socket 多用户】

我的CSDN博客:Java进阶:基于TCP的网络实时聊天室(socket通信案例)_陆海潘江小C的博客-CSDN博客

Java进阶:基于TCP的网络实时聊天室(socket通信案例)相关推荐

  1. 基于TCP的网络实时聊天室(socket通信案例)

    开门见山 一.数据结构Map 二.保证线程安全 三.群聊核心方法 四.聊天室具体设计 0.用户登录服务器 1.查看当前上线用户 2.群聊 3.私信 4.退出当前聊天状态 5.离线 6.查看帮助 五.聊 ...

  2. java: java mina ——基于TCP/IP、UDP/IP协议栈的通信框架

    Apache Mina Server 是一个网络通信应用框架,也就是说,它主要是对基于TCP/IP.UDP/IP协议栈的通信框架(当然,也可以提供JAVA 对象的序列化服务.虚拟机管道通信服务等),M ...

  3. Java NIO基于控制台的多人聊天室

    闲来无事写了个基于NIO的聊天室项目,费话不说了,直接贴代码吧. Server端代码如下: package com.xz.helloworld.nettyt.nio.im;import java.io ...

  4. linux多人聊天室 qt,Qt编程详解--网络通信之基于TCP的多人聊天室

    一.了解TCP的通信过程 Qt中封装了TCP协议 QTcpServer类负责服务端: 1.创建QTcpServer对象 2.监听listen需要的参数是地址和端口号 3.当有新的客户端连接成功时会发射 ...

  5. 基于UDP/IP协议的聊天室

    基于TCP/IP通信协议的聊天室 概述: 基于TCP/IP通信协议的聊天室是通过服务端/客户端的模式进行的,必须是服务端开启之后,客户端通过获取服务端的ip和端口号并连接之后才能进行信息的共享,具体的 ...

  6. java毕业设计——基于java+TCP+UDP的局域网聊天室系统设计与实现(毕业论文+程序源码)——局域网聊天室系统

    基于java+TCP+UDP的局域网聊天室系统设计与实现(毕业论文+程序源码) 大家好,今天给大家介绍基于java+TCP+UDP的局域网聊天室系统设计与实现,文章末尾附有本毕业设计的论文和源码下载地 ...

  7. 基于TCP的网络聊天室实现(C语言)

    基于TCP的网络聊天室实现(C语言) 一.网络聊天室的功能 二.网络聊天室的结果展示 三.实现思路及流程 四.代码及说明 1.LinkList.h 2.LinkList.c 3.client.c 4. ...

  8. 【Linux网络编程】基于UDP实现多人聊天室

    文章目录 一.UDP的概念 1.1 UDP 1.2 UDP特点 二. 采用UDP实现多人聊天室原因 三.多人聊天室项目功能 四.实现多人聊天室项目流程分析 4.1 前期准备 4.1.1 定义结构体 4 ...

  9. 基于阿里云用C/C++做了一个http协议与TCP协议的web聊天室的服务器——《干饭聊天室》

    基于阿里云用C/C++做了一个http协议与TCP协议的web聊天室的服务器--<干饭聊天室> 在这里首先感谢前端小伙伴飞鸟 前端技术请看一款基于React.C++,使用TCP/HTTP协 ...

最新文章

  1. 计算机图形学曲线生成原理,计算机图形学_曲线及生成.ppt
  2. Python发送邮件(带附件)
  3. webstorm代码行数统计_10万行代码10万年薪,利用python查看自己写了多少代码
  4. 编写自己的Shell解释器
  5. 钢材种类有哪些?怎么分类
  6. Python中使用cutecharts实现简单的手绘风格的图表
  7. 【译】Activitys, Threads和 内存泄露
  8. HttpUrlConnection使用详解--转
  9. 387. First Unique Character in a String
  10. 机器学习之RandomForest(随机深林)原理与实战
  11. javascript入门系列演示·三种弹出对话框的用法实例
  12. mysql 存储过程执行ddl_mysql存储过程执行ddl语句
  13. java集合输入存储_Java练习IO流使用Properties集合存储数据并...
  14. Beta版本冲刺(四)
  15. postgresql 查询sql字符串拼接相关
  16. JAVA计算机毕业设计疫情监测管理系统Mybatis+源码+数据库+lw文档+系统+调试部署
  17. python 调用matlab 数据类型问题
  18. editplus java显示竖线_EditPlus对齐竖线怎么调出来?
  19. 《写给大家看的设计书》读书笔记
  20. 江在川上曰:JS函数

热门文章

  1. java毕业设计基于VUE的个人记账管理系统mybatis+源码+调试部署+系统+数据库+lw
  2. 【Java EE】二手书交易平台
  3. 计算机里的公共汽车(总线)
  4. cad计算机绘图知识点,cad制图初学入门方法与技巧
  5. java海伦公式求三角形面积_海伦公式求三角形面积出错求教
  6. msm android机型适配,魅族系统适配全机型了,Flyme8 安卓9高通通刷包,通用适配...
  7. 深度学习推荐系统综述
  8. 企业管理软件开发管理中的沟通
  9. 数据查询(1)-简单查询
  10. iOS 类似亲宝宝app下拉刷新动画效果