SpringBoot + WebSocket

SpringBoot + WebSocket

1. 简介

WebSocket协议是基于TCP的一种新的网络协议。它实现了客户端与服务器全双工通信,学过计算机网络都知道,既然是全双工,就说明了服务器可以主动发送信息给客户端 。这与我们的推送技术或者是多人在线聊天的功能不谋而合。HTTP是单工通信,通信只能由客户端发起。

HTTP单工通信 WebSocket全双工通信

image.png

2. pom.xml

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

3. Config

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

4. Server

因为WebSocket是类似客户端服务端的形式(采用ws协议),那么这里的WebSocketServer其实就相当于一个ws协议的Controller

  • @ ServerEndpoint 注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端, 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端
  • 新建一个ConcurrentHashMap webSocketMap 用于接收当前token的WebSocket,方便传递之间对user进行推送消息。

```java /** * websocket server * * @author CShip * @date 2021/2/22 */ @Slf4j @ServerEndpoint(value = "/ws/{token}") @Component public class WebSocketServer {

private static final AtomicInteger CONNECTED_COUNT = new AtomicInteger(0);

/**
 * key userId
 * value session
 *
 */
private static final ConcurrentHashMap<String, Session> sessionMap = new ConcurrentHashMap<>();

private static LoginHelper loginHelper;

@Autowired
private LoginHelper loginHelperResource;

@PostConstruct
public void init() {
    WebSocketServer.loginHelper = loginHelperResource;
}

@OnOpen
public void onOpen(@PathParam("token") String token, Session session) {
    // token+三位随机数
    // 使用随机数来区分同一用户打开多个客户端进行操作
    Long userId = loginHelper.token2UserId(token.substring(0, token.length() - 4));
    String num = token.substring(token.length() - 4);
    sessionMap.put(userId.toString()  + num, session);
    int count = CONNECTED_COUNT.incrementAndGet();
    log.info("连接成功:{}", userId);
    log.info("当前连接:{}", count);
    sendMessage(session, new WebSocketVO<>("connected", 0));
}

@OnClose
public void onClose(@PathParam("token") String token) {
    Long userId = loginHelper.getUserIdFromToken(token.substring(0, token.length() - 4));
    String num = token.substring(token.length() - 4);
    sessionMap.remove(userId.toString()  + num);
    int count = CONNECTED_COUNT.decrementAndGet();
    log.info("连接关闭:{}", userId);
    log.info("当前连接:{}", count);
}


@OnMessage
public void onMessage(@PathParam("token") String token, String message, Session session) {
    WebSocketVO vo = new WebSocketVO<>();
    vo.setMessage(message);
    vo.setFlag(0);
    sendMessage(session, vo);
}

@OnError
public void onError(@PathParam("token") String token, Session session, Throwable error) {
    log.error("Session {}  error : {}", session.getId(), error.getMessage());
}


public static void sendMessage(Session session, WebSocketVO vo) {
    try {
        session.getBasicRemote().sendText(JSONObject.toJSONString(vo));
    } catch (IOException e) {
        log.error("发送消息异常:{}", e.getMessage());
    }
}

/**
 * 向指定用户发送消息
 */
public static void sendMessage(String userId, WebSocketVO vo) {
    for (Map.Entry<String, Session> entry : sessionMap.entrySet()) {
        String userIdWithSession = entry.getKey().split("\\.")[0];
        Session session = entry.getValue();
        if (userIdWithSession.equals(userId)) {
            if (session != null && session.isOpen()) {
                vo.setFlag(1);
                sendMessage(session, vo);
            }
        }
    }
}

/**
 * 群发消息
 */
public static void broadCastInfo(WebSocketVO vo) {
    if (!CollectionUtils.isEmpty(sessionMap)) {
        sessionMap.forEach((userId, session) -> {
            if (session.isOpen()) {
                vo.setFlag(1);
                sendMessage(session, vo);
            }
        });
    }
}

} ```

5. 问题

a. 调用业务Service空指针

WebSocket启动的时候优先于spring容器,从而导致在WebSocketServer中调用业务Service会报空指针异常;需要在WebSocketServer中将所需要用到的service静态初始化一下。

  1. 在WebSocketServer中使用static修饰业务service
  2. 在WebSocketConfig中使用@Autowired修饰业务service的set方法

b. Nginx转发配置

基本上就是在转发时,要把转发的TCP/IP(socket)数据的头中的Upgrade和Connection给带过去(或设置的和前端一样)即可.

```nginx

websocket相关配置

proxyhttpversion 1.1;

将客户端的Upgrade(作为websocket重要标识)请求转发(必须)

proxysetheader Upgrade $http_upgrade;

将客户端的Connection(作为websocket重要标识)转发

proxysetheader Connection "upgrade";

从字面看 X-Real-IP 代表的是客户端请求真实的 IP 地址,这个参数没有相关标准规范,如果是直接访问的请求,可能是客户端真实的 IP 地址,但是中间若经过了层层的代理,就是最后一层代理的 IP 地址。

proxysetheader X-real-ip $remote_addr;

X-Forwarded-For 记录着从客户端发起请求后访问过的每一个 IP 地址,当然第一个是发起请求的客户端本身的地址,各 IP 地址间由“英文逗号+空格”(,)分隔。

remoteaddr与$httpxforwardedfor用以记录客户端的ip地址 即转发IP地址

proxysetheader X-Forwarded-For $remote_addr; ```

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值