
长轮询是一种客户端-服务器通信模式,客户端发起一个http请求,服务器端在有新数据可用或达到特定超时时间之前保持该请求开放。这在需要“实时”更新但又无法使用websockets(例如,客户端环境受限或不支持)的场景中非常有用。
然而,在Google App Engine的标准前端实例上实现长轮询存在一个核心障碍:HTTP请求具有严格的60秒截止时间。这意味着任何超过60秒的请求都将被GAE强制终止,这使得传统意义上的长轮询无法在前端实例上有效运行。
此外,Go语言中常用的并发原语如goroutine和channel虽然强大,但在前端实例上用于长时间持有请求时,仍受限于这个60秒的请求生命周期。如果应用场景允许,GAE提供了Channel API,它专门用于在浏览器和应用之间建立持久连接,但如果客户端应用不受控制(例如,需要与外部、预先存在的长轮询协议兼容的系统集成,如问题中提及的deepbit.net),则Channel API并非一个可行的选项。在这种特定情况下,我们需要一个能够突破60秒限制的服务器端解决方案。
为了克服GAE前端实例的60秒请求限制,最有效的策略是利用GAE的Backends(在现代GAE架构中,这通常对应于配置了手动或基本缩放类型的服务,特别是灵活环境服务)。与前端实例不同,Backends(或具有特定缩放配置的服务)不强制执行60秒的HTTP请求截止时间,它们可以处理持续更长时间的请求,甚至理论上是无限的。
Backends/灵活环境服务的优势:
立即学习“go语言免费学习笔记(深入)”;
架构考量:
通常,前端服务(标准环境)负责处理用户界面的常规请求,而长轮询请求则路由到专门的后端服务。这种分离可以确保前端服务的响应性不受长轮询请求的影响。
以下是一个概念性的Go语言HTTP处理程序示例,展示了如何在GAE后端服务中处理长轮询请求。此示例模拟了一个事件源,并在有新事件时响应客户端。
package main
import (
"context"
"fmt"
"log"
"net/http"
"time"
)
// 模拟一个全局的事件发布器
// 实际应用中,这可能是一个消息队列、数据库监听或内部事件总线
var globalEventBus = make(chan string)
func init() {
// 模拟每隔一段时间发布一个新事件
// 在实际后端服务中,这个goroutine可能负责从其他服务或数据源获取事件
go func() {
for i := 0; ; i++ {
time.Sleep(15 * time.Second) // 每15秒发布一个事件
event := fmt.Sprintf("Event %d occurred at %s", i, time.Now().Format(time.RFC3339))
log.Printf("Publishing event: %s", event)
// 将事件发送到所有等待的监听器(通过扇出机制实现)
// 简单的示例,实际需考虑并发安全和多个客户端
select {
case globalEventBus <- event:
default:
// 如果没有监听者,则丢弃事件或缓冲
log.Println("No active long polling listeners for event.")
}
}
}()
}
// longPollingHandler 处理长轮询请求
func longPollingHandler(w http.ResponseWriter, r *http.Request) {
log.Printf("Received long polling request from %s", r.RemoteAddr)
// 创建一个用于等待事件的通道
// 每个长轮询请求都应该有自己的等待机制
eventChan := make(chan string)
// 使用goroutine等待事件或请求超时
go func() {
select {
case event := <-globalEventBus: // 假设globalEventBus是一个扇出(fan-out)通道
eventChan <- event
case <-time.After(55 * time.Second): // 服务器端设置的单个轮询超时
eventChan <- "timeout"
case <-r.Context().Done(): // 客户端断开连接
log.Printf("Client disconnected during long poll from %s", r.RemoteAddr)
return // 提前退出,避免写入已关闭的连接
}
}()
select {
case data := <-eventChan:
if data == "timeout" {
// 没有新数据,告知客户端重新轮询
w.WriteHeader(http.StatusNoContent) // 204 No Content
fmt.Fprint(w, "No new data within server timeout, please re-poll.")
log.Printf("Long polling request timed out for %s. Client should re-poll.", r.RemoteAddr)
} else {
// 有新数据,返回给客户端
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "New data: %s", data)
log.Printf("Long polling request responded with data for %s", r.RemoteAddr)
}
case <-r.Context().Done():
// 在等待事件时客户端断开连接
log.Printf("Client disconnected before server could respond for %s", r.RemoteAddr)
}
}
func main() {
http.HandleFunc("/poll", longPollingHandler)
// GAE服务通常监听PORT环境变量
port := "8080" // 默认端口,GAE会注入
if p := os.Getenv("PORT"); p != "" {
port = p
}
log.Printf("GAE Backend service listening on port %s for long polling requests", port)
if err := http.ListenAndServe(":"+port, nil); err != nil {
log.Fatalf("Server failed to start: %v", err)
}
}代码说明:
globalEventBus: 这是一个模拟的全局事件通道。在真实的生产环境中,你需要一个更健壮的事件发布-订阅系统,例如使用Google Cloud Pub/Sub,或者一个内部的扇出(fan-out)机制来将事件广播给所有等待的客户端。
longPollingHandler: 这是处理长轮询请求的核心。
部署到GAE后端:
# app.yaml for the long polling backend service runtime: go118 # 或更高版本 service: long-polling-backend # 自定义服务名称 manual_scaling: instances: 1 # 或更多,根据需求配置 # 或者 basic_scaling # basic_scaling: # max_instances: 5 # 根据流量自动扩缩,但实例会一直运行直到空闲 # idle_timeout: 10m # 实例空闲10分钟后关闭 handlers: - url: /poll script: auto # entrypoint: go run main.go # 如果需要指定启动命令
在Google App Engine的Go语言环境中实现长轮询,当GAE Channel API因客户端限制而不可用时,核心策略是利用具有无限请求截止时间的GAE Backends(或灵活环境服务)。通过将长轮询请求路由到这些专用服务,我们可以突破前端实例的60秒限制,从而有效地为不受控的客户端提供持久的、类实时的数据更新。在实施过程中,需要仔细考虑客户端重试、服务器端资源管理、事件通知机制和安全性,以构建一个健壮可靠的长轮询系统。
以上就是Go语言在Google App Engine上实现长轮询:突破60秒请求限制的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号