package nnet import ( "errors" "git.noahlan.cn/northlan/nnet/log" "git.noahlan.cn/northlan/nnet/nface" "git.noahlan.cn/northlan/nnet/packet" "git.noahlan.cn/northlan/nnet/pipeline" "git.noahlan.cn/northlan/nnet/session" "github.com/gorilla/websocket" "net" "sync/atomic" "time" ) var ( ErrCloseClosedSession = errors.New("close closed session") ) type ( Connection struct { session nface.ISession // Session server *Server // Server 引用 conn net.Conn // low-level conn fd status int32 // 连接状态 lastMid uint64 // 最近一次消息ID lastHeartbeatAt int64 // 最近一次心跳时间 chDie chan struct{} // 停止通道 chSend chan []byte // 消息发送通道 pipeline pipeline.Pipeline // 消息管道 } pendingMessage struct { typ byte // message type route string // message route mid uint64 // response message id payload interface{} // payload } ) func newConnection(server *Server, conn net.Conn, pipeline pipeline.Pipeline) nface.IConnection { r := &Connection{ conn: conn, server: server, status: nface.StatusStart, lastHeartbeatAt: time.Now().Unix(), chDie: make(chan struct{}), chSend: make(chan pendingMessage, 2048), pipeline: pipeline, } // binding session r.session = session.New() return r } func newConnectionWS(server *Server, conn *websocket.Conn, pipeline pipeline.Pipeline) nface.IConnection { c, err := newWSConn(conn) if err != nil { // TODO panic ? panic(err) } return newConnection(server, c, pipeline) } func (r *Connection) Status() int32 { return atomic.LoadInt32(&r.status) } func (r *Connection) SetStatus(s int32) { atomic.StoreInt32(&r.status, s) } func (r *Connection) Conn() net.Conn { return r.conn } func (r *Connection) ID() int64 { return r.session.ID() } func (r *Connection) Session() nface.ISession { return r.session } func (r *Connection) write() { ticker := time.NewTicker(r.server.HeartbeatInterval) chWrite := make(chan []byte, 1024) defer func() { ticker.Stop() close(r.chSend) close(chWrite) _ = r.Close() log.Debugf("Session write goroutine exit, SessionID=%d, UID=%d", r.session.ID(), r.session.UID()) }() for { select { case <-ticker.C: deadline := time.Now().Add(-2 * r.server.HeartbeatInterval).Unix() if atomic.LoadInt64(&r.lastHeartbeatAt) < deadline { log.Debugf("Session heartbeat timeout, LastTime=%d, Deadline=%d", atomic.LoadInt64(&r.lastHeartbeatAt), deadline) return } // TODO heartbeat data chWrite <- []byte{} case data := <-r.chSend: // message marshal data payload, err := r.server.Serializer.Marshal(data.payload) if err != nil { switch data.typ { } break } // TODO new message and pipeline // TODO encode message ? message processor ? // packet pack data p, err := r.server.Packer.Pack(packet.Data, payload) if err != nil { log.Error(err.Error()) break } chWrite <- p case data := <-chWrite: // 回写数据 if _, err := r.conn.Write(data); err != nil { log.Error(err.Error()) return } case <-r.chDie: // connection close signal return // TODO application quit signal } } } func (r *Connection) Close() error { if r.Status() == StatusClosed { return ErrCloseClosedSession } r.SetStatus(StatusClosed) log.Debugf("close connection, ID: %d", r.ID()) select { case <-r.chDie: default: close(r.chDie) // TODO lifetime } return r.conn.Close() }