网络程序设计课程报告——基于websocket的简单聊天室

一、开发环境与技术栈

  • Windows
  • WebSocket
  • SpringBoot
  • Vue
  • Session
  • Ajax

二、项目功能

主要业务

  • 实现多人在线群聊,记录并管理所有的聊天信息
  • 用户登录,打开主页可以看见登录界面,可以识别用户是否登录
  • 登录成功进入主界面,显示聊天窗口和用户信息列表
  • 每个用户都可以发送信息,并且可以接受到他人的信息
  • 历史消息,每个用户都可以看见历史消息列表

核心技术要点

  • 利用websocket实现消息推送机制
  • 使用session自动识别用户是否登录
  • 通过ajax实现数据异步调用
  • 利用vue整合前端
  • 利用springboot整合后端

三、项目展示

四、具体实现

1. 前端

1) 登录界面

  • 基于element-ui的简单form表单,用户需要填写用户名和密码
<template>
  <div style="width: 1920px;height:1080px;display: flex;align-items:center;justify-content:center">
    <el-form :model="form" status-icon :rules="rules" ref="form" label-width="80px" style="height: 600px;width: 400px">
      <el-form-item label="用户名" prop="username">
        <el-input v-model="form.username"></el-input>
      </el-form-item>
      <el-form-item label="密码" prop="password">
        <el-input type="password" v-model="form.password"></el-input>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="submitForm">登录</el-button>
        <el-button @click="resetForm">重置</el-button>
      </el-form-item>
    </el-form>
  </div>
</template>
  • 点击登录按钮,进入submitForm方法,将用户信息存入sessionStorage中并进入聊天界面,在聊天界面中可以获取sessionStorage
      submitForm() {
        this.$refs['form'].validate((valid) => {
          if (valid) {
            sessionStorage.setItem('user', this.form.username)
            this.$router.push('/chat')
          } else {
            console.log('error submit!!');
            return false;
          }
        });
  • 点击重置,将form表单清空
      resetForm() {
        this.form = null
      }

2)聊天界面

  • 使用element-ui组件构建聊天界面,效果如上文所示
  • 使用钩子函数延迟加载init方法,若未在sessionStorage中找到用户信息,则跳转回到登录界面,找到则寻找websocket的url开启websocket服务,浏览器接受消息获取服务端的信息,有用户列表或者消息列表,将接受到的列表存入前端定义的列表中
      init() {//session
        // 如果sessionStorage中没有用户信息,则跳转登录页面
        this.user = sessionStorage.getItem('user')
        if (!this.user) {
          this.$router.push('/')
        }
        let that = this;
        if (typeof (WebSocket) == "undefined") {
          console.log("您的浏览器不支持WebSocket");
        } else {
          console.log("您的浏览器支持WebSocket");
          let socketUrl = "ws://localhost:8888/socket/" + this.user;
          if (socket != null) {
            socket.close();
            socket = null;
          }
          // 开启一个websocket服务
          socket = new WebSocket(socketUrl);
          //打开事件
          socket.onopen = function () {
            console.log("websocket已打开");
          };
          //  浏览器端收消息,获得从服务端发送过来的文本消息
          socket.onmessage = function (msg) {
            console.log("收到数据====" + msg.data)
            let data = JSON.parse(msg.data)
            if (data.userNames) {
              // userNames存在则是有人登录,获取在线人员信息
              that.userList = data.userNames
            } else {
              // userNames不存在则是有人发消息
              that.msgList.push(data)
            }
          };
          //关闭事件
          socket.onclose = function () {
            console.log("websocket已关闭");
          };
          //发生了错误事件
          socket.onerror = function () {
            console.log("websocket发生了错误");
          }
        }
      }
  • 点击发送按钮,与前端发送框双向绑定,向所有用户发送消息
      send() {
        if (!this.message.msg) {
          this.$message({
            message: '大兄弟,请输入聊天消息!',
            type: 'warning'
          });
        } else {
          if (typeof (WebSocket) == "undefined") {
            console.log("您的浏览器不支持WebSocket");
          } else {
            console.log("您的浏览器支持WebSocket");
            this.message.from=this.user;
            this.message.time=new Date().toLocaleTimeString();
            socket.send(JSON.stringify(this.message));
            this.message.msg = '';
          }
        }
      }

3)路由设置

  • 设置两个登录和聊天两个路由,默认为登录界面
export default new Router({
  routes: [
    {
      path: '/',
      name: 'login',
      component: login
    },
    {
      path: '/chat',
      name: 'chat',
      component: chat
    }
  ]
})

2. 后端

1)Message类

  • 创建信息类Message,在类中定义用户名、信息、发送方、接收方、时间
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Message {
//    时间
    private String time;
//    接收方
    private String to;
//    发送方
    private String from;
//    消息
    private String msg;
//    登录用户名
    private List<String> userNames;
}

2)WebSocket服务

  • 使用sessionMap存储每个用户对象,用户名对应一个session对象
    public static final Map<String, Session> sessionMap = new ConcurrentHashMap<>();
  • 建立连接,将登录用户存储在sessionMap中,发给所有人当前的userList
    @OnOpen
    public void onOpen(Session session, @PathParam("username") String username) {
        System.out.println("当前用户名=="+username);
        sessionMap.put(username, session);
        // 发送登录人员消息给所有的客户端
        sendAllMessage(setUserList());
    }
  • 设置用户列表,遍历sessionMap,设置用户消息类用户名,格式化为字符串格式
    private String setUserList(){
        ArrayList<String> list = new ArrayList<>();
        for(String key:sessionMap.keySet()){
            list.add(key);
        }
        Message message = new Message();
        message.setUserNames(list);
        return JSON.toJSONString(message);
    }
  • 发送消息,遍历sessionMap中的session域,将message发送给所有的session,若想指定发送,可以选择特定的session
//    服务端发送消息给指定客户端
    private void sendMessage(String message, Session toSession) {
        try {
            toSession.getBasicRemote().sendText(message);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

//   服务端发送消息给所有客户端
    private void sendAllMessage(String message) {
        try {
            for (Session session : sessionMap.values()) {
                session.getBasicRemote().sendText(message);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
  • 接受消息,将消息格式为字符串,解析消息为java对象,根据session进行转发
    @OnMessage
    public void onMessage(String message) {
//        解析消息为java对象
        Message msg = JSON.parseObject(message, Message.class);
        if(StringUtils.isEmpty(msg.getTo())){
            sendAllMessage(JSON.toJSONString(msg));
        }else{
            Session sessionTo = sessionMap.get(msg.getTo());
            sendMessage(message,sessionTo);
        }
    }
  • 关闭连接,将用户信息从sessionMap中删除,并向所有人发送当前的用户列表
    @OnClose
    public void onClose(@PathParam("username") String username) {
        sessionMap.remove(username);
        sendAllMessage(setUserList());
    }

3)Session拦截

  • 设置session拦截,对未登录的用户拒绝访问,跳转到登录界面,并且识别已登录用户24小时内自动访问
@Configuration
public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        // 设置允许跨域的路由
        registry.addMapping("/**")
                // 设置允许跨域请求的域名
                .allowedOriginPatterns("*")
                // 是否允许证书(cookies)
                .allowCredentials(true)
                // 设置允许的方法
                .allowedMethods("*")
                // 跨域允许时间
                .maxAge(24*3600);
    }

}

五、项目总结

这个项目实现了多人的网络聊天功能,使用sessionMap记录用户信息,实现了基本的登录和聊天功能,已存在的功能实现和界面的设计都是比较好的,但是缺少了足够强大的数据库来作为支撑,只能支持实时聊天,既不能保存用户群组信息,也不能在用户离线时接收消息,等待用户连接后再推送给用户。但是尽管如此,这个项目的可拓展性还是很强的,它拥有一套简单而强大的数据交换协议。
在技术方面,此项目使用了网络程序设计课程中所学到的知识,基本满足了专题实验的需求。但是仍然发现了个人在项目开发时有一些缺点,比如在需求分析时有时分析的不够深入,在软件设计过程中也存在犯错的地方。此外由于时间关系这个项目还有很多不完善的地方比如异常处理,还有很多代码逻辑有待优化。
感谢孟宁老师的教导,孟老师的网络程序设计,让我改变了对网络刻板的枯燥印象,并对此产生了浓烈的兴趣。同时,孟老师的授课风格也给我带来了以往所收获不到的惊喜,同学们可以自由地发言,孟老师也极其鼓励大家有不同的想法。一个阶段的结束意味着下一个阶段的开始,在以后的学习开发中要吸取从这次课程设计过程中的经验教训,戒骄戒躁,砥砺前行。

  • 25
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值