搭建信令服务器---SpringBoot简单使用WebRTC(后端篇)

前端篇在这

其实后端的实现还比较容易,就是配合前端进行数据的传递

其实现也就是跟使用SpringBoot实现WebSocket一样,为什么这么说?

因为本来就是基于WebSocket进行WebRTC连接的,所以后端就是实现一个WebSocket服务即可,他就是我们前端篇所说的信令服务器


本次使用需要的依赖(除去SpringBoot的Web项目所必须的依赖外)

<!--        websocket-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>
<!--        JSON处理工具-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.79</version>
        </dependency>

首先先来实现一下前端的登录接口

在前,我们前端登录的时候就发送了一个请求给我们的后端接口,即http://localhost:8080/user/login

现在来看后端来处理这个请求:

(1)首先需要一个User类来接收前端传来的参数

package org.xwx.videodemo.pojo;

import lombok.Data;

// 这个类用于描述用户信息
@Data
public class User {
    private String username;
    private String password;
}

一个结果类来返回给前端:

package org.xwx.videodemo.pojo;

import lombok.Data;

/**
 * 这个类主要用于处理处理登录时的返回结果
 */
@Data
public class Result {
    // 标志位:true 表示成功,false 表示失败
    private boolean flag;
    // 描述信息
    private String message;

    public Result(boolean flag, String message) {
        this.flag = flag;
        this.message = message;
    }

    public static Result success(String message) {
        return new Result(true,message);
    }

    public static Result failed() {
        return new Result(false,"失败");
    }
}

(2)来到控制层处理请求,我这里就随便写一个校验,只要用户名不为空即可!(因为用户名比较重要)把校验完的用户名放到httpSession中发给前端,之后的请求都会携带这个Session,以便我们来获取本次请求的用户是谁。也有要注意点!!!

package org.xwx.videodemo.controller;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.xwx.videodemo.pojo.Result;
import org.xwx.videodemo.pojo.User;

import javax.servlet.http.HttpSession;

@RestController
@RequestMapping("/user")
public class UserController {

    @PostMapping("/login")
    public Result login(User user, HttpSession httpSession) {
        if(user==null||user.getUsername()==null)return Result.failed();
        //这一步非常重要!!,不然后面有一个地方会获取httpSession为NULL
        httpSession.setAttribute("user",user.getUsername());

        return Result.success("登录成功!");

    }
}

到此,用户的登录接口就完成了!!!

下面我们将来开始搭建WebSocket!

1、要定义一个WebSocket的配置类

在Java中使用Spring框架编写的下面代码定义了一个配置类WebSocketConfig,它配置了WebSocket服务端点(ServerEndpoint)的导出器。 

具体来说:

  • @Configuration注解表示这个类是一个配置类,在Spring应用启动时会加载并执行其中的配置。

  • @Bean注解用于告诉Spring,下面的方法将返回一个对象,这个对象应该被注册到Spring的上下文中,使得其他需要它的地方可以通过依赖注入来使用。

  • serverEndpointExporter()方法创建并返回了一个ServerEndpointExporter的实例。ServerEndpointExporter是Spring用于扫描和注册所有使用@ServerEndpoint注解的类的组件。简单来说,这个Bean确保了任何标注为@ServerEndpoint的类都会被识别并注册为WebSocket端点。

简而言之,WebSocketConfig类的作用是启用Spring对WebSocket端点的支持,这样你的应用程序就可以处理WebSocket连接了。当你的应用程序启动时,Spring将会扫描项目中所有的@ServerEndpoint注解的类,并将它们作为WebSocket端点进行注册,即标识这些类来处理前端发来的WebSocket请求。

package org.xwx.videodemo.config;

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();
    }
}

2、要让WebSocket会话中有httpSession来获取用户数据

在登录接口我们已经把用户的信息存到了httpSession中了,之后的每次的http请求都会携带这个httpSession。因为我们设置了。

        //这一步非常重要!!,不然后面有一个地方会获取httpSession为NULL
        httpSession.setAttribute("user",user.getUsername());

如果没有设置,后面要获取这个httpSession的时候就会获取到NULL值

但是只是在http请求能获取到httpSession,可是后面建立了WebSocket连接后我们的请求都是基于WebSocket协议的,不是HTTP协议,也就是说我们如果不进行一些其他的操作,我们后面用WebSocket进行通信的时候就无法获取httpSession,即获取不到我们登录的时候存的用户信息。所以我们需要来做以下的操作:

2.1怎么在WebSocket会话中怎么获取到这个httpSession呢?

这就需要我们对WebSocket初始连接的时候进行一些配置:

package org.xwx.videodemo.ws;


import javax.servlet.http.HttpSession;
import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;
public class GetHttpSessionConfigurator extends ServerEndpointConfig.Configurator {

    @Override
    public void modifyHandshake(ServerEndpointConfig  sec, HandshakeRequest request, HandshakeResponse response) {
            // 获取httpsession对象
            HttpSession httpSession = (HttpSession) request.getHttpSession();
            //将从http请求获取到的httpSession存到WebSocket服务端点中
            // 这样后面就可以通过EndpointConfig来获取这个httpSession
            sec.getUserProperties().put(HttpSession.class.getName(), httpSession);

    }
}

创建一个GetHttpSessionConfigurator 类继承自 ServerEndpointConfig.Configurator,并重写了 modifyHandshake 方法。这个类的作用是在WebSocket握手(handshake)过程中修改配置,并允许访问WebSocket会话的HTTP会话(HttpSession)信息。

以下是代码的具体功能:

  • modifyHandshake 方法会在WebSocket握手阶段被调用,它允许开发者对握手请求和响应进行修改。

  • ServerEndpointConfig sec 参数代表WebSocket端点的配置。

  • HandshakeRequest request 参数包含了客户端发送的握手请求信息。

  • HandshakeResponse response 参数代表了服务器端将要发送的握手响应信息。

在 modifyHandshake 方法中,以下是关键步骤:

  1. 从 HandshakeRequest 对象中获取 HttpSession 对象。这个 HttpSession 对象是HTTP会话的一部分,它存储了与当前用户会话相关的数据。

  2. 将获取到的 HttpSession 对象存储到 ServerEndpointConfig 的用户属性中。这是通过 sec.getUserProperties().put(HttpSession.class.getName(), httpSession); 实现的。

这样就可以在WebSocket会话中获取到这个httpSession了!!!

至于ServerEndpointConfigEndpointConfig的区别可以到网上搜索一下。

3、实现我们自己的WebSocket端点来处理WebSocket请求

先上代码在逐个分析:

package org.xwx.videodemo.ws;

import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.xwx.videodemo.pojo.Message;

import javax.servlet.http.HttpSession;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;

@Component
@ServerEndpoint(value = "/video", configurator = GetHttpSessionConfigurator.class)
public class ChatEndpoint {
    private static final Logger log = LoggerFactory.getLogger(ChatEndpoint.class);
    //存储客户端的连接对象,每个客户端连接都会产生一个连接对象
    private static ConcurrentHashMap<String,ChatEndpoint> onlineClient = new ConcurrentHashMap<String,ChatEndpoint>();

    // //每个连接都会有自己的会话Session,通过该对象可以发生消息给指定的客户端
    private Session session;
    // 声明Httpsession对象,里面记录了用户相关信息
    private HttpSession httpSession;

    @OnOpen
    public void onOpen(Session session, EndpointConfig config) {
        log.info("onOpen");
        this.session = session;
        this.httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName());

        String userName = (String) this.httpSession.getAttribute("user");
        onlineClient.put(userName,this);


    }

    @OnMessage
    public void onMessage(String message) {
        log.info(message);
        Message msg = JSON.parseObject(message, Message.class);
        if(this.session.isOpen()){
            try {
                ChatEndpoint chatEndpoint = onlineClient.get(msg.getName());
                chatEndpoint.session.getBasicRemote().sendText(message);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

        }
    }

    @OnClose
    public void onClose(Session session) {
        String userName = (String) this.httpSession.getAttribute("user");
        onlineClient.remove(userName);

    }

}

3.1、我们需要定义一个自己的端点类

我这里定义了一个名为 ChatEndpoint 的WebSocket服务器端点类。这个类使用 @ServerEndpoint 注解标记,表明它是一个WebSocket端点,并且它指定了端点的URL路径为 /video,同时指定了一个配置器 GetHttpSessionConfigurator.class,这个配置器用于在WebSocket握手阶段关联HTTP会话。@Component注解确保了端点类被Spring容器管理。


日志记录:通过 LoggerFactory 获取一个日志记录器,用于记录日志信息;

我们需要这两个成员变量

  • session:每个连接都会有自己的会话Session,通过该对象可以发生消息给指定的客户端,用于发送和接收消息。
  • httpSession:获取到当前与服务端WebSocket连接的客户端的Httpsession对象,里面记录了用户我们再登录的时候存储的用户信息,以便来让服务端知道这一个ChatEndpoint 是谁的。

这两个成员变量在 onOpen()中初始化,来存储当前客户端的session和httpSession到当前客户端创建的这个ChatEndpoint类的实例中。因为每个客户端和服务器建立一次WebSocket连接都会创建一个属于自己的ChatEndpoint实例。其代码如下:

 @OnOpen
    public void onOpen(Session session, EndpointConfig config) {
        log.info("onOpen");
        this.session = session;
        this.httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName());

        String userName = (String) this.httpSession.getAttribute("user");
        onlineClient.put(userName,this);


    }

然后你会发现,为什么会有这样一段代码:

onlineClient.put(userName,this);

我们来看一下这个成员变量:

//存储客户端的连接对象,每个客户端连接都会产生一个连接对象
    private static ConcurrentHashMap<String,ChatEndpoint> onlineClient = new ConcurrentHashMap<String,ChatEndpoint>();

在线客户端存储:使用 ConcurrentHashMap 来存储每个在线的客户端(用户)的连接对象ChatEndpoint,以用户名作为键。每个客户端都会有一个自己的ChatEndpoint,而我们服务器需要借助目标用户的ChatEndpoint中的我们手动存的session成员变量才能给目标用户发送信息,即服务器主动向客户端推送消息。所以需要在与服务端开始建立WebSocket的时候存起来它是类的静态成员,服务器只会有一份,且与其实例共享。

这样我们服务端与客户端之间建立WebSocket的流程就结束了,建立成功后服务端可以开始收发客户端的消息了!!!但是在哪里收发消息呢???


3.1.2、还要了解一下WebSocket的生命周期方法:

生命周期方法

  • @OnOpen:当WebSocket连接打开时调用。
  • @OnMessage:当从客户端接收到消息时调用。
  • @OnClose:当WebSocket连接关闭时调用。

3.1.3 、在哪里收发消息呢?

从生命周期方法中可以知道:肯定是在onMessage()方法中,不过都要带上注解,其他生命周期方法也一样。

因为我们与前端约定好了,前后端之间参数的数据格式是JSON格式,内容格式如下:


         {
            type:"",消息的类型
            sdp:"",消息传输的sdp信息
            name:"",发给谁
            from:"" 谁发的
          }

所以我们可以定义一个Message类,来专门解析这条数据,以便获取到这一条数据是要服务端发送个谁的:

package org.xwx.videodemo.pojo;

import lombok.Data;

@Data
public class Message {
    // 接收人
    private String name;
    //消息类型
    private String type;
    // 消息内容
    private String sdp;
    private String from;
}

再来看这个onMessage方法:

@OnMessage
    public void onMessage(String message) {
        log.info(message);
        //将JSON字符串message转换为Message对象
        Message msg = JSON.parseObject(message, Message.class);
        //判断是否这个会话是处于打开状态
        if(this.session.isOpen()){
            try {
                //根据前端传过来的消息中的name,即要发送的目标用户来找到目标用户的chatEndpoint
                ChatEndpoint chatEndpoint = onlineClient.get(msg.getName());
                //再通过目标用户的chatEndpoint来给客户端发消息
                chatEndpoint.session.getBasicRemote().sendText(message);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }

        }
    }

这也是为什么我们前后端要约定的JSON格式要有name,即目标用户的用户名的原因,它可以让我们服务端更加方便的找到目标用户的chatEndpoint来发送消息!终于为什么有from,则是在前端有用到

3.1.4在哪里关闭与客户端的连接呢?

从生命周期方法中可以知道:肯定是在onClose()方法中!!!

直接把断开连接的客户端的ChatEndpoint移除即可:

@OnClose
    public void onClose(Session session) {
        String userName = (String) this.httpSession.getAttribute("user");
        onlineClient.remove(userName);

    }

到此,我们就完成了后端的信令服务器的搭建!!!

你会发现,这不就是在后端搭建一个WebSocket服务端点吗?确实是,因为信令服务器就是起到一个连接多个客户端,让客户端之间可以进行信息传输的作用。所以和用WebSocket来搭建在线聊天室是一样的!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值