爱生活,爱分享


关于Websocket的使用

haiten 2020-04-02 561浏览 0条评论
首页/正文
分享到: / / / /

一、前言

在 WebSocket 中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道,两者之间就直接可以数据互相传送。其特别重要的一个功能是可以实现服务端主动推送给前端,让前端去响应处理。刚好最近在开发中遇到了这个场景,所以将处理经验过程记录一下。

二、在 SpingBoot 中使用 Websocket(后端)

1、maven 依赖包的引用

在项目的 pom 文件中引入:
<dependency>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>

2、在 SpringBoot 的启动类中添加注解和 Bean

@EnableWebSocket
@SpringBootApplication(scanBasePackages = "com.gmcc.frame")
public class SingleApplication {

    public static void main(String[] args) {
        SpringApplication.run(SingleApplication.class, args);
    }

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

}

3、WebSocket的使用(以 chat-room 为例)

(1)工具类

public final class WebSocketUtils {

    /**
     * 模拟存储 websocket session 使用
     */
    public static final Map<String, Session> LIVING_SESSIONS_CACHE = new ConcurrentHashMap<>();

    public static void sendMessageAll(String message) {
        LIVING_SESSIONS_CACHE.forEach((sessionId, session) -> sendMessage(session, message));
    }

    /**
     * 发送给指定用户消息
     *
     * @param session 用户 session
     * @param message 发送内容
     */
    public static void sendMessage(Session session, String message) {
        if (session == null) {
            return;
        }
        final RemoteEndpoint.Basic basic = session.getBasicRemote();
        if (basic == null) {
            return;
        }
        try {
            basic.sendText(message);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 发送给指定用户数据实体包
     *
     * @param session 用户 session
     * @param socketEntity 数据实体包,需要自定义
     */
    public static void sendEntity(Session session, SocketEntity socketEntity) {
        if (session == null) {
            return;
        }
        final RemoteEndpoint.Basic basic = session.getBasicRemote();
        if (basic == null) {
            return;
        }
        try {
            basic.sendText(JsonUtil.toJson(socketEntity));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

(2)控制器

@RestController
@ServerEndpoint("/chat-room/{username}")
public class WebSocketEndpoint {

    private static final Logger log = LoggerFactory.getLogger(WebSocketEndpoint.class);

    @OnOpen
    public void onConnect(@PathParam("username") String username, Session session) {
        LIVING_SESSIONS_CACHE.put(username, session);
        String message = "欢迎用户[" + username + "] 登录系统!";
        log.info(message);
				
				// 并且通知其他人当前用户已经进入聊天室了
        sendMessageAll(message);

    }

    @OnMessage
    public void onMessage(@PathParam("username") String username, String message) {
        log.info(message);
        sendMessageAll("用户[" + username + "] : " + message);
    }

    @OnClose
    public void onClose(@PathParam("username") String username, Session session) {
        // 当前的Session 移除
        LIVING_SESSIONS_CACHE.remove(username);
        String message = "用户[" + username + "] 已经离开系统了!";
        log.info(message);
				
        // 并且通知其他人当前用户已经离开聊天室了
        sendMessageAll(message);
				
        try {
            session.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @OnError
    public void onError(Session session, Throwable throwable) {
        try {
            session.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        throwable.printStackTrace();
    }


    @PostMapping("/chat-room/{sender}/to/{receive}")
    public void onMessage(@PathVariable("sender") String sender, @PathVariable("receive") String receive, String message) {
        sendMessage(LIVING_SESSIONS_CACHE.get(receive), "[" + sender + "]" + "-> [" + receive + "] : " + message);
    }

}

三、在 Vue 中使用 Websocket(前端)

(1)工具类

const wsUrl = process.env.SOCKET_BASE_API + '/chat-room/'
let ws = null

function doMessage(data) {
  const socketEntity = JSON.parse(data)
  // 处理服务器推送的信息,需要实现自己的业务逻辑
	// ......
}

export function runSocket(userAccount) {
  if (ws !== null) {
    return
  }
  ws = new WebSocket(wsUrl + userAccount)
  ws.onopen = function() {
    console.log('建立 websocket 连接...')
  }
  ws.onmessage = (event) => {
    // 服务端发送的消息
    doMessage(event.data)
  }
  ws.onclose = function() {
    console.log('关闭 websocket 连接...')
  }
}

export function closeSocket() {
  if (ws !== null) {
    ws.onclose()
    ws = null
  }
}

(2)使用 Websocket

需要根据自己的实际情况,在合适的时机建立连接和断开连接,以下是在 store 中的用法举例

import { runSocket, closeSocket } from '@/api/socket'

const state = {
  ......
}

const mutations = {

  ......

  // 设置用户相关信息
  SET_USER_INFO: async(state, { ...... }) => {
    ......
    await runSocket(account)
  },

  // 重置用户相关信息
  RESET_USER: async(state) => {
    ......
    closeSocket()
  }
	
  ......
	
}

const actions = {

  ......
	
  // 登录
  login({ commit }, userInfo) {
       ......
  },

  // 获取用户信息
  getInfo({ state, commit, dispatch }) {
    return new Promise((resolve, reject) => {
      aclApi.getUserInfo().then(res => {
        // 设置用户信息
        ......
        commit('SET_USER_INFO', userInfo)
        resolve(data)
      }).catch(error => {
        reject(error)
      })
    })
  },

  // 登出
  logout({ state, commit, dispatch }) {
    return new Promise((resolve, reject) => {
      aclApi.logout().then(() => {
        commit('RESET_USER')
        resolve()
      }).catch(error => {
        reject(error)
      })
    })
  },

  ......

}

export default {
  namespaced: true,
  state,
  mutations,
  actions
}

四、Nginx的配置(前端分离部署)

(1)在 http 配置内添加:

    # websocket support
    map $http_upgrade $connection_upgrade {
        default upgrade;
        ''      close;
    }

(2)在 server 配置内添加:

以下为一个举例的配置,需要更加自己的实际情况修改,请不要完全照抄:

    location ^~ /service-module/ {
        proxy_pass http://websocket-module/service-module/;
        proxy_set_header   Host   $host;
        proxy_set_header   Referer $http_referer;
        proxy_set_header   Cookie $http_cookie;
        proxy_set_header   X-Real-IP  $remote_addr;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;

        # websocket support
        proxy_http_version 1.1;
        proxy_connect_timeout 4s;
        proxy_read_timeout 36000s;
        proxy_send_timeout 12s;
        proxy_redirect off;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
    }

注意:proxy_read_timeout 是服务器对你等待最大的时间,也就是说,当你 WebSocket 使用 nginx 转发的时候,超出这个时间就会自动断开,请根据具体需求调整。

最后修改:2020-04-02 11:31:07 © 著作权归作者所有
如果觉得我的文章对你有用,请随意赞赏
扫一扫支付

上一篇

发表评论

说点什么吧~

评论列表

还没有人评论哦~赶快抢占沙发吧~