如何快速实现一个聊天室?

1,976 阅读3分钟

前些天做了一个网站:modubox.cn 其中有个群聊插件,许多人问如何实现的。这里简单说下,为了快速完成群聊功能,我选择从最简单的 WebSocket 开始。

什么是WebSocket ?

既然要使用它,就需要了解一下它吧。WebSocket其实也是一种基于TCP的网络协议,它与HTTP协议最大的不同是:是一种双向通信协议,在建立连接后,WebSocket服务器端和客户端都能主动向对方发送或接收数据,而HTTP协议只能客户端主动发起通信。

所以WebSocket能够用于聊天,当然其他地方也能应用,如果做客服系统或推送消息都可以从这里开始。

如何实现单聊/群聊?

群聊:所有客户端的消息发送到服务器,服务端将消息发送给所有客户端。

单聊:WebSocket客户端之间是无法直接通信的,想要通信,必须由服务端转发。

群聊单聊

群聊单聊

实现

1. 引入WebSocket的支持

我们使用当前最流行的Spring Boot框架构建项目,然后引入Spring Boot 对 WebSocket 的支持:

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

2. 开启WebSocket

@Configuration
public class WebSocketConfig {

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

3. 服务端

这里主要有以下几点:

  1. 声明服务端点路径

  2. 存储所有连接用户,等待匹配用户

  3. 连接 onOpen,消息OnMessage,关闭onClose,错误onError 方法

  4. 发送消息给特定连接者

    @ServerEndpoint(value = "/websocket/random/") @Component public class ChatRandomServer { //所有连接 public static ConcurrentHashMap<String, ChatRandomServer> webSocketSet = new ConcurrentHashMap<>(); //与某个客户端的连接会话,需要通过它来给客户端发送数据 private Session session; //所有在配对的ID private static List webSocketLiveList = new CopyOnWriteArrayList(); //自己的id标识 private String id = ""; //连接对象的id标识 private String toUser = "";

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session) {
        session.setMaxIdleTimeout(3600000);
        this.session = session;
        //获取用户ip
        String ip = IpUtil.getRemoteAddress(session);
        this.id = ip;
        ChatRandomServer put = webSocketSet.put(this.id, this);
        //如果已经在队里,就不去找对象
        if (put == null) {
            try {
                if (pair()) {
                    sendMessage("匹配成功");
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        } else {
            try {
                sendMessage("匹配失败");
                webSocketSet.remove(this.id); 
                session.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
    
        }
        log.info("用户{}加入!当前在线人数为: {}", this.id, webSocketSet.size());
    
    }
    
    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        ChatRandomServer UserId = webSocketSet.get(toUser);
        webSocketLiveList.remove(this.id);
        if (UserId != null) {
            try {
                sendToUser(session, "对方已离开", toUser);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        webSocketSet.remove(this.id);
        log.info("{}连接关闭!当前在线人数:{}, 当前在匹配的人数:{}" ,this.id,webSocketSet.size(), webSocketLiveList.size());
    }
    
    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        log.info("来自 {} 的消息: {}", this.id, message);
        try {
            ChatRandomServer.sendToUser(session, message, toUser, 2);
        } catch (IOException e) {
            e.printStackTrace();
        }
    
    }
    
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("发生错误");
        error.printStackTrace();
        try {
            SendSelf(session,"服务器出现错误");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    /**
     * 发送消息给自己
     */
    public void sendMessage(String message) throws IOException {
        SendSelf(this.session, message);
    }
    private static void SendSelf(Session session, String message) throws IOException {
        session.getBasicRemote().sendText(message);
    }
    
    /**
     * 发送信息给指定ID用户
     */
    public static void sendToUser(Session session, String message, String sendUserId) throws IOException {
        ChatRandomServer UserId = webSocketSet.get(sendUserId);
        if (UserId != null) {
            UserId.sendMessage(message);
        } else {
            SendSelf(session, "发送失败");
        }
    }
    
    /**
     * 通知除了自己之外的所有人
     */
    private void sendOnlineCount(String message) {
        for (String key : webSocketSet.keySet()) {
            try {
                if (key.equals(id)) {
                    continue;
                }
                webSocketSet.get(key).sendMessage(message);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    /**
     * 发送信息给所有人
     */
    public void sendToAll(String message) throws IOException {
        for (String key : webSocketSet.keySet()) {
            try {
                webSocketSet.get(key).sendMessage(message);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    public synchronized boolean pair() throws IOException {
        //是否存在等待匹配的用户
        if (webSocketLiveList.size() > 0) {
            //随机匹配一个
            Random ra = new Random();
            int nextInt = ra.nextInt(webSocketLiveList.size());
            toUser = webSocketLiveList.get(nextInt);
    
            try {
                ChatRandomServer UserId = webSocketSet.get(toUser);
                UserId.setToUser(id);
                sendToUser(session, "配对成功", toUser);
            } catch (IOException e) {
                e.printStackTrace();
            }
            webSocketLiveList.remove(nextInt);
            return true;
        }
        //没有匹配的,则将自己加入等待匹配队列
        webSocketLiveList.add(id);
        return false;
    }
    

    }

4. 前端支持

 start: function () {
        if (typeof (WebSocket) === "undefined") {
          alert("您的浏览器不支持socket")
        } else {
          // 实例化socket
          this.socket = new WebSocket(`ws://localhost:8082/websocket/room`);
          // 监听socket连接
          this.socket.onopen = this.open
          // 监听socket错误信息
          this.socket.onerror = this.error
          // 监听socket消息
          this.socket.onmessage = this.getMessage
          this.socket.onclose = this.close
        }
      },
      open: function () {
      },
      error: function () {
      },
      getMessage: function (obj) {
        //接收信息后根据不同情况不同处理方式
        let data = JSON.parse(obj.data);
        if (data.code === 1) {
        } else if (data.code === 2) {
        } else {
        }
      },
      close: function (e) {
      },

      doSend: function () {
        if (that.sendData === '') {
          return;
        }
        this.socket.send(that.sendData);
        that.sendData = '';
      },

以上代码不完整,如果需要看下完整代码,联系我。