使用websocket实现群聊(多个群)

版权声明:不短不长八字刚好@wzy https://blog.csdn.net/qq_38089964/article/details/81541846

最近一个项目中需要用到一个用户实时聊天需求:需要很多用户(在不同的房间)进行实时聊天,也就是一个简单的聊天室,这里用的是websocket实现。

这里需要对每一个连接都指定两个参数:用户的userId和所加入的房间id(roomId);

@ServerEndpoint("/community/{ro_user}")使用{ro_user}来绑定请求参数,不同的用户连接的时候就把参数加入到连接的后面。这个参数在四个方法都可以绑定到具体参数,这个呢我就只是在onopen的时候绑定一次,然后把参数信息存放到私有变量中,因为用到的是两个整型参数,所以前端使用‘-’把两个参数拼在一起,在后台只需分割成两个参数。

package cn.wzy.sport.controller;

import cn.wzy.sport.entity.User_Message;
import cn.wzy.sport.service.User_MessageService;
import lombok.extern.log4j.Log4j;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * Create by Wzy
 * on 2018/8/6 13:24
 * 不短不长八字刚好
 */
@ServerEndpoint("/community/{ro_user}")
@Log4j
public class CommunityController {


    private static final User_MessageService service;

    static {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
        service = ((User_MessageService) ctx.getBean("user_MessageServiceImpl"));
    }

    private static final Map<Integer, CopyOnWriteArraySet<CommunityController>> rooms = new HashMap<>();

    private Session session;

    private Integer userId;

    private Integer roomId;

    @OnOpen
    public void onOpen(@PathParam(value = "ro_user") String ro_user, Session session) {
        this.session = session;
        String[] param = ro_user.split("-");
        this.roomId = Integer.parseInt(param[0]);
        this.userId = Integer.parseInt(param[1]);
        CopyOnWriteArraySet<CommunityController> friends = rooms.get(roomId);
        if (friends == null) {
            synchronized (rooms) {
                if (!rooms.containsKey(roomId)) {
                    friends = new CopyOnWriteArraySet<>();
                    rooms.put(roomId, friends);
                }
            }
        }
        friends.add(this);
    }

    @OnClose
    public void onClose() {
        CopyOnWriteArraySet<CommunityController> friends = rooms.get(roomId);
        if (friends != null) {
            friends.remove(this);
        }
    }

    @OnMessage
    public void onMessage(final String message, Session session) {
        //新建线程来保存用户聊天信息
        new Thread(new Runnable() {
            @Override
            public void run() {
                service.save(new User_Message(0, userId, message, roomId, new Date()));
            }
        }).start();


        CopyOnWriteArraySet<CommunityController> friends = rooms.get(roomId);
        if (friends != null) {
            for (CommunityController item : friends) {
                item.session.getAsyncRemote().sendText(message);
            }
        }
    }

    @OnError
    public void onError(Session session, Throwable error) {
        log.info("发生错误" + new Date());
        error.printStackTrace();
    }
}
CopyOnWriteArraySet<CommunityController> friends = rooms.get(roomId);
        if (friends == null) {
            synchronized (rooms) {
                if (!rooms.containsKey(roomId)) {
                    friends = new CopyOnWriteArraySet<>();
                    rooms.put(roomId, friends);
                }
            }
        }
        friends.add(this);

这里是用到一个map<房间id, 用户set>来保存房间对应的用户连接列表,当有用户进入一个房间的时候,就会先检测房间是否存在,如果不存在那就新建一个空的用户set,再加入本身到这个set中;这里需要考虑线程安全问题,因为用到的是一个hashMap,如同时又AB两个用户加入一个空房间,同时访问friends为空,然后都会新建一个set再加入进去,那么可能会出现一个情况就是A检测不存在房间,然后创建加入进去,B也同时检测到不存在,也重新创建一个用户set,这样就会覆盖原来的set,也就是说A用户就加入失败。

现在设定的逻辑就保证了不存在的情况下就采用阻塞同步,保证只能一个线程新建房间,这里可能会有一个疑惑:同步块是检测了房间为空之后才进去的,为什么还要检测这个房间是否存在呢?这是因为上一句过去friends的语句:CopyOnWriteArraySet<CommunityController> friends = rooms.get(roomId)没采用任何同步措施,可能会产生AB两个线程同时访问这句,然后得出friends都是空,然后一个线程进入同步块,另一个则等待,如果不在后面再次检测是否存在该房间,那么阻塞的线程仍然会重新覆盖值。

用户连接set用的是一个CopyOnWriteArraySet,这个特点就是在写的时候会把所有数据都复制到另外一个数组中,进行修改之后再把原来set中数组指针指向新的数组,这个add方法是采用了lock的同步策略,所以不需要我们考虑线程安全问题。

效果:这里是用在线的websocket测试的

有三个连接:

连接1:

连接2:

连接3:

这样就实现了不同房间的所有人聊天,其他房间就收不到聊天信息。

猜你喜欢

转载自blog.csdn.net/qq_38089964/article/details/81541846