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