//加载数据 func (s *server) restore() { db := s.open_db() defer db.Close() count := 0 //查询 db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(BOLTDB_BUCKET)) b.ForEach(func(k, v []byte) error { //查询每个用户对应消息记录集合,将其反序列化成ChatMessage[]对象 var msg []Chat_Message err := msgpack.Unmarshal(v, &msg) if err != nil { log.Critical("chat data corrupted:", err) os.Exit(-1) } //id转成uint64 id, err := strconv.ParseUint(string(k), 0, 64) if err != nil { log.Critical("chat data corrupted:", err) os.Exit(-1) } //用NewEndPoint包装msg ep := NewEndPoint() ep.inbox = msg //用过eps包装ep s.eps[id] = ep count++ return nil }) return nil }) log.Infof("restored %v chats", count) }
//持久化任务 func (s *server) persistence_task() { //一分钟执行一次 timer := time.After(CHECK_INTERVAL) db := s.open_db() //定义改变的id集合 changes := make(map[uint64]bool) //信号监听 sig := make(chan os.Signal, 1) signal.Notify(sig, syscall.SIGTERM, syscall.SIGINT) for { select { //s.pending传入变更的id case key := <-s.pending: changes[key] = true case <-timer: //一分钟后写入 s.dump(db, changes) log.Infof("perisisted %v endpoints:", len(changes)) //清空 changes = make(map[uint64]bool) //重新计时 timer = time.After(CHECK_INTERVAL) //当接受关闭信号时 case nr := <-sig: //写入数据 s.dump(db, changes) //关闭 db.Close() log.Info(nr) os.Exit(0) } } }
// 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, 当玩家再次登陆时, 原先的连接何时删除 }