本文目录导读:

我将为您提供一个完整的Java长轮询实现案例,包括服务端和客户端。
基础概念
长轮询(Long Polling) 是Web应用中常用的实时通信方案:
- 客户端发起请求
- 服务端保持连接,直到有新数据或超时
- 数据到达后立即返回响应
- 客户端收到响应后立即发起新的请求
服务端实现
1 使用Spring MVC实现
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@RestController
public class LongPollingController {
// 存储等待响应的请求
private final Map<String, DeferredResult<String>> pendingRequests = new ConcurrentHashMap<>();
// 定时任务线程池
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2);
/**
* 长轮询接口
*/
@GetMapping("/api/long-polling")
public DeferredResult<String> longPolling(
@RequestParam String requestId,
@RequestParam(defaultValue = "30000") long timeout) {
// 创建DeferredResult,设置超时时间(毫秒)
DeferredResult<String> deferredResult = new DeferredResult<>(timeout);
// 存储请求
pendingRequests.put(requestId, deferredResult);
// 设置超时回调
deferredResult.onTimeout(() -> {
pendingRequests.remove(requestId);
deferredResult.setResult("timeout");
});
// 设置完成回调
deferredResult.onCompletion(() -> {
pendingRequests.remove(requestId);
});
System.out.println("收到请求: " + requestId + ", 当前等待数: " + pendingRequests.size());
return deferredResult;
}
/**
* 触发数据更新接口
*/
@GetMapping("/api/trigger")
public String trigger(@RequestParam String requestId, @RequestParam String message) {
DeferredResult<String> deferredResult = pendingRequests.remove(requestId);
if (deferredResult != null) {
deferredResult.setResult("消息: " + message);
return "已发送响应给: " + requestId;
}
return "未找到对应的请求: " + requestId;
}
/**
* 批量触发接口(用于测试)
*/
@GetMapping("/api/trigger-all")
public String triggerAll(@RequestParam String message) {
int count = 0;
for (Map.Entry<String, DeferredResult<String>> entry : pendingRequests.entrySet()) {
entry.getValue().setResult("批量消息: " + message);
count++;
}
return "已发送响应给 " + count + " 个请求";
}
}
2 更高级的服务端实现
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.Map;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;
@Service
public class LongPollingService {
// 使用CompletableFuture实现异步
private final ConcurrentHashMap<String, CompletableFuture<String>> requests = new ConcurrentHashMap<>();
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(4);
@PostConstruct
public void init() {
// 启动后台线程模拟数据推送
simulateDataPush();
}
@PreDestroy
public void destroy() {
scheduler.shutdown();
}
/**
* 等待消息更新
*/
public CompletableFuture<String> waitForUpdate(String requestId, long timeout) {
CompletableFuture<String> future = new CompletableFuture<>();
requests.put(requestId, future);
// 设置超时
scheduler.schedule(() -> {
future.complete("timeout");
requests.remove(requestId);
}, timeout, TimeUnit.MILLISECONDS);
return future;
}
/**
* 推送消息给指定请求
*/
public void pushMessage(String requestId, String message) {
CompletableFuture<String> future = requests.remove(requestId);
if (future != null) {
future.complete(message);
}
}
/**
* 模拟数据推送
*/
private void simulateDataPush() {
scheduler.scheduleAtFixedRate(() -> {
System.out.println("模拟数据推送: 当前等待请求数 " + requests.size());
// 可以在这里添加业务逻辑
}, 5, 5, TimeUnit.SECONDS);
}
}
3 Spring WebFlux实现(响应式)
import org.springframework.http.codec.ServerSentEvent;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Sinks;
import java.time.Duration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@RestController
public class WebFluxLongPollingController {
// 使用Sinks实现多播
private final Map<String, Sinks.Many<String>> sinks = new ConcurrentHashMap<>();
@GetMapping("/api/reactive-long-polling")
public Flux<ServerSentEvent<String>> reactiveLongPolling(String requestId) {
Sinks.Many<String> sink = Sinks.many().multicast().onBackpressureBuffer();
sinks.put(requestId, sink);
// 设置超时
return sink.asFlux()
.take(1)
.map(data -> ServerSentEvent.builder(data).build())
.timeout(Duration.ofSeconds(30))
.doFinally(signalType -> sinks.remove(requestId));
}
@GetMapping("/api/reactive-trigger")
public String reactiveTrigger(String requestId, String message) {
Sinks.Many<String> sink = sinks.get(requestId);
if (sink != null) {
sink.tryEmitNext(message);
return "sent";
}
return "not found";
}
}
客户端实现
1 Java原生客户端
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class LongPollingClient {
private static final String SERVER_URL = "http://localhost:8080/api/long-polling";
private static final ExecutorService executor = Executors.newCachedThreadPool();
public static void main(String[] args) {
// 启动多个客户端
for (int i = 0; i < 3; i++) {
final String clientId = "client-" + i;
executor.submit(() -> startLongPolling(clientId));
}
// 防止主线程退出
try {
Thread.sleep(60000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private static void startLongPolling(String clientId) {
while (true) {
try {
String response = sendLongPollingRequest(clientId);
System.out.println(clientId + " 收到响应: " + response);
// 收到响应后立即发起新的请求
Thread.sleep(100); // 短暂延迟
} catch (Exception e) {
System.err.println(clientId + " 请求异常: " + e.getMessage());
try {
Thread.sleep(1000); // 错误延迟
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
break;
}
}
}
}
private static String sendLongPollingRequest(String clientId) throws Exception {
String urlStr = SERVER_URL + "?requestId=" + clientId + "&timeout=30000";
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(2000);
conn.setReadTimeout(35000); // 稍大于服务端超时
int responseCode = conn.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String response = in.readLine();
in.close();
return response;
} else {
return "HTTP Error: " + responseCode;
}
}
}
2 Spring WebClient客户端
import org.springframework.web.reactive.function.client.WebClient;
import java.time.Duration;
import java.util.concurrent.atomic.AtomicInteger;
public class WebClientLongPolling {
private final WebClient webClient;
private final AtomicInteger requestCount = new AtomicInteger(0);
public WebClientLongPolling() {
this.webClient = WebClient.builder()
.baseUrl("http://localhost:8080")
.build();
}
public void startPolling(String clientId) {
webClient.get()
.uri(uriBuilder -> uriBuilder
.path("/api/long-polling")
.queryParam("requestId", clientId)
.queryParam("timeout", 30000)
.build())
.retrieve()
.bodyToMono(String.class)
.timeout(Duration.ofSeconds(35))
.doOnSuccess(response -> {
System.out.println("收到响应: " + response);
int count = requestCount.incrementAndGet();
System.out.println("请求次数: " + count);
// 递归调用
startPolling(clientId);
})
.doOnError(error -> {
System.err.println("请求错误: " + error.getMessage());
// 延迟重试
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
startPolling(clientId);
})
.subscribe();
}
public static void main(String[] args) {
WebClientLongPolling client = new WebClientLongPolling();
client.startPolling("webclient-1");
// 防止主线程退出
try {
Thread.sleep(60000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3 HTML/JavaScript客户端
<!DOCTYPE html>
<html>
<head>Long Polling Demo</title>
<script>
class LongPollingClient {
constructor(url) {
this.url = url;
this.isPolling = false;
this.requestCount = 0;
}
async start() {
if (this.isPolling) return;
this.isPolling = true;
document.getElementById('status').textContent = '开始轮询...';
this.poll();
}
async poll() {
if (!this.isPolling) return;
try {
document.getElementById('status').textContent = '等待数据...';
const response = await fetch(this.url);
const data = await response.text();
this.requestCount++;
document.getElementById('requests').textContent = this.requestCount;
document.getElementById('messages').innerHTML +=
`收到消息: ${data}<br>`;
console.log('收到响应:', data);
// 再次发起请求
this.poll();
} catch (error) {
console.error('请求失败:', error);
document.getElementById('status').textContent = '请求失败,重试中...';
// 错误延迟重试
setTimeout(() => this.poll(), 1000);
}
}
stop() {
this.isPolling = false;
document.getElementById('status').textContent = '已停止';
}
}
// 初始化
const client = new LongPollingClient('/api/long-polling?requestId=client1&timeout=30000');
</script>
<style>
body { font-family: Arial, sans-serif; padding: 20px; }
.container { max-width: 600px; margin: 0 auto; }
#messages {
border: 1px solid #ddd;
padding: 10px;
min-height: 200px;
margin: 20px 0;
}
button { margin: 5px; padding: 10px; }
</style>
</head>
<body>
<div class="container">
<h1>长轮询演示</h1>
<p>状态: <span id="status">未启动</span></p>
<p>请求次数: <span id="requests">0</span></p>
<button onclick="client.start()">开始轮询</button>
<button onclick="client.stop()">停止轮询</button>
<div id="messages">消息列表:<br></div>
</div>
</body>
</html>
完整配置示例
1 Spring Boot配置
# application.yml
spring:
application:
name: long-polling-demo
server:
port: 8080
tomcat:
max-threads: 200
min-spare-threads: 50
max-connections: 10000
accept-count: 100
logging:
level:
com.example: DEBUG
2 配置类
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class ServerConfig {
@Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> containerCustomizer() {
return factory -> {
// 设置连接超时
factory.addConnectorCustomizers(connector -> {
connector.setKeepAliveTimeout(60000);
connector.setMaxKeepAliveRequests(-1); // 无限
});
};
}
}
测试脚本
import java.util.concurrent.atomic.AtomicInteger;
public class LongPollingLoadTest {
private static final int THREAD_COUNT = 10;
private static final AtomicInteger successCount = new AtomicInteger(0);
private static final AtomicInteger timeoutCount = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
System.out.println("开始负载测试,线程数: " + THREAD_COUNT);
Thread[] threads = new Thread[THREAD_COUNT];
long startTime = System.currentTimeMillis();
for (int i = 0; i < THREAD_COUNT; i++) {
final int threadId = i;
threads[i] = new Thread(() -> {
try {
for (int j = 0; j < 10; j++) {
testRequest(threadId, j);
}
} catch (Exception e) {
System.err.println("线程 " + threadId + " 异常: " + e.getMessage());
}
});
threads[i].start();
}
// 等待所有线程完成
for (Thread thread : threads) {
thread.join();
}
long duration = System.currentTimeMillis() - startTime;
System.out.println("\n测试完成,耗时: " + duration + "ms");
System.out.println("成功请求: " + successCount.get());
System.out.println("超时请求: " + timeoutCount.get());
}
private static void testRequest(int threadId, int requestNum) throws Exception {
String requestId = "thread-" + threadId + "-req-" + requestNum;
long startTime = System.currentTimeMillis();
String response = LongPollingClientUtil.sendRequest(requestId);
long duration = System.currentTimeMillis() - startTime;
if ("timeout".equals(response)) {
timeoutCount.incrementAndGet();
System.out.println(requestId + " 超时,耗时: " + duration + "ms");
} else {
successCount.incrementAndGet();
System.out.println(requestId + " 成功: " + response + ",耗时: " + duration + "ms");
}
}
}
// 客户端工具类
class LongPollingClientUtil {
public static String sendRequest(String requestId) throws Exception {
// 简化实现,实际应使用HTTP客户端
java.net.URL url = new java.net.URL(
"http://localhost:8080/api/long-polling?requestId=" + requestId + "&timeout=1000"
);
java.net.HttpURLConnection conn = (java.net.HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(2000);
conn.setReadTimeout(5000);
int code = conn.getResponseCode();
if (code == 200) {
java.io.BufferedReader reader = new java.io.BufferedReader(
new java.io.InputStreamReader(conn.getInputStream())
);
return reader.readLine();
}
return "error-" + code;
}
}
注意事项
- 连接管理:使用连接池避免频繁创建连接
- 超时处理:合理设置超时时间,避免资源浪费
- 并发控制:限制最大并发数,防止服务器过载
- 心跳机制:可考虑WebSocket或SSE实现心跳检测
- 失败重试:实现优雅的重试机制,避免雪崩
这个案例提供了完整的Java长轮询实现,包括服务端、客户端和测试代码,可以直接应用到实际项目中。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
足球直播
足球直播
足球直播
篮球直播
篮球直播
篮球直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
亚冠直播
亚冠直播
亚冠直播
欧冠直播
欧冠直播
欧冠直播
英超直播
英超直播
英超直播
西甲直播
西甲直播
西甲直播
中超直播
中超直播
中超直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
曼联直播
曼联直播
曼联直播
日职联直播
日职联直播
日职联直播
澳超直播
澳超直播
澳超直播
阿森纳直播
阿森纳直播
阿森纳直播
足球直播
足球直播
足球直播
NBA直播
NBA直播
NBA直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
欧冠直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播
中超直播