Comet(Server-Side Push)讓服務器主動推送數據到瀏覽器,打破 HTTP 請求-響應模型。本文對比 Tomcat 原生 API、Jetty Continuation、Servlet 3.0 標準化異步三種實現路徑。
| 模式 | 機制 | 延遲 | 連接佔用 | 瀏覽器兼容 |
|---|---|---|---|---|
| 短輪詢 | 定時 AJAX 請求 | 高 (間隔時間) | 極短 | 完美 |
| 長輪詢 | 請求掛起直到有數據 | 極低 | 中等 (數秒-分) | 完美 |
| 流/iframe | 持久連接分塊傳輸 | 極低 | 長 (直到超時) | 僅 Firefox 可靠 |
// server.xml 配置 NIO 連接器
<Connector port="8080" protocol="org.apache.coyote.http11.Http11NioProtocol" />
// Servlet 實現
public class WeatherServlet extends HttpServlet implements CometProcessor {
private MessageSender sender = new MessageSender();
public void event(CometEvent event) throws IOException, ServletException {
HttpServletRequest req = event.getHttpServletRequest();
HttpServletResponse resp = event.getHttpServletResponse();
switch (event.getEventType()) {
case BEGIN:
req.setAttribute("org.apache.tomcat.comet.timeout", 60_000);
sender.setConnection(resp);
new Thread(new DataProducer(sender)).start();
break;
case END: case ERROR:
event.close();
break;
case READ:
throw new UnsupportedOperationException();
}
}
}
特點:Tomcat 專有 API,事件模型清晰 (BEGIN/READ/END/ERROR),需配置 NIO 連接器。
public class WeatherServlet extends HttpServlet {
private MessageSender sender = new MessageSender();
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp)
throws IOException, ServletException {
Continuation cont = ContinuationSupport.getContinuation(req);
if (cont.isPending()) {
// 恢復階段:發送數據
resp.getWriter().write((String) cont.getAttribute("data"));
cont.complete();
} else {
// 掛起階段:釋放線程等待數據
cont.suspend(5_000); // 5秒超時
new Thread(new DataProducer(cont)).start();
}
}
}
特點:Jetty 專有 Continuation API,語義為「掛起-恢復」,線程釋放更直觀。
@WebServlet(urlPatterns = "/weather", asyncSupported = true, asyncTimeout = 5000)
public class WeatherServlet extends HttpServlet {
private MessageSender sender = new MessageSender();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp)
throws IOException, ServletException {
AsyncContext async = req.startAsync(req, resp);
sender.setConnection(async); // 傳遞 AsyncContext 而非 Response
async.start(new DataProducer(sender)); // 容器管理線程池
}
}
// 發送端
class DataProducer implements Runnable {
private AsyncContext async;
public void run() {
try {
async.getResponse().getWriter().write("data...");
async.complete(); // 關鍵:標記完成
} catch (IOException e) { /* handle */ }
}
}
優勢:標準化、可移植、容器管理線程池、支援過濾器鏈異步、超時自動處理。
function poll() {
fetch('/weather')
.then(r => r.text())
.then(data => {
document.getElementById('forecast').innerHTML = data;
poll(); // 立即發起下一輪
})
.catch(() => setTimeout(poll, 5000)); // 錯誤重試
}
poll();