多线程聊天室

项目名称

  • 多线程聊天室

项目简介

  • 多个用户可以在同一局域网下进行聊天.

  • 类图:
    在这里插入图片描述

  • 项目执行流程

客户端通过对应端口号,和IP地址t与服务器端建立连接后,进行注册,然后通过输入输出流,进行通信。
服务器端收到信息后,进行解析,判断客户端的需求(私聊,群聊,下线等),从而进行相应的处理。
因为采取多线程的方式,服务器端,线程比较多,采取线程池的方式进行处理。每个客户端仅需要一个发送和一个接受线程,而且也不牵扯什么资源共享的问题,直接继承Thread类来实现多线程。
用一个ConcurrentHashMap存储当前在线用户信息。key–Socket,Value–String(用户名)

项目基本功能

  • 注册
  • 私聊
  • 群聊
  • 下线
  • 显示当前在线的用户

项目扩展功能

  • 参数灵活化。将IP地址,端口号,线程池的线程数通过外界传入。
  • 添加登录功能。用户注册过之后,下线之后,下次直接登陆即可,无需重新注册。
  • 收到历史私聊信息。用户下线之后,有人私聊对其发消息,当用户再次上线后,可以查收到。
  • 客户端异常关闭,服务器端进行相应的处理。

扩展方案

实现参数灵活化:

  • 通过键盘将参数运行时读入。
  • 通过main方法的args参数传入。
  • 通过文件读入。
  • 通过数据库传入。

最终选择main方法的参数进行传入,因为需要的参数(端口号,IP地址,线程数目)比较少,且很方便,便于施行。

实现登录功能

  • 在HandelClient类中,添加一个静态内部类User,存放用户的用户名和密码及与对象相关联的一个消息文件(文件后期实现上线后收到私聊消息有用)。
  • 在HandelClient类中,添加一个静态常量USER_SET,本身是一个CopyOnWriteArraySet(线程安全的Set集合)。
  • 用户进行注册时,就用用户的信息生成User对象,存进Set中。之所以选择Set是因为Set里面的元素不可重复,且查询时间复杂度O(1).
  • 用户在选择登陆功能时,只需判断Set中是否包含该对象即可,存在就将该用户放进ConcurrentHashMap中就OK了。

实现收到历史私聊信息。

业务需求说明,用户下线之后,有人对其私聊,将该信息存储起来,待用户再次上线后,将消息发过去。

  • 在进行私聊时,判断用户想要聊天的目标用户,如果该用户在ConcurrentHashMap中,证明该用户还在线,否则判断是不是在CopyOnWriteArraySet中,在就说明该目标用户是一个合法的注册过的.
  • 因此将相应的消息存储在目标用户的文件中,当该目标用户上线之后,将所有消息发送过去,并且将文件内容清空(很重要)。

客户端异常关闭,服务器端进行相应的处理。

待续…

遇到的问题及解决方案

  • 问题一:客户端收不到消息(1.服务器没发过来? 2.客户端读不来?)
    1.没有使用println。
    2.使用了仍然没有发出去,没有flush。
  • 问题二:一次注册,终生使用
    程序结束时,将用户名和密码按行写进文件
    程序运行时,从文件里面按行都出来,拆分生成新对象,放进Set集合中。
  • 问题三:流采用自动关闭的方式
    流关闭的太快了,导致Socket中断。最终只能采取手动关闭。
  • 问题四:IP地址的验证
    相对来说比较简单,字符串拆分加判断就OK了。
  • 文件内容的问题
    用户上线之后,将私聊消息发过去之后,需要将文件内容清空,否则下次依然发原始的信息。

项目源码

https://github.com/excellent01/Java-Code

项目难点

  • 实现登陆功能,
  • 下线后再次上线可以收到私聊消息。

知识体系

  • Java集合框架
  • JUC包下的线程安全的集合
  • 多线程
  • Java网络编程
  • JavaIO
  • 其它知识

效果展示

服务器端启动

在这里插入图片描述

客户端启动并注册

在这里插入图片描述

群聊功能

在这里插入图片描述

登录功能及上线后收到私聊信息

在这里插入图片描述

不足之处

因为暂时没有学习数据库,不能将用户最终的信息,存入数据库,只能使用文件代替.

小段代码:IP地址的校验

private static boolean hostIsLegal(String host) {
   if(host == null)
    return true;
   String[] arr = host.split("\\.");
   if(arr.length != 4){
       return false;
   }
   for(String str : arr){
       try{
           int num = Integer.parseInt(str);
           if(num < 0 || num > 255){
               return false;
           }
       }catch (NumberFormatException e){
           return false;
       }
   }
   if(host.endsWith("\\.")){
       return false;
   }
   return true;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章