使用websocket实现实时通讯的功能,这里介绍两种方式

  1. 只创建一个聊天的房间,所有人都往里面发送数据,然后接收数据的那一方,通过自己定义的规则,对接收到的数据进行筛选,获取自自己想要的数据。
  2. 创建多个两天的房间,每个房间管理自己的聊天信息,接收方不用对数据进行筛选,因为只能接收自己所在房间的信息,所以无需过滤。
  3. 将webSocket 整合进SpringBoot(这里有个问题,让我找了半天)

(?:本人刚开始学习,所以有表述不准确或不对的地方,请您耐心提出,我会积极改正。)

1.一个房间的实现

package com.green.rainbow.modules.app.socket;

import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;

@ServerEndpoint(value = "/websocket")
@Component
public class WebSocketService {

	// concurrent包的线程安全Set,用来存放每个客户端对应的WebSocketService对象。
    private static CopyOnWriteArraySet<WebSocketService> webSocketSet = new CopyOnWriteArraySet<WebSocketService>();
	// 与某个客户端的连接会话,需要通过它来给客户端发送数据
    private Session session;

	/**
     * 连接建立成功调用的方法*/
    @OnOpen
    public void onOpen(Session session) {
        this.session = session;
        webSocketSet.add(this);

        try {
            sendMessage("连接成功");
        } catch (IOException e) {
            System.out.println("IO异常");
        }
    }

	/**
     * 收到客户端消息后调用的方法
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        for (WebSocketService item : webSocketSet) {
            try {
                item.sendMessage(message);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
	
	/**
     * 发生错误时调用
     * */
     @OnError
     public void onError(Session session, Throwable error) {
         System.out.println("发生错误");
         error.printStackTrace();
     }

	/**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        webSocketSet.remove(this);
    }
    
    public void sendMessage(String message) throws IOException {
     	// websocket  session发送文本消息有两个方法:
       	// getAsyncRemote()和getBasicRemote() 推荐使用getAsyncRemote()这个方法
       	// getBasicRemote是阻塞式的,getAsyncRemote是非阻塞式的
       	// 。。。别问我,你在这里为什么使用getBasicRemote
        this.session.getBasicRemote().sendText(message);
    }
}

2. 多个房间的实现

package com.example.demo.socket;

import net.sf.json.JSONObject;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

@ServerEndpoint(value = "/websocket/{info}")
@Component
public class WebSocketService {


    private static SimpleDateFormat df = new SimpleDateFormat("HH:mm:ss");//创建时间格式对象
    //创建房间的集合,使用ConcurrentHashMap是为了保证线程安全,HashMap在多线程的情况下会出现问题
    private static ConcurrentHashMap<String, ConcurrentHashMap<String, WebSocketService>> roomList = new ConcurrentHashMap<>();
    // 与某个客户端的连接会话,需要通过他来给客户端发送消息
    private Session session;
    //重新加入房间标识
    private int rejoin = 0;


    //info 格式: 标识|房间名|用户名(保证唯一性)
    @OnOpen
    public void onOpen(@PathParam("info")String param, Session session){
    	// 建立连接的时候,通过获取地址上的{info}的信息,通过截取字符串获取连接人的登录信息
        this.session = session;
        // flag 可以定义两个类型 joinRoom(加入房间) , existRoom(退出房间) 和 chatRoom (聊天)
        String flag = param.split("[|]")[0];
        // roomId 房间号
        String roomId = param.split("[|]")[1];
        if(flag.equals("joinRoom")){
        	// 用户名(必须保证唯一性)
            String nickname = param.split("[|]")[2];
            this.joinRoom(roomId , nickname);
        }
    }

    //加入房间
    public void joinRoom(String roomId, String nickname){

        if (!roomList.containsKey(roomId)) {
            // 创建房间不存在时,创建房间
            ConcurrentHashMap<String, WebSocketService> room = new ConcurrentHashMap<>();
            // 添加用户
            room.put(nickname, this);
            roomList.put(roomId, room);
        } else {
            // 房间已存在,直接添加用户到相应的房间
            ConcurrentHashMap<String, WebSocketService> room = roomList.get(roomId);
            if (room.get(nickname) != null) {
                this.rejoin = 1;
            }else{
                room.put(nickname, this);
            }
            //发送消息给房间内的其他人,通知他们user已经上线
            for(String item : room.keySet()){
                Map<String, Object> map = new HashMap<>();
                map.put("flag", "joinRoom");
                map.put("name", item);
                map.put("status", "上线");
                room.get(item).sendMessage(map);
            }
        }
    }

    //发送消息
    public void sendMessage(Map<String, Object> map){
        JSONObject jsonObject = JSONObject.fromObject(map);
        try {
        	// websocket  session发送文本消息有两个方法:
        	// getAsyncRemote()和getBasicRemote() 推荐使用getAsyncRemote()这个方法
        	// getBasicRemote是阻塞式的,getAsyncRemote是非阻塞式的
        	// 。。。别问我,你在这里为什么使用getBasicRemote
            this.session.getBasicRemote().sendText(jsonObject.toString());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    //接收来自用户的消息
    @OnMessage
    public void onMessage(String message, Session session) throws IOException{
        //把用户发来的消息解析成json对象
        JSONObject param = JSONObject.fromObject(message);
        //如果是退出房间 flag 分类:exitRoom 离开 joinRoom 加入房间 chatRoom 聊天
        String flag = (String)param.get("flag");
        if(flag.equals("exitRoom")){
            String roomId = (String )param.get("roomId");
            String nickname = (String)param.get("nickname");
            ConcurrentHashMap<String, WebSocketService> room = roomList.get(roomId);//获取房间
            room.remove(nickname);//从房间中移除该用户
            //判断房间中是否有人,如果没人则删除房间,否则通知其他用户该用户已经下线
            if(room.size() == 0 ){
                roomList.remove(roomId);
            }else{
                for(String item : room.keySet()){
                    param.put("status", "下线");
                    room.get(item).sendMessage(param);
                }
            }
        }else if(flag.equals("chatRoom")){
            param.put("date", df.format(new Date()));
            String roomId = (String)param.get("roomId");
            String nickname = (String)param.get("nickname");
            ConcurrentHashMap<String, WebSocketService> room = roomList.get(roomId);
            if(room.get(nickname).rejoin == 0 ){
                for(String item: room.keySet()){
                    room.get(item).sendMessage(param);
                }
            }else{
                room.get(nickname).sendMessage(param);
            }
        }
    }

    //用户断开
    @OnClose
    public void onClose(Session session){
        System.out.print("onClose");
    }

    @OnError
    public void onError(Throwable t){
        System.out.print("onError");
    }


}

3. 整合SpringBoot 和 WebSocket

也许你在看到上面的代码时,就开始在自己的项目里调试了,可是当你在客户端调用ws://localhost:8080/websocket(以创建一个房间的那个为例)想创建连接时,会发现怎么也连不上,当时我是通过三步解决的这个问题的

  1. 这个应该是必须要有的,但是因为看不懂,以为没用就没加。。。。

(1). 引入mvn

<!--webSocket依赖jar-->
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

(2). 创建一个Java类,例如叫WebSocketConfig.java

package com.green.rainbow.modules.app.socket;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

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

}
  1. 如果你的项目的拦截器或者过滤器,拦截的是所有路径(/*)那么他也会拦截ws://localhost:8080/websocket 的“/websocket ” 路径,所以会连接不上。解决的方法就是,在你的项目中配置一下不拦截这个路径。
  2. 。。。。在实现websocket的类上加上注解@Component,嗯~~完美。