func (server *Server) serveHTTP(w http.ResponseWriter, r *http.Request, tr *itime.Timer) { var ( b *Bucket ok bool hb time.Duration // heartbeat key string cb string err error trd *itime.TimerData conn net.Conn rwr *bufio.ReadWriter hj http.Hijacker // no client send ch = NewChannel(1, define.NoRoom) ) if key, cb, hb, err = server.authHTTP(r, ch); err != nil { http.Error(w, "auth failed", http.StatusForbidden) return } if hj, ok = w.(http.Hijacker); !ok { log.Error("w.(http.Hijacker) type assection failed") http.Error(w, "not support", http.StatusInternalServerError) return } if conn, rwr, err = hj.Hijack(); err != nil { log.Error("hj.Hijack() error(%v)", err) http.Error(w, "not support", http.StatusInternalServerError) return } // register key->channel b = server.Bucket(key) b.Put(key, ch, tr) // hanshake ok start dispatch goroutine server.dispatchHTTP(rwr, cb, ch, hb) tr.Del(trd) conn.Close() b.Del(key) // don't use close chan, Signal can be reused // if chan full, writer goroutine next fetch from chan will exit // if chan empty, send a 0(close) let the writer exit if err = server.operator.Disconnect(key, ch.RoomId); err != nil { log.Error("%s operator do disconnect error(%v)", key, err) } if Debug { log.Debug("%s serverconn goroutine exit", key) } return }
// push merge proto and push msgs in batch. func (r *Room) push(timer *itime.Timer, batch int, sigTime time.Duration) { var ( done bool last time.Time least time.Duration ch *Channel td *itime.TimerData ) if Debug { log.Debug("start room: %d goroutine", r.id) } td = timer.Add(sigTime, func() { select { case r.signal <- ProtoReady: default: } }) for { if r.n > 0 { if least = sigTime - time.Now().Sub(last); least > 0 { timer.Set(td, least) } else { // timeout done = true } } else { last = time.Now() } if !done { if <-r.signal != ProtoReady { break } // try merge msg hard if r.n < batch { continue } } r.rLock.RLock() for ch, _ = range r.chs { ch.Pushs(r.vers[:r.n], r.ops[:r.n], r.msgs[:r.n]) } // r.n set here and Push, so RLock is exclusive with Lock r.n = 0 r.rLock.RUnlock() done = false ch = nil // avoid gc memory leak } timer.Del(td) if Debug { log.Debug("room: %d goroutine exit", r.id) } }
// TODO linger close? func (server *Server) serveTCP(conn *net.TCPConn, rp, wp *bytes.Pool, tr *itime.Timer) { var ( b *Bucket key string hb time.Duration // heartbeat err error trd *itime.TimerData rb = rp.Get() wb = wp.Get() ch = NewChannel(server.Options.Proto, define.NoRoom) rr = &ch.Reader wr = &ch.Writer p = &ch.CliProto ) ch.Reader.ResetBuffer(conn, rb.Bytes()) ch.Writer.ResetBuffer(conn, wb.Bytes()) // handshake trd = tr.Add(server.Options.HandshakeTimeout, func() { conn.Close() }) if key, ch.RoomId, hb, err = server.authTCP(rr, wr, p); err != nil { conn.Close() rp.Put(rb) wp.Put(wb) tr.Del(trd) log.Error("key: %s handshake failed error(%v)", key, err) return } trd.Key = key tr.Set(trd, hb) b = server.Bucket(key) b.Put(key, ch, tr) // hanshake ok start dispatch goroutine go server.dispatchTCP(key, conn, wr, wp, wb, ch) for { if err = server.readTCPRequest(rr, p); err != nil { break } if p.Operation == define.OP_HEARTBEAT { tr.Set(trd, hb) p.Body = nil p.Operation = define.OP_HEARTBEAT_REPLY if Debug { log.Debug("key: %s receive heartbeat", key) } } else { if err = server.operator.Operate(p); err != nil { break } } if err = ch.Reply(); err != nil { break } } log.Error("key: %s server tcp failed error(%v)", key, err) conn.Close() ch.Close() rp.Put(rb) b.Del(key) tr.Del(trd) if err = server.operator.Disconnect(key, ch.RoomId); err != nil { log.Error("key: %s operator do disconnect error(%v)", key, err) } if Debug { log.Debug("key: %s server tcp goroutine exit", key) } return }
func (server *Server) serveWebsocket(conn *websocket.Conn, tr *itime.Timer) { var ( b *Bucket hb time.Duration // heartbeat key string err error trd *itime.TimerData ch = NewChannel(server.Options.Proto, define.NoRoom) p = &ch.CliProto ) // handshake trd = tr.Add(server.Options.HandshakeTimeout, func() { conn.Close() }) if key, ch.RoomId, hb, err = server.authWebsocket(conn, p); err == nil { trd.Key = key tr.Set(trd, hb) } if err != nil { tr.Del(trd) conn.Close() log.Error("handshake failed error(%v)", err) return } // register key->channel b = server.Bucket(key) b.Put(key, ch, tr) // hanshake ok start dispatch goroutine go server.dispatchWebsocket(key, conn, ch) for { // parse request protocol if err = server.readWebsocketRequest(conn, p); err != nil { break } if p.Operation == define.OP_HEARTBEAT { // heartbeat tr.Set(trd, hb) p.Body = nil p.Operation = define.OP_HEARTBEAT_REPLY } else { // process message if err = server.operator.Operate(p); err != nil { break } } if err = ch.Reply(); err != nil { break } } log.Error("key: %s server websocket failed error(%v)", key, err) conn.Close() ch.Close() b.Del(key) if err = server.operator.Disconnect(key, ch.RoomId); err != nil { log.Error("key: %s operator do disconnect error(%v)", key, err) } if Debug { log.Debug("key: %s server websocket goroutine exit", key) } return }