websocket实现多聊天室,聊天(附完整代码)

需求

1、最开始想到是今年的课程设计,想着做个有点意思的东西,发现关于websocket技术没有接触过,就搞了类似一个QQ的多聊天室聊天的小项目。
2、需要实现,登录,注册,等等等…当然最核心,最基础的还是我们的聊天的实现
3、采用的技术,后端java,前端vue

关于websocket

1、WebSocket是一种在单个TCP连接上进行全双工通信的协议
2、WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
3、简单来说,就是客户端给服务器端发消息,服务器端会有监听,并可以再给客户端发消息,客户端也有监听,对服务器端的消息进行处理。甲——>服务端——>其他用户

websocket的方法

后端

1、引入依赖

       <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>

2、在自己的websocket上加ServerEndpoint注解

因为要实现多聊天室,多用户聊天,所以,需要一个当前聊天室的id(page),以及当前用户id(userId)

@ServerEndpoint(value = "/webSocket/{page}/{userId}")

3、监听的方法

1、@OnOpen(连接建立时触发)
2、@OnClose(关闭连接时触发)
3、@OnMessage( 收到客户端消息触发事件)
4、@OnError(通信发生错误时触发)

4、发送消息的方法(利用Session会话)

1、在建立连接时触发@OnOpen,

将当前用户的会话存到一个map集合<房间id,该房间内用户sessionId集合set>

  • 如果不实现,多聊天室,set集合即可
2、收到客户端消息时,将该消息进行转发,
//遍历集合,将消息给该集合中的所有人
for (Session s : sessions) {
    
    
	// if (!s.getId().equals(session.getId()))  可以加判断,不发给谁,		
       s.getBasicRemote().sendText(JSONUtil.toJsonStr(contentBo));
}

前端

1、实例化websocket

1、利用vue在data里面,定义一个变量socket
2、进行实例化操作,之后便可利用socket进行操作

 if (typeof (WebSocket) === "undefined") {
    
    
 		alert("您的浏览器不支持socket")
 } else {
    
    
     // 实例化socket
     	this.socket = new WebSocket("ws://url(服务地址) + webSocket/" + page + "/" +userId)
 }

2、监听的方法

3、4方法需要进行实时监听,建议写在vue 生命周期的mounted里面

1、websocket.onopen(建立连接成功时触发)
2、websocket.onclose(连接关闭时触发)
3、websocket.onmessage(收到服务器的数据触发)
4、websocket.onerror(连接出现错误时触发)

3、发送消息的方法

 send: function (msg) {
    
    
 	this.socket.send(msg)
 }

websocket核心代码

后端

package cn.itcast.config;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.json.JSONUtil;
import cn.itcast.api.bo.ContentBo;
import cn.itcast.api.entity.Content;
import cn.itcast.api.entity.User;
import cn.itcast.api.service.IContentService;
import cn.itcast.api.service.IUserService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;


import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @author: zpt
 * @Date: 2019-09-20 15:12
 * @Description:
 */
@Component
@ServerEndpoint(value = "/webSocket/{page}/{userId}")
public class WebSocket {
    
    

    private static IUserService userService;

    @Autowired
    public void setMyServiceImpl(IUserService userService){
    
    
        WebSocket.userService = userService;
    }


    private static IContentService contentService;

    @Autowired
    public void setMyServiceImpl(IContentService contentService){
    
    
        WebSocket.contentService = contentService;
    }


    private Logger log = LoggerFactory.getLogger(this.getClass());

    /**
     * 用来记录房间及房间内用户session_id
     */
    private static Map<String, Set> roomMap = new ConcurrentHashMap(8);

    /**
     * 用来记录session_id与用户id之间的关系
     */
    private static Map<String, Integer> userIdMap = new ConcurrentHashMap(8);


    @OnOpen
    public void open( @PathParam("page") String page, Session session, @PathParam("userId") Integer userId ) throws IOException {
    
    
        Set set = roomMap.get(page);
        userIdMap.put(session.getId(), userId);
        // 如果是新的房间,则创建一个映射,如果房间已存在,则把用户放进去
        if (set == null) {
    
    
            set = new CopyOnWriteArraySet();
            set.add(session);
            roomMap.put(page, set);
        } else {
    
    
            set.add(session);
        }
    }

    @OnClose
    public void close( @PathParam("page") String page, Session session, @PathParam("userId") Integer userId ) {
    
    
        userIdMap.remove(session.getId());
        // 如果某个用户离开了,就移除相应的信息
        if (roomMap.containsKey(page)) {
    
    
            roomMap.get(page).remove(session);
        }
    }

    @OnMessage
    public void reveiveMessage( @PathParam("page") String page, Session session, String message ) throws IOException {
    
    
        log.info("接受到用户{}的数据:{}", session.getId(), message);

        Integer userId = userIdMap.get(session.getId());

        //存入数据库中
        Content content = new Content();
        content.setRoomId(Integer.valueOf(page));
        content.setContent(message);
        content.setSendTime(LocalDateTime.now());
        content.setSendId(userId);
        contentService.save(content);

        //查询当前用户信息
        User user = userService.getById(userId);

        //返回给前端的Bo对象
        ContentBo contentBo = new ContentBo();
        BeanUtil.copyProperties(content, contentBo);
        BeanUtil.copyProperties(user, contentBo);

        // 拼接一下用户信息
        //String msg = session.getId() + " : " + message;
        Set<Session> sessions = roomMap.get(page);
        // 给房间内所有用户推送信息
        for (Session s : sessions) {
    
    
            if (!s.getId().equals(session.getId())) {
    
    
                s.getBasicRemote().sendText(JSONUtil.toJsonStr(contentBo));
            }
        }
    }


    @OnError
    public void error( Throwable throwable ) {
    
    
        try {
    
    
            throw throwable;
        } catch (Throwable e) {
    
    
            log.error("未知错误");
        }
    }
}

前端(首先要在created里面获取到当前聊天的roomId,以及当前用户userId!!!)

    new Vue({
    
    
        el: '#app',
        data: {
    
    
            "roomId": null,
            "userId": null,
            "websocket": null,
        },
        created() {
    
    
        	//因为我是html页面引入的vue,因此页面之间的数据直接用了,location.href = ""
        	//getQueryVariable方法起到了一个接受上一个页面传的参数的作用
            this.userId = this.getQueryVariable('userId')
            this.roomId = this.getQueryVariable('roomId')
        },

        mounted() {
    
    
            // 初始化
            this.init()
        },

        computed() {
    
    

        },
        methods: {
    
    
            init: function () {
    
    
                if (typeof (WebSocket) === "undefined") {
    
    
                    alert("您的浏览器不支持socket")
                } else {
    
    
                    // 实例化socket
                    this.socket = new WebSocket("ws://url(服务端地址)/webSocket/" + this.roomId + "/" + this.userId)
                    // 监听socket连接
                    this.socket.onopen = this.open
                    // 监听socket错误信息
                    this.socket.onerror = this.error
                    // 监听socket消息
                    this.socket.onmessage = this.getMessage
                }
            },
            open: function () {
    
    
                console.log("socket连接成功")
            },
            error: function () {
    
    
                console.log("连接错误")
            },
            send: function (msg) {
    
    
                this.socket.send(msg)
            },
            close: function () {
    
    
                console.log("socket已经关闭")
            },
            // 获取多个参数的时候
            getQueryVariable: function (variable) {
    
    
                var query = window.location.search.substring(1);
                var vars = query.split("&");
                for (var i = 0; i < vars.length; i++) {
    
    
                    var pair = vars[i].split("=");
                    if (pair[0] == variable) {
    
     return pair[1]; }
                }
                return (false);
            },
        destroyed() {
    
    
            // 销毁监听
            this.socket.onclose = this.close
        },
    }
)

遇到的问题

1、websocket不能正常注入service(直接@Autowired),需采用以下方式

https://blog.csdn.net/qq_43532386/article/details/111783423

2、后端服务器部署,websocket不能正常使用,需要在nginx里面配置一些东西

https://blog.csdn.net/qq_43532386/article/details/111784152

3、实现聊天的时候,聊天界面的滚动条,vuejs不能控制到最下边

https://blog.csdn.net/qq_43532386/article/details/111784809

完整代码(ui设计比较简略)

链接:https://pan.baidu.com/s/1FWnDR-psILqPEC4XLeRs6w
提取码:jqzq

文件上传,直接上传的服务器,通过配置nginx进行访问。
整体仅供参考,实用意义不大!!!

猜你喜欢

转载自blog.csdn.net/qq_43532386/article/details/111773941