// 玩家登陆过程 func P_user_login_req(sess *Session, reader *packet.Packet) []byte { // TODO: 登录鉴权 sess.UserId = 1 // TODO: 选择登录服务器 sess.GSID = DEFAULT_GSID // 选服 cli, err := sp.GetServiceWithId(sp.SERVICE_GAME, sess.GSID) if err != nil { log.Critical(err) return nil } // type assertion service, ok := cli.(spp.GameServiceClient) if !ok { log.Critical("canot do type assertion on: %v", sess.GSID) return nil } //开启到游戏服的流 // TODO: 处理context, 设置超时 stream, err := service.Stream(context.Background()) if err != nil { log.Critical(err) return nil } sess.Stream = stream //在game注册 // TODO: 新用户的创建由game处理 sess.Stream.Send(&spp.Game_Frame{Type: spp.Game_Register, UserId: sess.UserId}) // 读取GAME返回的消息 fetcher_task := func(sess *Session) { for { in, err := sess.Stream.Recv() if err == io.EOF { //流关闭 log.Trace(err) return } if err != nil { log.Error(err) return } sess.MQ <- *in } } go fetcher_task(sess) return packet.Pack(Code["user_login_ack"], user_snapshot{F_uid: sess.UserId}, nil) }
// 玩家一分钟定时器 func timer_work(sess *Session, out *Buffer) { // 发包频率控制, 太高的RPS直接踢掉 // 计算玩家已连上多长时间 interval := time.Now().Sub(sess.ConnectTime).Minutes() if interval >= 1 { //登陆时长超过1分钟才开始统计rpm。防脉冲 //计算rpm rpm := float64(sess.PacketCount) / interval if rpm > RPM_LIMIT { sess.Flag |= SESS_KICKED_OUT log.Error("玩家RPM太高 RPM:", rpm) return } } }
func PrintPanicStack(extras ...interface{}) { if x := recover(); x != nil { log.Error(x) i := 0 funcName, file, line, ok := runtime.Caller(i) for ok { log.Errorf("frame %v:[func:%v,file:%v,line:%v]\n", i, runtime.FuncForPC(funcName).Name(), file, line) i++ funcName, file, line, ok = runtime.Caller(i) } for k := range extras { log.Errorf("EXRAS#%v DATA:%v\n", k, spew.Sdump(extras[k])) } } }
// forward message to game server // 传送消息到游戏服务器 func forward(sess *Session, p []byte) error { frame := &Game_Frame{ Type: Game_Message, Message: p, } // 检查传输流是否打开 if sess.Stream == nil { return ERROR_STREAM_NOT_OPEN } //向game服务器传递数据 if err := sess.Stream.Send(frame); err != nil { log.Error(err) return err } return nil }
// client protocol handle proxy func proxy_user_request(sess *Session, p []byte) []byte { start := time.Now() defer utils.PrintPanicStack() //解密 if sess.Flag&SESS_ENCRYPT != 0 { //使用2组密钥匙, 增加破解难度 sess.Decoder.XORKeyStream(p, p) } //封装成reader reader := packet.Reader(p) // 读客户端数据包序列号(1,2,3...) // 可避免重放攻击-REPLAY-ATTACK //数据包的前4个字节存放的是客户端的发包数量 //客户端每次发包要包含第几次发包信息 seq_id, err := reader.ReadU32() if err != nil { log.Error("read client timestamp failed:", err) sess.Flag |= SESS_KICKED_OUT return nil } // 数据包个数验证 if seq_id != sess.PacketCount { //数据包真实长度是,总长度-4个字节的包个数长度-2个字节的协议号长度 log.Errorf("illegal packet sequeue id:%v should be:%v size:%v", seq_id, sess.PacketCount, len(p)-6) sess.Flag |= SESS_KICKED_OUT return nil } // 读协议号 b, err := reader.ReadS16() if err != nil { log.Error("read protocol number failed.") sess.Flag |= SESS_KICKED_OUT return nil } //根据协议号段 做服务划分 var ret []byte if b > MAX_PROTO_NUM { //去除4字节发包个数,将其余的向前传递 if err := forward(sess, p[4:]); err != nil { log.Errorf("service id:%v execute failed, error:%v", b, err) sess.Flag |= SESS_KICKED_OUT return nil } } else { if h := client_handler.Handlers[b]; h != nil { ret = h(sess, reader) } else { log.Errorf("service id:%v not bind", b) sess.Flag |= SESS_KICKED_OUT return nil } } // 统计处理时间 elasped := time.Now().Sub(start) if b != 0 { //排除心跳包日志 log.Trace("[REQ]", client_handler.RCode[b]) _statter.Timing(1.0, fmt.Sprintf("%v%v", STATSD_PREFIX, client_handler.RCode[b]), elasped) } return ret }
// start a goroutine when a new connection is accepted func handleClient(conn *net.TCPConn) { defer utils.PrintPanicStack() // set per-connection socket buffer //设置conn连接接受缓存的大小, 当超过缓存时, 会进入阻塞状态,等待被读取 conn.SetReadBuffer(SO_RCVBUF) // set initial socket buffer //设置conn连接发送缓存的大小, 当超过缓存时, 会进入阻塞状态,等待被发送成功 conn.SetWriteBuffer(SO_SNDBUF) // initial network control struct // 初始化2个字节数组, 用于存储header长度, 既后面要读取的文件长度 header := make([]byte, 2) // 输入流通道, 解析后的数据将放入,等待被处理 in := make(chan []byte) //设置延迟函数,当玩家断开连接时, 函数退出之前,关闭输入流 defer func() { close(in) // session will close }() // create a new session object for the connection //创建session对象, 用于封装客户端和服务器的信息交换 var sess Session host, port, err := net.SplitHostPort(conn.RemoteAddr().String()) if err != nil { log.Error("cannot get remote address:", err) return } //存储用户ip sess.IP = net.ParseIP(host) //打印用户的ip和端口, 用户可能会双开? log.Infof("new connection from:%v port:%v", host, port) // session die signal sess_die := make(chan bool) //SESSION_DIE 监控有问题................. // create a write buffer // 创建写入buffer对象 out := new_buffer(conn, sess_die) go out.start() // start one agent for handling packet //记录goroutine个数,让系统接收到关闭命令后,会阻塞主线程,至少所有agent线程退出,已保证数据落地 wg.Add(1) go agent(&sess, in, out, sess_die) //network loop for { // solve dead line problem // 设置读超时时间, 如果在任意一次执行Read syscall 返回的时候,超过这个时间点, 则算超时 conn.SetReadDeadline(time.Now().Add(TCP_READ_DEADLINE * time.Second)) //先读取2个字节头文件长度 n, err := io.ReadFull(conn, header) if err != nil { log.Warningf("read header failed, ip:%v reason:%v size:%v", sess.IP, err, n) return } //将2个字节数组转成int16类型, 不丢失精度 size := binary.BigEndian.Uint16(header) // alloc a byte slice for reading // 创建一个指定长度的切片,用于存放具体内容 payload := make([]byte, size) //read msg n, err = io.ReadFull(conn, payload) if err != nil { log.Warningf("read payload failed, ip:%v reason:%v size:%v", sess.IP, err, n) return } select { //接收的数据,转入in通道 case in <- payload: //payload queued //监听sess_die 通道 case <-sess_die: log.Warning("connection closed by logic, flag:%v ip:%v", sess.Flag, sess.IP) return } } // 好像没有处理连接超时, 如果玩家连上游戏后,一直未操作,再次链接时,会是新的连接?难道客户端在许久没有操作的情况下,先发一次ping, 如果有响应,继续操作,如果没响应,则执行重连? // 如果玩家已经退出了游戏,但是通过非正常途径退出的,这时,服务器还保留着该session, 当玩家再次登陆时, 原先的连接何时删除 }
func checkErr(err error) { if err != nil { log.Error(err) panic("error occured in protocol module") } }