Web系统设计 --- 后端消息推送

Web系统设计 --- 后端消息推送

轮询

  • 基本原理:客户端定期发送请求来查询服务器是否有新数据或事件,并将响应返回给客户端。如果服务器有新的数据或事件,则将其返回给客户端;如果没有,则返回一个空响应。客户端收到响应后,可以处理数据或事件,并根据需要继续发送下一个请求。
  • 轮询的优缺点
  • 优点:简单易实现,适用于各种浏览器和服务器。
  • 缺点:轮询会产生大量的无效请求,浪费带宽和服务器资源., 并且数据不一定是实时更新,要看设定的请求间隔,基本会有延迟
  • 轮询的使用场景
    用这种方式的场景也有很多,最常见的就是扫码登录, 比如,某信公众号平台,登录页面二维码出现之后,前端网页根本不知道用户扫没扫,于是不断去向后端服务器询问,看有没有人扫过这个码

长轮询

  • 长轮询是长连接的一种,当服务器收到客户端发来的请求后,服务器端不会直接进行响应,而是先将这个请求挂起,然后判断服务器端数据是否有更新。如果有更新,则进行响应,如果一直没有数据,则会 hold 住请求,直到服务端的数据发生变化,或者等待一定时间超时才会返回.
  • 还是之前扫码登录的例子
  • 如果我们的 HTTP 请求将超时设置的很大,比如 30 秒,在这 30 秒内只要服务器收到了扫码请求,就立马返回给客户端网页。如果超时,那就立马发起下一次请求。 这样就减少了 HTTP 请求的个数,并且由于大部分情况下,用户都会在某个 30 秒的区间内做扫码操作,所以响应也是及时的。比如百度网盘
    在这里插入图片描述
  • 还有我们常用的消息队列 RocketMQ 中,消费者去取数据时,也用到了这种方式.

Example: 服务端

namespace LongPollingExample.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class PollingController : ControllerBase
    {
        // 模拟一个共享的内存数据源,用于存储消息
        private static readonly List<string> Messages = new List<string>();
        private static readonly object LockObject = new object();

        // GET api/polling
        [HttpGet]
        public async Task<IActionResult> Get(CancellationToken cancellationToken)
        {
            string message = null;

            // 等待新的消息到来
            while (message == null)
            {
                lock (LockObject)
                {
                    if (Messages.Count > 0)
                    {
                        message = Messages[0];
                        Messages.RemoveAt(0);
                    }
                }

                if (message == null)
                {
                    // 没有新消息,等待一段时间后重试
                    await Task.Delay(1000, cancellationToken);
                }
            }

            return Ok(message);
        }

        // POST api/polling
        [HttpPost]
        public IActionResult Post([FromBody] string message)
        {
            lock (LockObject)
            {
                Messages.Add(message);
            }

            return Ok();
        }
    }
}

客户端

namespace LongPollingClient
{
    class Program
    {
        static async Task Main(string[] args)
        {
            HttpClient client = new HttpClient();
            string apiUrl = "http://localhost:5000/api/polling";

            // 启动一个任务来发送消息
            Task.Run(async () =>
            {
                while (true)
                {
                    Console.WriteLine("请输入要发送的消息:");
                    string message = Console.ReadLine();
                    await client.PostAsync(apiUrl, new StringContent($"\"{message}\"", Encoding.UTF8, "application/json"));
                }
            });

            // 长轮询来接收消息
            while (true)
            {
                try
                {
                    HttpResponseMessage response = await client.GetAsync(apiUrl);
                    response.EnsureSuccessStatusCode();
                    string message = await response.Content.ReadAsStringAsync();
                    Console.WriteLine($"收到消息:{message}");
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"轮询时出错:{ex.Message}");
                }
            }
        }
    }
}

SSE

  • Server-Sent Events(SSE)它提供了一种从服务器实时发送不断更新发送事件到客户端的技术。SSE主要解决了客户端与服务器之间的单向实时通信需求(例如ChatGpt回答的流式输出),
  • 客户端向服务端发起HTTP长连接,服务端返回stream响应流。客户端收到stream响应流并不会关闭连接而是一直等待服务端发送新的数据流 (重点在于客户端一直监听连接,不关闭)

服务端

   //一、创建controller 使用你自己定义的url
    @RequestMapping(value = "/chat-sse")
    public ResponseBodyEmitter chatSse(@RequestBody String params) throws IOException {
        SseEmitter sseEmitter = new SseEmitter();
        String message = JSONObject.parseObject(params).getString("message");
        gptClient.getGPTMessageSse(message, sseEmitter);
 
        //实际sseEmitter已经返回,新线程10秒之后发送test给客户端
        new Thread("testThread") {
            @Override
            public void run() {
                try {
                    Thread.sleep(10000);
                    sseEmitter.send("test");
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("间隔10秒后发送实时数据给客户端");
            }
        }.start();
        return sseEmitter;
    }

客户端

//设置EventSoure监听的url,监听处理事件,此处我的代码是把新增内容追加到聊天最后  
const eventSource = new EventSource(serverUrl);
    eventSource.onmessage = function (event) {
        const response = JSON.parse(event.data).data;
 
        // 显示服务器的返回并追加到最后
        messageContainer.innerHTML += `<p><strong>GPT:</strong><br /> ${response}</>`;
        messageContainer.scrollTop = messageContainer.scrollHeight;
    };   
 
 
   async function submitMessage(event) {
        event.preventDefault();
 
        //获取用户聊天框输入
        var message = userInput.value.trim();
        if (!message) return;
       
        userInput.value = '';
        // Display user's message
        messageContainer.innerHTML += `<p><strong>You:</strong><br /> ${message}</p>`;
        messageContainer.scrollTop = messageContainer.scrollHeight;
 
        //发起请求
        sendMessageToBackend(message);
    }
 
 
 async function sendMessageToBackend(message) {
        // 使用你的后台地址代替
        const apiUrl = 'url';
        const requestOptions = {
            method: 'POST',
            headers: {'Content-Type': 'application/json', 'Accept': '*'},
            body: JSON.stringify({message})
        };
        try {
            await fetch(apiUrl, requestOptions);
        } catch (error) {
            console.error('Error:', error);
        }
    }
 

WebSocket

  • 对于像扫码登录这样的简单场景还能用用。但如果是网页游戏呢,游戏一般会有大量的数据需要从服务器主动推送到客户端。 这就得说下 WebSocket 了

WebSocket详解

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值