前后端使用利用WebSocket进行通信

Posted F3nGaoXS

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了前后端使用利用WebSocket进行通信相关的知识,希望对你有一定的参考价值。

前后端使用利用WebSocket进行通信

1、前后端如何连接

服务端利用SpringBoot启动一个WebSocket服务,同时暴露出该服务的应用路径,客户端则利用该应用路径进行连接。需要注意的是,在服务端只需要启动一个WebSocket服务,而每一个客户端就是一个WebSocket应用。

就有点像:服务端是古老的电话接线员,而客户端就是打电话的人。假如用户A想要给用户B打电话就需要先打电话到接线员那,然后接线员再接通用户B。不过WebSocket可以实现广播和私聊。

2、前端初始化WebSocket对象

不需要引入第三方依赖包,直接使用js自带的WebSocket对象。

  1. 创建WebSocket对象

    const ws = new WebSocket('ws://localhost:8000/websocket/')
    

    ws://jdbc://http://一样都是协议名,同样的,Websocket还支持更加安全的wss:///websocket即该服务的应用路径名

  2. onopen事件监听

    与服务端连接成功会触发。

    webSocketOnOpen(e)
        console.log('与服务端连接打开->',e)
    ,
    
  3. onerror事件监听

    与服务端连接异常时触发。

    webSocketOnError(e)
        console.log('与服务端连接异常->',e)
    ,
    
  4. onclose事件监听

    与服务端连接关闭时触发。

    webSocketOnClose(e)
        console.log('与服务端连接关闭->',e)
    ,
    
  5. onmessage事件监听

    接收到来自服务端的消息时触发

    webSocketOnMessage(e)
        console.log('来自服务端的消息->',e)
    ,
    

一个完整的WebSocket对象应该具备以上属性,同时需要将以上属性跟WebSocket对象绑定。

使用原生JS初始化WebSocket对象演示

const ws = new WebSocket(webSocketUrl)
//onopen事件监听
ws.addEventListener('open',e=>
    console.log('与服务端连接打开->',e)
,false)
//onclose事件监听
ws.addEventListener('close',e=>
    console.log('与服务端连接关闭->',e)
,false)
//onmessage事件监听
ws.addEventListener('message',e=>
    console.log('来自服务端的消息->',e)
,false)
//onerror事件监听
ws.addEventListener('error',e=>
    console.log('与服务端连接异常->',e)
,false)

ws对象的addEventListener( )方法,为WebSocket绑定事件监听,从而在各个事件监听中处理事务。

使用Vue初始化WebSocket对象演示

export default 
  name: "Home",
  data() 
    return 
      webSocketObject: null,
    
  ,
  created() 
    //初始化WebSocket
    this.webSocketInit()
  ,
  methods: 
    webSocketInit()
      const webSocketUrl = 'ws://localhost:8000/websocket/'+this.username
      this.webSocketObject = new WebSocket(webSocketUrl);
      this.webSocketObject.onopen = this.webSocketOnOpen
      this.webSocketObject.onmessage = this.webSocketOnMessage
      this.webSocketObject.onerror = this.webSocketOnError
      this.webSocketObject.onclose = this.webSocketOnClose
    ,
    webSocketOnOpen(e)
      console.log('与服务端连接打开->',e)
    ,
    webSocketOnMessage(e)
      console.log('来自服务端的消息->',e)
    ,
    webSocketOnError(e)
      console.log('与服务端连接异常->',e)
    ,
    webSocketOnClose(e)
      console.log('与服务端连接关闭->',e)
    ,
  ,

</script>

同样的,利用methods分别定义好OnOpen、OnMessage、OnError、OnClose四个事件监听,然后进行初始化并且绑定就可以了。

这样就完成了WebSocket对象以及事件监听的初始化。

3、后端初始化WebSocket对象

SpringBoot自带的WebSocket有以下5个注解需要注意:

  1. @ServerEndpoint

    暴露出的ws应用的路径,支持RESTful风格传参,类似/websocket/username

  2. @OnOpen

    与当前客户端连接成功,有入参Session对象(当前连接对象),同时可以利用@PathParam()获取上述应用路径中传递的参数,比如@PathParam("username") String username

  3. @OnClose

    与当前客户端连接失败,有入参Session对象(当前连接对象),同时也可以利用@PathParam()获取上述应用路径中传递的参数。

  4. @OnError

    与当前客户端连接异常,有入参Session对象(当前连接对象)、Throwable对象(异常对象),同时也可以利用@PathParam()获取上述应用路径中传递的参数。

  5. @OnMessage

    当前客户端发送消息,有入参Session对象(当前连接对象)、String message对象(当前客户端传递过来的字符串消息)

利用SpringBoot创建项目,需要引入依赖:

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

在application.yaml中定义好该服务的端口号:

server:
  port: 8000

利用自定义配置类开启WebSocket:

package cn.wqk.serverwebsocket.config;

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

@Configuration
@EnableWebSocket
public class WebSocketConfig 

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



定义Websocket主业务类:

package cn.wqk.serverwebsocket.socket;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Date;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;

@Component
@Slf4j
@ServerEndpoint("/websocket/username") //暴露的ws应用的路径
public class WebSocket 

    /**
     * 客户端与服务端连接成功
     * @param session
     * @param username
     */
    @OnOpen
    public void onOpen(Session session,@PathParam("username") String username)
        /*
            do something for onOpen
            与当前客户端连接成功时
         */
    

    /**
     * 客户端与服务端连接关闭
     * @param session
     * @param username
     */
    @OnClose
    public void onClose(Session session,@PathParam("username") String username)
        /*
            do something for onClose
            与当前客户端连接关闭时
         */
    

    /**
     * 客户端与服务端连接异常
     * @param error
     * @param session
     * @param username
     */
    @OnError
    public void onError(Throwable error,Session session,@PathParam("username") String username) 
    

    /**
     * 客户端向服务端发送消息
     * @param message
     * @param username
     * @throws IOException
     */
    @OnMessage
    public void onMsg(Session session,String message,@PathParam("username") String username) throws IOException 
        /*
            do something for onMessage
            收到来自当前客户端的消息时
         */
    


4、前后端联动实现简单聊天室

前端在OnMessage事件监听中接收到来自后端的消息,然后进行处理(展示在页面上);同样的,后端也是在OnMessage中接收到来自前端的消息,然后进行处理(发送到所有客户端)。

前端

  1. 定义一个输入框,再定义一个按钮:接收消息并且发送

    <input
           type="text"
           v-model="sendMessage"
           placeholder="请输入你要发送的消息">
    <button @click="handleSendButton()">发送</button>
    
    handleSendButton() 
        const username = this.username
        const message = this.sendMessage
        this.webSocketObject.send(JSON.stringify(
            id: 1,
            message,
            username,
            time: new Date().getTime()
        ))
        this.sendMessage = ''
    ,
    

    **注意:**直接利用websocket对象的send()方法发送消息,前后端数据传输利用JSON字符串,所以发送的时候需要将对象转为JSON字符串。

  2. 定义一个列表:用于展示聊天信息

    <table>
        <thead>
            <tr>
                <th>消息编号</th>
                <th>发送者</th>
                <th>发送时间</th>
                <th>发送内容</th>
            </tr>
        </thead>
        <tbody>
            <tr v-for="item in messageList" :key="item.time">
                <td> item.id </td>
                <td> item.username </td>
                <td> new Date(item.time).toLocaleTimeString() </td>
                <td> item.message </td>
            </tr>
        </tbody>
    </table>
    
  3. 当客户端的onMessage接收到消息后就把消息展示到列表中:

    webSocketOnMessage(e)
        console.log('来自服务端的消息->',e)
        const receiveMessage = JSON.parse(e.data);
        this.messageList.push(receiveMessage)
    ,
    

    **注意:**通过console.log(e)不难发现,来自服务端的消息是存储在e.data中的,并且是JSON字符串,所以我们需要将它转为JSON对象。

此时已经完成了前端发送消息并且接收消息且展示消息了。

后端

接收消息并且群发消息:

@OnMessage
public void onMsg(Session session,String message,@PathParam("username") String username) throws IOException 
    /*
            do something for onMessage
            收到来自当前客户端的消息时
         */
    sendAllMessage(message);

//向所有客户端发送消息(广播)
private void sendAllMessage(String message)
    Set<String> sessionIdSet = onlineClientMap.keySet(); //获得Map的Key的集合
    for (String sessionId : sessionIdSet)  //迭代Key集合
        Session session = onlineClientMap.get(sessionId); //根据Key得到value
        session.getAsyncRemote().sendText(message); //发送消息给客户端
    

完整代码

<template>
  <div>
    <table>
      <thead>
      <tr>
        <th>消息编号</th>
        <th>发送者</th>
        <th>发送时间</th>
        <th>发送内容</th>
      </tr>
      </thead>
      <tbody>
      <tr v-for="item in messageList" :key="item.time">
        <td> item.id </td>
        <td> item.username </td>
        <td> new Date(item.time).toLocaleTimeString() </td>
        <td> item.message </td>
      </tr>
      </tbody>
    </table>
    <input
        type="text"
        v-model="sendMessage"
        placeholder="请输入你要发送的消息">
    <button @click="handleSendButton()">发送</button>
    <button @click="handleLogoutButton()">退出</button>
  </div>
</template>

<script>

import 
  getUsername,
  removeUsername
 from "@/utils/username";

export default 
  name: "Home",
  data() 
    return 
      webSocketObject: null,
      username: '',
      messageList: [

      ],
      sendMessage: ''
    
  ,
  created() 
    //从localStorage中获得username
    this.username = getUsername()
    //如果username不存在返回到登录页面
    if (!this.username)
      this.$router.push(
        name: 'Login'
      )
    
    //初始化WebSocket
    this.webSocketInit()
  ,
  beforeDestroy() 
    this.webSocketObject.close();//在该组件销毁时关闭该连接以节约资源
  ,
  methods: 
    webSocketInit()
      const webSocketUrl = 'ws://localhost:8000/websocket/'+this.username
      this.webSocketObject = new WebSocket(webSocketUrl);
      this.webSocketObject.onopen = this.webSocketOnOpen
      this.webSocketObject.onmessage = this.webSocketOnMessage
      this.webSocketObject.onerror = this.webSocketOnError
      this.webSocketObject.onclose = this.webSocketOnClose
    ,
    webSocketOnOpen(e)
      console.log('与服务端连接打开->',e)
    ,
    webSocketOnMessage(e)
      console.log('来自服务端的消息->',e)
      const receiveMessage = JSON.parse(e.data);
      this.messageList.push(receiveMessage)
    ,
    webSocketOnError(e)
      console.log('与服务端连接异常->',e)
    ,
    webSocketOnClose(e)
      console.log('与服务端连接关闭->',e)
    ,
    handleSendButton() 
      const username = this.username
      const message = this.sendMessage
      this.webSocketObject.send(JSON.stringify(
        id: 1,
        message,
        username,
        time: new Date().getTime()
      ))
      this.sendMessage = ''
    ,
    handleLogoutButton()
      removeUsername() //清除username然后断开连接
      this.webSocketObject.close();
      this.$router.push(
        name: 'Login'
      )
    
  ,

</script>

Tips:

我这里采用的是在上一个页面获取到用户的用户名然后存储到LocalStorage中。

package cn.wqk.serverwebsocket.socket;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Date;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;

@Component
@Slf4j
@ServerEndpoint("/websocket/username") //暴露的ws应用的路径
public class WebSocket 

    /** 当前在线客户端数量(线程安全的) */
    private static AtomicInteger onlineClientNumber = new AtomicInteger(0);

    /** 当前在线客户端集合(线程安全的):以键值对方式存储,key是连接的编号

以上是关于前后端使用利用WebSocket进行通信的主要内容,如果未能解决你的问题,请参考以下文章

springboot2实现Websocket前后端通信

#导入MD文档图片#WebSocket的前后端使用

webSocket实现数据的实时推送(附:前后端代码)

SpringBoot+Vue+Websocket 实现服务器端向客户端主动发送消息

基于 websocket 的多端桥接平台

WebSocket使用demo,node作后端服务器