websocket

消息推送常见方式

1. 轮询(短轮询)

浏览器以指定的时间间隔向服务器发出HTTP请求,服务器实时返回数据给浏览器

使用场景:使用定时任务每秒给后端发送HTTP请求

劣势:服务器没有新的数据更新会返回空的数据,下一次发请求并且更新数据才会请求到,展示会有延迟,而且每秒钟处理请求,会对服务器造成很大的压力

2. 长轮询

浏览器发出ajax请求,服务器端接收到请求后,会阻塞请求直到有数据或者超时才返回

3. SSE(server-sent event) : 服务器发送事件

SSE在服务器和客户端之间打开一个单向通道

服务端响应的不再是一次性的数据包,而是text/event-stream类型的数据流信息

服务器有数据变更时将数据流式传输到客户端

4. WebSocket

WebScoket是一种在基于TCP连接上进行全双工通信的协议

全双工(Full Duplex):允许数据在两个方向上同时传输。

半双工(Half Duplex):允许数据在两个方向上传输,但是同一个时间段内只允许一个方向上传输。

WebScoket API

客户端 [浏览器]API

1. WebSocket对象创建

let ws = new WebSocket(URL);

 2. WebSocket对象相关事件

3. WebSocket对象提供的方法

服务端的API

简介

Tomcat的7.0.5 版本开始支持WebSocket,并且实现了Java WebSocket规范。


Java WebSocket应用由一系列的Endpoint组成。Endpoint是一个iava对象,代表Websocket链接的一端,对于服务端我们可以视为处理具体WebSocket消息的接口。


我们可以通过两种方式定义Endpoint:

第一种是编程式,即继承类javax.websocket.Endpoint并实现其方法
第二种是注解式,即定义一个POJO,并添加 @ServerEndpoint相关注解

Endpoint实例在WebSocket握手时创建,并在客户端与服务端链接过程中有效,最后在链接关闭时结束。在Endpoint接口中明确定义了与其生命周期相关的方法,规范实现者确保生命周期的各个阶段调用实例的相关方法。生命周期方法如下:


//Endpoint是一个iava对象,代表Websocket链接的一端,对于服务端我们可以视为处理具体WebSocket消息的接口。
//注解使用@ServerEndpoint("/路径")
@ServerEndpoint("/chat")
@Component//交给spring容器管理
public class ChatEndpoint {

    @OnOpen
    //连接建立时被调用
    public void onePen(Session session, EndpointConfig config){

    }
    @OnMessage
    //接收到客户端发送的数据时被调用
    public void onMessage(String message){

    }
    @OnClose
    //连接关闭时被调用
    public void onClose(Session session){

    }
}

 

服务端接受客户端发送的数据

编程式
通过定义一个类去实现 MessageHgndler接口 消息处理器来接收消息

注解式
在定义Endpoint时,通过@OnMessage注解指定接收消息的方法

服务器推送数据给客户端

发送消息则由 RemoteEndpoint 完成,其实例由 Session 维护。

发送消息有2种方式发送消息

通过session.getBasicRemote 获取同步消息发送的实例,然后调用其 sendXxx()方法发送消息

通过session.getAsyncRemote 获取异步消息发送实例,然后调用其sendXxx()方法发送消息

在线聊天室实现

流程图

消息格式

客户端-->服务端
{"toName":"张三","message":“你好”}


服务端-->客户端
1.系统消息格式:

{"'system":true,"fromName":null,"message":["李四","王五"]}

2.推送给某一个用户的消息格式:

{"system":false,"fromName":"张三",message":"你好"}

代码实现

依赖:

<!--   websocket的坐标     -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.79</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

Configuration配置

package com.example.webscoket.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
    //注入ServerEndpointExporter, 自动扫描使用@@ServerEndpoint注解的
    public ServerEndpointExporter serverEndpointExporter(){
        return new ServerEndpointExporter();
    }
}

 配置类,用于获取HHpSession对象

package com.example.webscoket.component;

import javax.servlet.http.HttpSession;
import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;

//配置类,用于获取HHpSession对象
public class GetHttpSessionConfigurator extends ServerEndpointConfig.Configurator {
    @Override
    public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
        HttpSession httpSession = (HttpSession) request.getHttpSession();
        //将httpsession对象存储到配置对象中
        sec.getUserProperties().put(HttpSession.class.getName(),httpSession);
    }
}

 工具类

package com.example.webscoket.utils;

import com.alibaba.fastjson.JSON;
import com.example.webscoket.ws.pojo.ResultMessage;

public class MessageUtils {
    public static String getMessage(boolean isSystemMessage,String fromName,Object message) {
        ResultMessage result = new ResultMessage();
        result.setSystem(isSystemMessage);
        result.setMessage(message);
        if (fromName!=null){
            result.setMessage(fromName);
        }
        return JSON.toJSONString(result);
    }
}

 

package com.example.webscoket.ws.pojo;

import lombok.Data;

@Data
public class Message {
    private String toName;
    private String message;
}

 

package com.example.webscoket.ws.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 封装json格式消息的工具类
 *
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ResultMessage {

     private Boolean System;
     private Object Message;
     private String FromName;

     public Boolean getSystem() {
          return System;
     }

     public void setSystem(Boolean system) {
          System = system;
     }

     public Object getMessage() {
          return Message;
     }

     public void setMessage(Object message) {
          Message = message;
     }

     public String getFromName() {
          return FromName;
     }

     public void setFromName(String fromName) {
          FromName = fromName;
     }
}

 

 注解形式使用@ServerEndpoint("/路径")

在 @ServerEndpoint 注解中引入配置器configurator

@ServerEndpoint(value ="/chat",configurator = GetHttpSessionConfigurator.class)

 

package com.example.webscoket.ws;

import com.alibaba.fastjson.JSON;
import com.example.webscoket.config.GetHttpSessionConfigurator;
import com.example.webscoket.utils.MessageUtils;
import com.example.webscoket.ws.pojo.Message;
import com.example.webscoket.ws.pojo.ResultMessage;
import org.springframework.stereotype.Component;

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

//Endpoint是一个iava对象,代表Websocket链接的一端,对于服务端我们可以视为处理具体WebSocket消息的接口。
//注解使用@ServerEndpoint("/路径")
//在 @ServerEndpoint 注解中引入配置器@ServerEndpoint(value ="/chat",configurator = GetHttpSessionConfigurator.class)
@ServerEndpoint(value = "/chat",configurator = GetHttpSessionConfigurator.class)
@Component//交给spring容器管理
public class ChatEndpoint {
   private static  final Map<String,Session>onLineUsers=new ConcurrentHashMap<>();//用来保存session

   private HttpSession httpSession;

    /**
     * 建立websocket连接后,被调用
     * @param session
     * @param config
     */
    @OnOpen
    public void onePen(Session session, EndpointConfig config){//config==sec
        //1.将session进行保存
     this.httpSession= (HttpSession) config.getUserProperties().get(HttpSession.class.getName());
     String user = (String) this.httpSession.getAttribute("user");
     onLineUsers.put(user,session);//存入Map中
        //2.广播消息.需要将登录的所有的用户推送给所有的用户
        String message = MessageUtils.getMessage(true, null, getFriends());
        broadcastAllUsers(message);
    }
    //返回map集合中的所有key对象,并封装返回为Set集合
    public Set getFriends(){
        Set<String> set = onLineUsers.keySet();
        System.out.printf("set======"+set);
        return set;
    }
    //遍历存入的map取消息
    private void broadcastAllUsers(String message){
        //遍历map集合
        Set<Map.Entry<String, Session>> entries = onLineUsers.entrySet();//entrySet实现了Set接口,里面存放的是键值对。一个K对应一个V。
        for (Map.Entry<String, Session> entry : entries) {
            //获取到所有用户对应的session对象
            Session session = entry.getValue();
            System.out.println(entry.getKey()+","+entry.getValue());
            //发送消息
            try {
                session.getBasicRemote().sendText(message);//同步消息
            } catch (Exception e) {
                //记录日志
            }
        }
    }

    /**
     * 浏览器发送消息到服务端,该方法被调用
     *
     * 张三  --->  李四
     * @param message
     */
    @OnMessage
    public void onMessage(String message){
        //将消息推送给指定的用户
        Message msg = JSON.parseObject(message, Message.class);//转换为java对象
        //获取  消息接收方的用户名
        String toName = msg.getToName();
        String mess = msg.getMessage();
        //获取消息接收方用户对象的session对象
        Session session = onLineUsers.get(toName);
        //当前登录者的session对象
        String user = (String) this.httpSession.getAttribute("user");
        String msg1 = MessageUtils.getMessage(false, user, mess);
        try {
            session.getBasicRemote().sendText(msg1);
        } catch (Exception e) {

            //记录日志
        }
    }

    /**
     * 断开 websocket 连接时被调用
     * @param session
     */
    @OnClose
    //连接关闭时被调用
    public void onClose(Session session){
        //1.从onlineUser中剔除当前用户的session对象
        String  user = (String) this.httpSession.getAttribute("user");
        onLineUsers.remove(user);
        //2.通知其他所有的用户,当前用户下线了
        String message = MessageUtils.getMessage(true, null, getFriends());
        broadcastAllUsers(message);
    }

}

 实体

package com.example.webscoket.entity;

import lombok.Data;

@Data
public class User {
    private String userId;
    private String username;
    private String password;
}
package com.example.webscoket.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Result {
    private Boolean flag;
    private String message;


    public Boolean getFlag() {
        return flag;
    }

    public void setFlag(Boolean flag) {
        this.flag = flag;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

 

 

controller

package com.example.webscoket.controller;

import com.example.webscoket.entity.Result;
import com.example.webscoket.entity.User;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpSession;

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

    public Result login(@RequestBody User user, HttpSession session){
        Result result = new Result();
        if (user!=null &&"123".equals(user.getPassword())){
            result.setFlag(true);
            result.setMessage("登陆成功");
            session.setAttribute("user",user.getUsername());
        }else {
            result.setFlag(false);
            result.setMessage("登陆失败");
        }
        return result;
    }
}

  • 24
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值