Spring Boot 长轮询示例
这是一个 Spring Boot 项目,演示了如何使用长轮询实现服务器推送功能。其中展示两种实现方式:
- 阻塞式长轮询:使用阻塞队列来存储服务器推送的消息,当客户端发起请求时,服务器会一直阻塞,直到有新消息或者超时。
- 非阻塞式长轮询:使用
DeferredResult
来实现非阻塞式的长轮询,当客户端发起请求时,服务器会立即返回一个DeferredResult
对象,然后在后台线程中处理请求,并在处理完成后设置结果。
java
package me.liujiajia.example.longpolling;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;
@RestController
public class LongPollingController {
private static Logger log = LoggerFactory.getLogger(LongPollingController.class);
@Autowired
private HttpServletRequest request;
@Autowired
private HttpServletResponse response;
// 用来存储服务器推送的消息
private static final BlockingQueue<String> messageQueue = new ArrayBlockingQueue<>(10);
// 模拟一个消息生产者线程
static {
new Thread(() -> {
try {
int i = 0;
while (true) {
String message = "Message " + i++;
messageQueue.put(message);
log.info("Produced: {}", message);
// 每 5 秒生产一条消息
TimeUnit.SECONDS.sleep(5);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}).start();
}
@GetMapping("/long-polling-with-block")
public void longPollingWithBlock() throws IOException {
log.info("long-polling-with-block start");
// 设置响应内容类型为文本
response.setContentType("text/plain");
response.setCharacterEncoding("UTF-8");
// 设置响应头,防止浏览器关闭连接
response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate");
response.setHeader("Pragma", "no-cache");
response.setDateHeader("Expires", 0);
PrintWriter out = response.getWriter();
try {
// 从队列中获取消息,如果队列为空则阻塞,直到有新消息或者超时
String message = messageQueue.poll(30, TimeUnit.SECONDS);
log.info("long-polling-with-block poll {}", message);
if (message != null) {
out.println(message);
} else {
out.println("No new messages. Try again later.");
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
out.flush();
out.close();
}
log.info("long-polling-with-block end");
}
@GetMapping("/long-polling-no-block")
public DeferredResult<String> longPollingNoBlock() {
log.info("long-polling-no-block start");
DeferredResult<String> result = new DeferredResult<>();
ForkJoinPool.commonPool().submit(() -> {
String message;
try {
// 从队列中获取消息,如果队列为空则阻塞,直到有新消息或者超时
message = messageQueue.poll(30, TimeUnit.SECONDS);
} catch (InterruptedException e) {
log.error("Interrupted", e);
result.setResult("error");
return;
}
result.setResult(message);
log.info("long-polling-no-block poll {}", message);
});
log.info("long-polling-no-block end");
return result;
}
}
long-polling-with-block_ 接口的日志,阻塞到 poll 成功后,才打印的 end 日志。
java
2025-03-13T18:35:07.392+08:00 INFO 36976 --- [longpolling] [nio-8080-exec-6] m.l.e.longpolling.LongPollingController : long-polling-with-block start
2025-03-13T18:35:11.553+08:00 INFO 36976 --- [longpolling] [ Thread-1] m.l.e.longpolling.LongPollingController : Produced: Message 16
2025-03-13T18:35:11.553+08:00 INFO 36976 --- [longpolling] [nio-8080-exec-6] m.l.e.longpolling.LongPollingController : long-polling-with-block poll Message 16
2025-03-13T18:35:11.555+08:00 INFO 36976 --- [longpolling] [nio-8080-exec-6] m.l.e.longpolling.LongPollingController : long-polling-with-block end
long-polling-no-block 接口的日志,可以看到,先打印的 end 日志,等到队列中添加了消息后才打印的 pool 日志。
java
2025-03-13T18:35:17.442+08:00 INFO 36976 --- [longpolling] [nio-8080-exec-3] m.l.e.longpolling.LongPollingController : long-polling-no-block start
2025-03-13T18:35:17.443+08:00 INFO 36976 --- [longpolling] [nio-8080-exec-3] m.l.e.longpolling.LongPollingController : long-polling-no-block end
2025-03-13T18:35:21.569+08:00 INFO 36976 --- [longpolling] [ Thread-1] m.l.e.longpolling.LongPollingController : Produced: Message 18
2025-03-13T18:35:21.569+08:00 INFO 36976 --- [longpolling] [onPool-worker-2] m.l.e.longpolling.LongPollingController : long-polling-no-block poll Message 18