55 "errors"
66 "fmt"
77 "io"
8+ "net"
89 "net/http"
910 "net/url"
1011 "os"
@@ -23,16 +24,20 @@ import (
2324)
2425
2526const (
26- downloadTimeout = 5 * time .Minute
27- maxReconnectDelay = 5 * time .Minute
27+ downloadTimeout = 30 * time .Second
28+ minReconnectDelay = 1 * time .Second // 最小重连延迟
29+ maxReconnectDelay = 30 * time .Second // 最大重连延迟
30+ fastReconnectAttempt = 3 // 快速重连尝试次数
31+ tcpKeepaliveInterval = 30 * time .Second // TCP keepalive 间隔
32+ heartbeatInterval = 30 * time .Second // 应用层心跳间隔 - 保持连接活跃
2833)
2934
3035var (
3136 isConnected atomic.Bool
3237)
3338
3439type Client struct {
35- clientID string
40+ clientId string
3641 serverURL string
3742 httpClient * http.Client
3843 connectClient deployPBconnect.DeployServiceClient
@@ -42,49 +47,70 @@ type Client struct {
4247 systemInfo * system.SystemInfo // 缓存的系统信息
4348 systemInfoOnce sync.Once // 确保系统信息只获取一次
4449 httpServer * server.HTTPServer // HTTP-01 验证服务器
50+ busyOperations atomic.Int32 // 正在执行的业务操作数量
4551}
4652
4753func NewClient (ctx context.Context ) (* Client , error ) {
4854 cfg := config .GetConfig ()
4955
5056 // 生成客户端ID
51- clientID , err := system .GetUniqueClientID (ctx )
57+ clientId , err := system .GetUniqueClientId (ctx )
5258 if err != nil {
5359 return nil , err
5460 }
5561
56- // 配置 HTTP 客户端
57- httpClient := & http.Client {
58- Timeout : 30 * time .Second ,
59- }
62+ // 配置 HTTP Transport
63+ var transport http.RoundTripper
64+
6065 if cfg .Server .Env == "local" {
6166 p := new (http.Protocols )
6267 p .SetUnencryptedHTTP2 (true )
63- httpClient = & http.Client {
64- Timeout : 30 * time .Second ,
65- Transport : & http.Transport {
66- Protocols : p ,
67- MaxIdleConns : 100 ,
68- MaxIdleConnsPerHost : 10 ,
69- IdleConnTimeout : 90 * time .Second ,
70- },
68+ transport = & http.Transport {
69+ Protocols : p ,
70+ MaxIdleConns : 100 ,
71+ MaxIdleConnsPerHost : 10 ,
72+ IdleConnTimeout : 90 * time .Second ,
73+ DisableKeepAlives : false ,
74+ ForceAttemptHTTP2 : true ,
75+ TLSHandshakeTimeout : 10 * time .Second ,
76+ ResponseHeaderTimeout : 30 * time .Second ,
77+ ExpectContinueTimeout : 1 * time .Second ,
78+ DialContext : (& net.Dialer {
79+ Timeout : 10 * time .Second ,
80+ KeepAlive : tcpKeepaliveInterval ,
81+ }).DialContext ,
7182 }
7283 } else {
73- httpClient .Transport = & http.Transport {
74- MaxIdleConns : 100 ,
75- MaxIdleConnsPerHost : 10 ,
76- IdleConnTimeout : 90 * time .Second ,
84+ transport = & http.Transport {
85+ MaxIdleConns : 100 ,
86+ MaxIdleConnsPerHost : 10 ,
87+ IdleConnTimeout : 90 * time .Second ,
88+ DisableKeepAlives : false ,
89+ ForceAttemptHTTP2 : true ,
90+ TLSHandshakeTimeout : 10 * time .Second ,
91+ ResponseHeaderTimeout : 30 * time .Second ,
92+ ExpectContinueTimeout : 1 * time .Second ,
93+ DialContext : (& net.Dialer {
94+ Timeout : 10 * time .Second ,
95+ KeepAlive : tcpKeepaliveInterval ,
96+ }).DialContext ,
7797 }
7898 }
7999
100+ httpClient := & http.Client {
101+ Timeout : 0 ,
102+ Transport : transport ,
103+ }
104+
80105 client := & Client {
81- clientID : clientID ,
106+ clientId : clientId ,
82107 serverURL : config .URL ,
83108 httpClient : httpClient ,
84109 ctx : ctx ,
85110 accessKey : cfg .Server .AccessKey ,
86111 }
87112
113+ // 创建 connect client
88114 client .connectClient = deployPBconnect .NewDeployServiceClient (httpClient , config .URL )
89115
90116 // 启动连接通知
@@ -107,9 +133,9 @@ func (c *Client) SetHTTPServer(httpServer *server.HTTPServer) {
107133 c .httpServer = httpServer
108134}
109135
110- // StartConnectNotify 启动连接通知
136+ // StartConnectNotify 启动连接通知 - 建立持久连接并通过心跳保持
111137func (c * Client ) StartConnectNotify () {
112- reconnectDelay := time . Second
138+ reconnectDelay := minReconnectDelay
113139 consecutiveFailures := 0
114140
115141 for {
@@ -123,24 +149,27 @@ func (c *Client) StartConnectNotify() {
123149 stream , err := c .connectClient .Notify (c .ctx )
124150 if err != nil {
125151 consecutiveFailures ++
126-
127- // 只在状态变化时打印日志
128152 if isConnected .Load () || consecutiveFailures == 1 {
129- logger .Error ("连接失败" , "error" , err )
153+ logger .Error ("连接失败" , "error" , err , "attempt" , consecutiveFailures )
130154 }
131155
132156 isConnected .Store (false )
133157 c .lastDisconnectLogged .Store (true )
134158
135- // 等待重连
159+ // 指数退避重连
160+ if consecutiveFailures <= fastReconnectAttempt {
161+ reconnectDelay = minReconnectDelay
162+ } else {
163+ reconnectDelay = min (reconnectDelay * 2 , maxReconnectDelay )
164+ }
165+
136166 time .Sleep (reconnectDelay )
137- reconnectDelay = min (reconnectDelay * 2 , maxReconnectDelay )
138167 continue
139168 }
140169
141- // 连接成功,重置计数器
170+ // 连接成功,重置失败计数
142171 consecutiveFailures = 0
143- reconnectDelay = time . Second
172+ reconnectDelay = minReconnectDelay
144173
145174 // 获取系统信息(使用缓存)
146175 systemInfo , err := c .getSystemInfo ()
@@ -151,10 +180,10 @@ func (c *Client) StartConnectNotify() {
151180 continue
152181 }
153182
154- // 构造注册请求
183+ // 构造并发送注册请求
155184 registerReq := & deployPB.NotifyRequest {
156185 AccessKey : c .accessKey ,
157- ClientId : c .clientID ,
186+ ClientId : c .clientId ,
158187 Version : config .Version ,
159188 Data : & deployPB.NotifyRequest_RegisterResponse {
160189 RegisterResponse : & deployPB.RegisterResponse {
@@ -168,54 +197,53 @@ func (c *Client) StartConnectNotify() {
168197 },
169198 }
170199
171- // 注册客户端
172200 if err := stream .Send (registerReq ); err != nil {
201+ logger .Error ("注册失败" , "error" , err )
173202 stream .CloseRequest ()
174203 time .Sleep (reconnectDelay )
175204 continue
176205 }
177206
178- // 流断开,先检查主 context 是否被取消(而不是检查错误类型)
179- // 因为错误链中可能包含 context.Canceled,但实际是连接断开导致的
180- select {
181- case <- c .ctx .Done ():
182- logger .Info ("主 context 已取消,退出连接循环" )
183- return
184- default :
185- }
207+ logger .Info ("连接已建立,开始处理消息" )
186208
187- // 处理消息流
188- if err := c .handleNotifyStream (stream ); err != nil {
189- // logger.Error("连接断开", "error", err)
209+ // 处理消息流 - 正常情况下会因为心跳保持而永不返回
210+ streamErr := c .handleNotifyStream (stream )
211+
212+ // 只有在异常情况下才会到这里(网络故障、服务端主动断开等)
213+ stream .CloseRequest ()
214+
215+ busyOps := c .busyOperations .Load ()
216+ if busyOps > 0 {
217+ logger .Warn ("连接意外断开(有业务正在执行)" , "error" , streamErr , "busyOps" , busyOps )
218+ } else {
219+ logger .Info ("连接断开,准备重连" , "error" , streamErr )
190220 }
191221
192- // 标记断开连接
193222 isConnected .Store (false )
194223 c .lastDisconnectLogged .Store (true )
195224
196- // 等待后重连
225+ // 短暂延迟后重连
197226 time .Sleep (reconnectDelay )
198- reconnectDelay = min (reconnectDelay * 2 , maxReconnectDelay )
199227 }
200228}
201229
202230// handleNotifyStream 处理通知流
203231func (c * Client ) handleNotifyStream (stream * connect.BidiStreamForClientSimple [deployPB.NotifyRequest , deployPB.NotifyResponse ]) error {
204- // 启动心跳 goroutine
232+ // 启动心跳 goroutine - 持续发送心跳保持连接活跃
205233 heartbeatCtx , cancelHeartbeat := context .WithCancel (c .ctx )
206234 defer cancelHeartbeat ()
207235
208236 go c .sendHeartbeat (heartbeatCtx , stream )
209237
210- receiveCount := 0
238+ // 简单的消息接收循环
211239 for {
212240 select {
213241 case <- c .ctx .Done ():
214242 return c .ctx .Err ()
215243 default :
216244 }
217245
218- // 阻塞接收消息
246+ // 阻塞接收消息 (无超时限制,依赖 TCP keepalive 和心跳保持连接)
219247 req , err := stream .Receive ()
220248 if err != nil {
221249 if errors .Is (err , io .EOF ) {
@@ -224,15 +252,11 @@ func (c *Client) handleNotifyStream(stream *connect.BidiStreamForClientSimple[de
224252 return fmt .Errorf ("接收消息失败: %w" , err )
225253 }
226254
227- receiveCount ++
228-
229255 // 首次收到消息,标记连接成功
230256 if ! isConnected .Load () {
231257 isConnected .Store (true )
232-
233- // 如果之前断开过连接,打印重连成功日志
234258 if c .lastDisconnectLogged .Load () {
235- // logger.Info("重新连接成功")
259+ logger .Info ("重新连接成功" )
236260 c .lastDisconnectLogged .Store (false )
237261 }
238262 }
@@ -274,7 +298,7 @@ func (c *Client) handleMessage(stream *connect.BidiStreamForClientSimple[deployP
274298
275299// sendHeartbeat 定期发送心跳(保持连接活跃)
276300func (c * Client ) sendHeartbeat (ctx context.Context , stream * connect.BidiStreamForClientSimple [deployPB.NotifyRequest , deployPB.NotifyResponse ]) {
277- ticker := time .NewTicker (30 * time . Second )
301+ ticker := time .NewTicker (heartbeatInterval )
278302 defer ticker .Stop ()
279303
280304 for {
@@ -285,11 +309,11 @@ func (c *Client) sendHeartbeat(ctx context.Context, stream *connect.BidiStreamFo
285309 // 发送心跳消息
286310 err := stream .Send (& deployPB.NotifyRequest {
287311 AccessKey : c .accessKey ,
288- ClientId : c .clientID ,
312+ ClientId : c .clientId ,
289313 Version : config .Version ,
290314 })
291315 if err != nil {
292- // logger.Error ("发送心跳失败", "error", err)
316+ logger .Debug ("发送心跳失败" , "error" , err )
293317 return
294318 }
295319 }
0 commit comments