V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
mentalidade
V2EX  ›  程序员

websocket 的心跳请求

  •  
  •   mentalidade · 2017-12-12 16:58:31 +08:00 · 12061 次点击
    这是一个创建于 2326 天前的主题,其中的信息可能已经有所发展或是发生改变。
    func writer(ws *websocket.Conn) {
    	pingTicker := time.NewTicker(time.Second * 1)
    	fileTicker := time.NewTicker(time.Second * 1)
    	defer func() {
    		pingTicker.Stop()
    		fileTicker.Stop()
    		ws.Close()
    	}()
    
    	for {
    		select {
    		case <-fileTicker.C:
    			ws.SetWriteDeadline(time.Now().Add(time.Second * 2))
    			fmt.Println("------>hello")
    			if err := ws.WriteMessage(websocket.TextMessage, []byte("hello")); err != nil {
    				fmt.Println("send msg err:", err)
    				return
    			}
    		case <-pingTicker.C:
    			ws.SetWriteDeadline(time.Now().Add(time.Second * 2))
    			fmt.Println("---->send ping")
    			if err := ws.WriteMessage(websocket.PingMessage, []byte{}); err != nil {
    				fmt.Println("send ping err:", err)
    				return
    			}
    		}
    	}
    }
    func reader(ws *websocket.Conn) {
    	ws.SetReadLimit(512)
    	ws.SetReadDeadline(time.Now().Add(time.Second * 2))
    	ws.SetPongHandler(func(string) error { ws.SetReadDeadline(time.Now().Add(time.Second * 2)); return nil })
    	defer ws.Close()
    	for {
    		t, msg, err := ws.ReadMessage()
    		fmt.Println("server recieve t:", t)
    		fmt.Println("server recieve body:", string(msg))
    		if err != nil {
    			if websocket.IsUnexpectedCloseError(err, websocket.CloseGoingAway) {
    				fmt.Println("error:", err)
    			}
    			return
    		}
    		time.Sleep(time.Second *5)
    	}
    }
    

    在 main 函数中,writer 函数必须加关键字 go,开启两个定时器,一个定时向客户端推送消息,一个定时 ping 客户端检测是否连接正常。如果断开显示send ping err: write tcp 127.0.0.1:3000->127.0.0.1:62228: write: broken pipe。但是感觉这个 ping 的定时器没啥用啊,如果客户端断开,WriteMessage 这个方法就会报错退出,那这个 ping 效果和它一样的,请问还有必要吗?

    go writer(conn)
    go reader(conn)
    
    20 条回复    2017-12-13 13:42:09 +08:00
    kyuuseiryuu
        1
    kyuuseiryuu  
       2017-12-12 17:08:00 +08:00
    老哥, 你把 WebSocket 玩出了 HTTP 的感觉.

    WebSocket 本来就是长连接, 干嘛要发心跳包?
    如果连接断开都会触发客户端和服务端的 onClose 事件, 你处理好这个事件不就行了吗?
    wtbhk
        2
    wtbhk  
       2017-12-12 17:16:24 +08:00
    @kyuuseiryuu 心跳还是要发的,说不定中间链路断了
    morethansean
        3
    morethansean  
       2017-12-12 17:18:38 +08:00
    @kyuuseiryuu 建议补一下网络知识 23333
    suikator
        4
    suikator  
       2017-12-12 17:19:11 +08:00 via Android
    @kyuuseiryuu 为了维持 NAT 还是需要的吧
    lianyue
        5
    lianyue  
       2017-12-12 17:37:52 +08:00
    WebSocket 自带心跳包啊
    mentalidade
        6
    mentalidade  
    OP
       2017-12-12 17:42:55 +08:00
    @lianyue 求教,我找了好久,这是抄的官网 demo 的
    que01
        7
    que01  
       2017-12-12 17:47:30 +08:00
    最近在学 go 这段代码我居然能看懂了 哈哈
    mentalidade
        8
    mentalidade  
    OP
       2017-12-12 17:48:38 +08:00
    @que01 😅,因为我也是新学 go
    bhagavad
        9
    bhagavad  
       2017-12-12 17:50:38 +08:00   ❤️ 2
    @suikator 说的正解,一方面为了 NAT 需要,另一方面是业务需求。

    在中间网络断掉后( client 网络断了、链路上的某个路由器断了) TCP 链接两端是不会收到任何通知的,所以这个时候是根本不会触发 @kyuuseiryuu 所说的 onClose 事件的。这个时候就需要依靠心跳去监听,如果发现对方掉线后就触发类似 onClose 的事件(不管是 client 还是 server ),所以业务的心跳是必须的(当然也可以通过自定义 tcp 的 keepalive 来实现)。
    kaneg
        10
    kaneg  
       2017-12-12 18:39:18 +08:00 via iPhone   ❤️ 1
    1. 维持链路连接,同楼上各位的观点
    2. 及时感知连接断开。如果对端端由某种原因断开连接而没有被网络层感知到,心跳包能及时感知到这种情况,从而来后续清理或者重连。
    kyuuseiryuu
        11
    kyuuseiryuu  
       2017-12-12 18:43:02 +08:00
    @morethansean #3 求教.
    Kilerd
        12
    Kilerd  
       2017-12-12 19:16:16 +08:00
    1、ping pong 是必须的,如果你传输的消息量少,而且想保持长连接
    2、websocket 不自带心跳, 你可能是 socket.io 的受害者
    jswh
        13
    jswh  
       2017-12-12 19:21:35 +08:00
    如果是我写,我就让客户端来 ping server,一般心跳维持都是客户端报告我还活着吧。这样你就记录一个上次 ping 时间,超时断开就好了。
    mentalidade
        14
    mentalidade  
    OP
       2017-12-12 20:13:37 +08:00
    @jswh 那和我的疑惑差不多,因为我是服务端定时推送,如果客户端断开,那么消息直接推送失败而断开。不过官网给的几个例子是使用 ping 的。自己也有 deadline, https://github.com/gorilla/websocket/issues/52
    pathbox
        15
    pathbox  
       2017-12-12 20:58:37 +08:00 via iPhone
    go 原包不带心跳,gorilla 的 websocket 有心跳方法。你自己实现一个也是可以的。 一般建议 client 发心跳 别让服务端发。
    TangMonk
        16
    TangMonk  
       2017-12-12 21:14:58 +08:00 via Android
    @Kilerd websocket 的 pingpong 就是用来实现 heartbeat 的
    Kilerd
        17
    Kilerd  
       2017-12-12 21:31:43 +08:00
    @jswh 客户端发 ping 不是通用做法吗?

    client: hey, 我还活着,别挂电话。
    server: 嗯,好的
    client: hey,我还活着
    server: 好的
    client: hey....
    server:噢
    client: emmmmm... 是我
    server: emmmmmmmmmmm
    client: 还是我
    server: ..................


    .........

    server: 那货终于走了。 (啪,挂电话(on_close))
    gamexg
        18
    gamexg  
       2017-12-12 23:25:11 +08:00 via Android
    @Kilerd 我上次为了收集客户网络延迟做成服务端主动发包来统计 pong 回应耗时,不过的确是客户端主动发 ping 简单。
    jswh
        19
    jswh  
       2017-12-13 10:41:49 +08:00
    @Kilerd 我的意思就是客户端发 ping 呀。楼主的代码是服务端在 ping
    mrnull
        20
    mrnull  
       2017-12-13 13:42:09 +08:00
    2 ping, 3 pong, 4 msg,看看各种 engine.io 的实现
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   3508 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 28ms · UTC 11:11 · PVG 19:11 · LAX 04:11 · JFK 07:11
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.