Example #1
0
//加载数据
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)
}
Example #2
0
//持久化任务
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)
		}
	}
}
Example #3
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, 当玩家再次登陆时, 原先的连接何时删除

}