示例#1
0
文件: handle.go 项目: pengswift/agent
// 玩家登陆过程
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)
}
示例#2
0
// 玩家一分钟定时器
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
		}
	}
}
示例#3
0
文件: stack.go 项目: pengswift/agent
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]))
		}
	}
}
示例#4
0
// 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
}
示例#5
0
文件: proxy.go 项目: pengswift/agent
// 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
}
示例#6
0
文件: main.go 项目: pengswift/agent
// 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, 当玩家再次登陆时, 原先的连接何时删除

}
示例#7
0
文件: handle.go 项目: pengswift/agent
func checkErr(err error) {
	if err != nil {
		log.Error(err)
		panic("error occured in protocol module")
	}
}