Websocket实战-消息推送

一 Websocket简介

1.1 是什么

WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket通信协议于2011年被IETF定为标准RFC 6455,并由RFC7936补充规范。WebSocket API也被W3C定为标准。

WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

1.2 出现背景

很多网站为了实现推送技术,所用的技术都是轮询。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。

而比较新的技术去做轮询的效果是Comet。这种技术虽然可以双向通信,但依然需要反复发出请求。而且在Comet中,普遍采用的长链接,也会消耗服务器资源。

在这种情况下,HTML5定义了WebSocket协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。

1.3 优点

  • 较少的控制开销。在连接创建后,服务器和客户端之间交换数据时,用于协议控制的数据包头部相对较小。在不包含扩展的情况下,对于服务器到客户端的内容,此头部大小只有2至10字节(和数据包长度有关);对于客户端到服务器的内容,此头部还需要加上额外的4字节的掩码。相对于HTTP请求每次都要携带完整的头部,此项开销显著减少了。

  • 更强的实时性。由于协议是全双工的,所以服务器可以随时主动给客户端下发数据。相对于HTTP请求需要等待客户端发起请求服务端才能响应,延迟明显更少;即使是和Comet等类似的长轮询比较,其也能在短时间内更多次地传递数据。

  • 保持连接状态。与HTTP不同的是,Websocket需要先创建连接,这就使得其成为一种有状态的协议,之后通信时可以省略部分状态信息。而HTTP请求可能需要在每个请求都携带状态信息(如身份认证等)。

  • 更好的二进制支持。Websocket定义了二进制帧,相对HTTP,可以更轻松地处理二进制内容。

  • 可以支持扩展。Websocket定义了扩展,用户可以扩展协议、实现部分自定义的子协议。如部分浏览器支持压缩等。

  • 更好的压缩效果。相对于HTTP压缩,Websocket在适当的扩展支持下,可以沿用之前内容的上下文,在传递类似的数据时,可以显著地提高压缩率。

    推送消息数目 即时通讯 直播间弹幕 等

二 Websocket入门-springboot

​ websocket是支持一对一(用户发给用户,管理员发给特定用户),一对多(管理员推送给一组用户)消息推送的,故接下来我们对这两种场景进行入门测试。

2.1 步骤分析

搭建springboot环境
springboot支持websocket
websocket服务端实现-一对一
websocket客户端实现-一对一
websocket服务端实现-一对多
websocket客户端实现-一对多

2.2 实现

2.2.1 搭建springboot环境
 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
</dependency>
server:
  port: 18092
@SpringBootApplication
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class,args);
    }
}
2.2.2 springboot支持websocket
<dependency>
    <!-- websocket -->
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<dependency>
    <!-- fastjson -->
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.47</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.16.16</version>
</dependency>
@Configuration
public class WebSocketConfig {
    /**
     * 注入一个ServerEndpointExporter,该Bean会自动注册
     * 使用@ServerEndpoint注解申明的websocket endpoint
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

}
2.2.3 websocket一对一测试

//后台
package cn.itsource.websocket;

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

import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 前后端交互的类实现消息的接收推送(自己发送给自己)
 * 
 * @ServerEndpoint(value = "/test/one") 前端通过此URI和后端交互,建立连接
 */
@Slf4j
@ServerEndpoint(value = "/test/one")
@Component
public class OneWebSocket {

    /**
     * 记录当前在线连接数
     */
    private static AtomicInteger onlineCount = new AtomicInteger(0);

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session) {
        onlineCount.incrementAndGet(); // 在线数加1
        log.info("有新连接加入:{},当前在线人数为:{}", session.getId(), onlineCount.get());
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose(Session session) {
        onlineCount.decrementAndGet(); // 在线数减1
        log.info("有一连接关闭:{},当前在线人数为:{}", session.getId(), onlineCount.get());
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        log.info("服务端收到客户端[{}]的消息:{}", session.getId(), message);
        this.sendMessage("Hello, " + message, session);
    }

    @OnError
    public void onError(Session session, Throwable error) {
        log.error("发生错误");
        error.printStackTrace();
    }

    /**
     * 服务端发送消息给客户端
     */
    private void sendMessage(String message, Session toSession) {
        try {
            log.info("服务端给客户端[{}]发送消息{}", toSession.getId(), message);
            toSession.getBasicRemote().sendText(message);
        } catch (Exception e) {
            log.error("服务端发送消息给客户端失败:{}", e);
        }
    }
}
//前台 resources/static/index.html
<!DOCTYPE HTML>
<html>
<head>
    <title>My WebSocket</title>
</head>

<body>
<input id="text" type="text" />
<button onclick="send()">Send</button>
<button onclick="closeWebSocket()">Close</button>
<div id="message"></div>
</body>

<script type="text/javascript">
    var websocket = null;

    //判断当前浏览器是否支持WebSocket, 主要此处要更换为自己的地址
    if ('WebSocket' in window) {
        websocket = new WebSocket("ws://localhost:18092/test/one");
        // websocket = new WebSocket("ws://localhost:18092/test/oneToMany");
    } else {
        alert('Not support websocket')
    }

    //连接发生错误的回调方法
    websocket.onerror = function() {
        setMessageInnerHTML("error");
    };

    //连接成功建立的回调方法
    websocket.onopen = function(event) {
        //setMessageInnerHTML("open");
    }

    //接收到消息的回调方法
    websocket.onmessage = function(event) {
        setMessageInnerHTML(event.data);
    }

    //连接关闭的回调方法
    websocket.onclose = function() {
        setMessageInnerHTML("close");
    }

    //监听窗口关闭事件,当窗口关闭时,主动去关闭websocket连接,防止连接还没断开就关闭窗口,server端会抛异常。
    window.onbeforeunload = function() {
        websocket.close();
    }

    //将消息显示在网页上
    function setMessageInnerHTML(innerHTML) {
        document.getElementById('message').innerHTML += innerHTML + '<br/>';
    }

    //关闭连接
    function closeWebSocket() {
        websocket.close();
    }

    //发送消息
    function send() {
        var message = document.getElementById('text').value;
        websocket.send(message);
    }
</script>
</html>

启动测试

2.2.4 websocket一对多测试
package cn.itsource.websocket;

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

import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 
 * 前后端交互的类实现消息的接收推送(自己发送给所有人(不包括自己))
 * 
 * @ServerEndpoint(value = "/test/oneToMany") 前端通过此URI 和后端交互,建立连接
 */
@Slf4j
@ServerEndpoint(value = "/test/oneToMany")
@Component
public class OneToManyWebSocket {

    /** 记录当前在线连接数 */
    private static AtomicInteger onlineCount = new AtomicInteger(0);

    /** 存放所有在线的客户端 */
    private static Map<String, Session> clients = new ConcurrentHashMap<>();

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session) {
        onlineCount.incrementAndGet(); // 在线数加1
        clients.put(session.getId(), session);
        log.info("有新连接加入:{},当前在线人数为:{}", session.getId(), onlineCount.get());
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose(Session session) {
        onlineCount.decrementAndGet(); // 在线数减1
        clients.remove(session.getId());
        log.info("有一连接关闭:{},当前在线人数为:{}", session.getId(), onlineCount.get());
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message
     *            客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        log.info("服务端收到客户端[{}]的消息:{}", session.getId(), message);
        this.sendMessage(message, session);
    }

    @OnError
    public void onError(Session session, Throwable error) {
        log.error("发生错误");
        error.printStackTrace();
    }

    /**
     * 群发消息
     * 
     * @param message
     *            消息内容
     */
    private void sendMessage(String message, Session fromSession) {
        for (Map.Entry<String, Session> sessionEntry : clients.entrySet()) {
            Session toSession = sessionEntry.getValue();
            // 排除掉自己
            if (!fromSession.getId().equals(toSession.getId())) {
                log.info("服务端给客户端[{}]发送消息{}", toSession.getId(), message);
                toSession.getAsyncRemote().sendText(message);
            }
        }
    }}
前台 //修改上面的一个访问地址为多个访问地址就ok
websocket = new WebSocket("ws://localhost:18092/test/oneToMany");

//启动测试

2.3 小结

三 Websocket实战-消息推送

3.1 非集群环境Websocket

3.1.1 需求分析

​ 环境:一个websocket服务器

​ 实现:

​ 用户1给用户2发送站内信,如果用户2在线实时显示消息总数!

​ 管理员给用户1,用户2发站内信,如果他们在线显示消息总数!

3.1.2 方案设计

在这里插入图片描述

3.1.3 代码实现
package cn.itsource.msg.websocket;

import cn.itsource.basic.util.LoginContext;
import cn.itsource.basic.util.SpringUtil;
import cn.itsource.msg.domain.Message;
import cn.itsource.msg.service.IMessageService;
import cn.itsource.user.domain.LoginInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.TextMessage;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 
 * 前后端交互的类实现消息的接收推送(自己发送给所有人(不包括自己))
 * 
 * @ServerEndpoint(value = "/test/oneToMany") 前端通过此URI 和后端交互,建立连接
 */
@Slf4j
@ServerEndpoint(value = "/msg/{token}")
@Component
public class MsgWebSocket {

    /** 记录当前在线连接数 */
    private static AtomicInteger onlineCount = new AtomicInteger(0);

    /** 存放所有在线的客户端 */
    private static Map<String, Session> clients = new ConcurrentHashMap<>();

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("token") String token) {
        RedisTemplate redisTemplate = (RedisTemplate) SpringUtil.getBean("redisTemplate");
        LoginInfo info = (LoginInfo) redisTemplate.opsForValue().get(token);
        System.out.println(info);
        //已连接上就获取信息数目发送
        IMessageService messageService = SpringUtil.getBean(IMessageService.class);
        Integer count = messageService.queryMsgTotal(info.getId());
        session.getAsyncRemote().sendText(count+"");
        onlineCount.incrementAndGet(); // 在线数加1
        clients.put(info.getId()+"", session);
        log.info("有新连接加入:{},当前在线人数为:{}", session.getId(), onlineCount.get());
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose(Session session,@PathParam("token") String token) {
        onlineCount.decrementAndGet(); // 在线数减1
        clients.remove(token);
        log.info("有一连接关闭:{},当前在线人数为:{}", session.getId(), onlineCount.get());
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message
     *            客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        log.info("服务端收到客户端[{}]的消息:{}", session.getId(), message);
        this.sendMessage(message, session);
    }

    @OnError
    public void onError(Session session, Throwable error) {
        log.error("发生错误");
        error.printStackTrace();
    }

    public void pushMsg(Message message){
        IMessageService messageService = SpringUtil.getBean(IMessageService.class);
        //发消息
        Session session = clients.get(message.getRec_id() + "");
        session.getAsyncRemote().sendText(messageService.queryMsgTotal(message.getRec_id())+"");
    }
}
发送接口
controller
 @PostMapping("/push/{id}")
    public AjaxResult pushMsg(@PathVariable("id") Long id){
        try {
            messageService.push(id);
            return AjaxResult.me();
        } catch (Exception e) {
            e.printStackTrace();
            return AjaxResult.me().setSuccess(false).setMessage("发送失败!"+e.getMessage());
        }
    }
service
     @Autowired
        private MsgWebSocket msgWebSocket;
     @Autowired
    private MessageMapper messageMapper;
    @Override
    public void push(Long id) {
        Message message = messageMapper.loadById(id);
        //非集群环境
        msgWebSocket.pushMsg(message);
       
    }
3.1.4 小结

3.2 集群环境Websocket

3.2.1 需求分析

环境:多个websocket服务器进行集群

​ 实现:

​ 用户1给用户2发送站内信,如果用户2在线实时显示消息总数!

​ 管理员给用户1,用户2发站内信,如果他们在线显示消息总数!

3.2.2 方案设计

在这里插入图片描述

3.2.3 代码实现
rabbitmq支持
  pom
   <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-amqp</artifactId>
        </dependency>
  yml
  spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
    
    发送配置
    @Configuration
public class SendConfig {
    @Bean("queue")
    public Queue queueMsg(){
        return new Queue("queue");
    }
    @Bean("exchange")
    public TopicExchange exchange(){
        return new TopicExchange("exchange");
    }
    @Bean
    Binding bindingExchangeAndQueuemsg(@Qualifier("queue") Queue queue, @Qualifier("exchange") TopicExchange exchange){
        return BindingBuilder.bind(queue).to(exchange).with("a");
    }
}
websocket
package cn.itsource.msg.websocket;

import cn.itsource.basic.util.LoginContext;
import cn.itsource.basic.util.SpringUtil;
import cn.itsource.msg.domain.Message;
import cn.itsource.msg.service.IMessageService;
import cn.itsource.user.domain.LoginInfo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.TextMessage;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 
 * 前后端交互的类实现消息的接收推送(自己发送给所有人(不包括自己))
 * 
 * @ServerEndpoint(value = "/test/oneToMany") 前端通过此URI 和后端交互,建立连接
 */
@Slf4j
@ServerEndpoint(value = "/msg/{token}")
@Component
public class MsgWebSocket {

    /** 记录当前在线连接数 */
    private static AtomicInteger onlineCount = new AtomicInteger(0);

    /** 存放所有在线的客户端 */
    private static Map<String, Session> clients = new ConcurrentHashMap<>();

    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("token") String token) {
        RedisTemplate redisTemplate = (RedisTemplate) SpringUtil.getBean("redisTemplate");
        LoginInfo info = (LoginInfo) redisTemplate.opsForValue().get(token);
        System.out.println(info);
        //已连接上就获取信息数目发送
        IMessageService messageService = SpringUtil.getBean(IMessageService.class);
        Integer count = messageService.queryMsgTotal(info.getId());
        session.getAsyncRemote().sendText(count+"");
        onlineCount.incrementAndGet(); // 在线数加1
        clients.put(info.getId()+"", session);
        log.info("有新连接加入:{},当前在线人数为:{}", session.getId(), onlineCount.get());
    }

    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose(Session session,@PathParam("token") String token) {
        onlineCount.decrementAndGet(); // 在线数减1
        clients.remove(token);
        log.info("有一连接关闭:{},当前在线人数为:{}", session.getId(), onlineCount.get());
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param message
     *            客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        log.info("服务端收到客户端[{}]的消息:{}", session.getId(), message);
        this.sendMessage(message, session);
    }

    @OnError
    public void onError(Session session, Throwable error) {
        log.error("发生错误");
        error.printStackTrace();
    }

    @RabbitListener(queues = "queue")
    public void recive(Message message) throws Exception{
        System.out.println(message);
        IMessageService messageService = SpringUtil.getBean(IMessageService.class);
        //发消息
        Session session = clients.get(message.getRec_id() + "");
        if (session!=null){
            session.getAsyncRemote().sendText(messageService.queryMsgTotal(message.getRec_id())+"");
        }

 }
发送接口
controller
 @PostMapping("/push/{id}")
    public AjaxResult pushMsg(@PathVariable("id") Long id){
        try {
            messageService.push(id);
            return AjaxResult.me();
        } catch (Exception e) {
            e.printStackTrace();
            return AjaxResult.me().setSuccess(false).setMessage("发送失败!"+e.getMessage());
        }
    }
service
@Autowired
    private AmqpTemplate amqpTemplate;

    @Autowired
    private MessageMapper messageMapper;
    @Override
    public void push(Long id) {
        Message message = messageMapper.loadById(id);
        //集群环境
        amqpTemplate.convertAndSend("exchange","a",message);
    }
3.2.4 小结

六 总结

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在微信小程序中,可以通过连接websocket来实现消息。首先,在页面的onLoad方法中,使用wx.connectSocket方法连接到websocket服务器的地址。例如,可以使用ws://你的IP地址:9501/作为连接地址。连接成功后,可以在wx.onSocketOpen方法中监听到连接成功的事件。接下来,可以使用wx.onSocketMessage方法监听到从服务器接收到的消息事件。可以通过JSON.parse将接收到的消息数据进行解析,并进行相应的处理。比如将消息添加到数据中,并使用setData方法更新页面的数据显示。最后,可以使用wx.setStorageSync方法将消息和相应的数据进行缓存,以便在后续使用。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [微信小程序使用swoole实现websocket消息](https://blog.csdn.net/qq_58589723/article/details/121564085)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] - *2* [利用php websocket实现小程序消息或即时通信功能 wss的实现](https://blog.csdn.net/ganggang4321/article/details/89952595)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值