You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
188 lines
4.1 KiB
Go
188 lines
4.1 KiB
Go
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"
|
|
"net"
|
|
"sync/atomic"
|
|
"time"
|
|
)
|
|
|
|
var (
|
|
_ nface.IConnection = (*Connection)(nil)
|
|
|
|
ErrCloseClosedSession = errors.New("close closed session")
|
|
// ErrBrokenPipe represents the low-level connection has broken.
|
|
ErrBrokenPipe = errors.New("broken low-level pipe")
|
|
// ErrBufferExceed indicates that the current session buffer is full and
|
|
// can not receive more data.
|
|
ErrBufferExceed = errors.New("session send buffer exceed")
|
|
)
|
|
|
|
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 pendingMessage // 消息发送通道
|
|
|
|
pipeline pipeline.Pipeline // 消息管道
|
|
}
|
|
|
|
pendingMessage struct {
|
|
typ interface{} // message type
|
|
route string // message route
|
|
mid uint64 // response message id
|
|
payload interface{} // payload
|
|
}
|
|
)
|
|
|
|
func newConnection(server *Server, conn net.Conn, pipeline pipeline.Pipeline) *Connection {
|
|
r := &Connection{
|
|
conn: conn,
|
|
server: server,
|
|
status: nface.StatusStart,
|
|
|
|
lastHeartbeatAt: time.Now().Unix(),
|
|
|
|
chDie: make(chan struct{}),
|
|
chSend: make(chan pendingMessage, 512),
|
|
|
|
pipeline: pipeline,
|
|
}
|
|
|
|
// binding session
|
|
r.session = session.New()
|
|
return r
|
|
}
|
|
|
|
func (r *Connection) send(m pendingMessage) (err error) {
|
|
defer func() {
|
|
if e := recover(); e != nil {
|
|
err = ErrBrokenPipe
|
|
}
|
|
}()
|
|
r.chSend <- m
|
|
return err
|
|
}
|
|
|
|
func (r *Connection) Server() nface.IServer {
|
|
return r.server
|
|
}
|
|
|
|
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) setLastHeartbeatAt(t int64) {
|
|
atomic.StoreInt64(&r.lastHeartbeatAt, t)
|
|
}
|
|
|
|
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("Connection write goroutine exit, ConnID=%d, SessionUID=%d", r.ID(), r.session.UID())
|
|
}()
|
|
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
// TODO heartbeat enable control
|
|
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:
|
|
// marshal packet body (data)
|
|
payload, err := r.server.Serializer.Marshal(data.payload)
|
|
if err != nil {
|
|
log.Errorf("message body marshal err: %v", err)
|
|
break
|
|
}
|
|
// TODO new message and pipeline
|
|
|
|
if pipe := r.pipeline; pipe != nil {
|
|
err := pipe.Outbound().Process(r)
|
|
if err != nil {
|
|
log.Errorf("broken pipeline err: %s", err.Error())
|
|
break
|
|
}
|
|
}
|
|
|
|
// 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
|
|
case <-r.server.DieChan: // application quit signal
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
func (r *Connection) Close() error {
|
|
if r.Status() == nface.StatusClosed {
|
|
return ErrCloseClosedSession
|
|
}
|
|
r.SetStatus(nface.StatusClosed)
|
|
|
|
log.Debugf("close connection, ID: %d", r.ID())
|
|
|
|
select {
|
|
case <-r.chDie:
|
|
default:
|
|
close(r.chDie)
|
|
// TODO lifetime
|
|
}
|
|
return r.conn.Close()
|
|
}
|