// pushproc merge proto and push msgs in batch. func (r *Room) pushproc(timer *itime.Timer, batch int, sigTime time.Duration) { var ( n int last time.Time p *proto.Proto td *itime.TimerData buf = bytes.NewWriterSize(int(proto.MaxBodySize)) ) log.Debug("start room: %d goroutine", r.id) td = timer.Add(sigTime, func() { select { case r.proto <- roomReadyProto: default: } }) for { if p = <-r.proto; p == nil { break // exit } else if p != roomReadyProto { // merge buffer ignore error, always nil p.WriteTo(buf) if n++; n == 1 { last = time.Now() timer.Set(td, sigTime) continue } else if n < batch { if sigTime > time.Now().Sub(last) { continue } } } else { if n == 0 { continue } } broadcastRoomBytes(r.id, buf.Buffer()) n = 0 // TODO use reset buffer // after push to room channel, renew a buffer, let old buffer gc buf = bytes.NewWriterSize(buf.Size()) } timer.Del(td) log.Debug("room: %d goroutine exit", r.id) }
// auth for goim handshake with client, use rsa & aes. func (server *Server) authTCP(rr *bufio.Reader, wr *bufio.Writer, p *proto.Proto) (key string, rid int32, heartbeat time.Duration, err error) { if err = p.ReadTCP(rr); err != nil { return } if p.Operation != define.OP_AUTH { log.Warn("auth operation not valid: %d", p.Operation) err = ErrOperation return } if key, rid, heartbeat, err = server.operator.Connect(p); err != nil { return } p.Body = nil p.Operation = define.OP_AUTH_REPLY if err = p.WriteTCP(wr); err != nil { return } err = wr.Flush() return }
// TODO linger close? func (server *Server) serveTCP(conn *net.TCPConn, rp, wp *bytes.Pool, tr *itime.Timer) { var ( err error key string hb time.Duration // heartbeat p *proto.Proto b *Bucket trd *itime.TimerData rb = rp.Get() wb = wp.Get() ch = NewChannel(server.Options.CliProto, server.Options.SvrProto, define.NoRoom) rr = &ch.Reader wr = &ch.Writer ) ch.Reader.ResetBuffer(conn, rb.Bytes()) ch.Writer.ResetBuffer(conn, wb.Bytes()) // handshake trd = tr.Add(server.Options.HandshakeTimeout, func() { conn.Close() }) // must not setadv, only used in auth if p, err = ch.CliProto.Set(); err == nil { if key, ch.RoomId, hb, err = server.authTCP(rr, wr, p); err == nil { b = server.Bucket(key) err = b.Put(key, ch, tr) } } if 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) // hanshake ok start dispatch goroutine go server.dispatchTCP(key, conn, wr, wp, wb, ch) for { if p, err = ch.CliProto.Set(); err != nil { break } if err = p.ReadTCP(rr); err != nil { break } //p.Time = *globalNowTime 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 } } ch.CliProto.SetAdv() ch.Signal() } if err != nil && err != io.EOF { log.Error("key: %s server tcp failed error(%v)", key, err) } b.Del(key) tr.Del(trd) rp.Put(rb) conn.Close() ch.Close() 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 }
// dispatch accepts connections on the listener and serves requests // for each incoming connection. dispatch blocks; the caller typically // invokes it in a go statement. func (server *Server) dispatchTCP(key string, conn *net.TCPConn, wr *bufio.Writer, wp *bytes.Pool, wb *bytes.Buffer, ch *Channel) { var ( p *proto.Proto err error ) if Debug { log.Debug("key: %s start dispatch tcp goroutine", key) } for { p = ch.Ready() if Debug { log.Debug("key:%s dispatch msg:%v", key, *p) } switch p { case proto.ProtoFinish: if Debug { log.Debug("key: %s wakeup exit dispatch goroutine", key) } goto failed case proto.ProtoReady: // fetch message from svrbox(client send) for { if p, err = ch.CliProto.Get(); err != nil { err = nil // must be empty error break } //LogSlow(SlowLogTypeReceive, key, p) if err = p.WriteTCP(wr); err != nil { goto failed } p.Body = nil // avoid memory leak ch.CliProto.GetAdv() } default: // server send //LogSlow(SlowLogTypeReceive, key, p) if err = p.WriteTCP(wr); err != nil { goto failed } } // only hungry flush response if err = wr.Flush(); err != nil { break } } failed: if err != nil { log.Error("key: %s dispatch tcp error(%v)", key, err) } conn.Close() wp.Put(wb) // must ensure all channel message discard, for reader won't blocking Signal for { if p == proto.ProtoFinish { break } p = ch.Ready() } if Debug { log.Debug("key: %s dispatch goroutine exit", key) } return }