// 启动服务器 // wg:WaitGroup // serverAddress:服务器监听地址 // getPlayerFunc:获取玩家对象的方法 func StartServer(wg *sync.WaitGroup, serverAddress string, getPlayerFunc func(*Client, string) (*player.Player, ResultStatus)) { defer func() { wg.Done() }() // 设置配置参数 SetRPCParam(serverAddress, getPlayerFunc) logUtil.Log("Socket服务器开始监听...", logUtil.Info, true) // 监听指定的端口 listener, err := net.Listen("tcp", ServerAddress()) if err != nil { panic(fmt.Errorf("Listen Error: %s", err)) } else { msg := fmt.Sprintf("Got listener for client. (local address: %s)", listener.Addr()) // 记录和显示日志,并且判断是否需要退出 logUtil.Log(msg, logUtil.Info, true) fmt.Println(msg) } for { // 阻塞直至新连接到来 conn, err := listener.Accept() if err != nil { logUtil.Log(fmt.Sprintf("Accept Error: %s", err), logUtil.Error, true) continue } // 启动一个新协程来处理链接(每个客户端对应一个协程) go handleConn(conn) } }
// 启动服务器 // wg:WaitGroup func StartServer(wg *sync.WaitGroup) { defer func() { wg.Done() }() logUtil.Log("Socket服务器开始监听...", logUtil.Info, true) // 监听指定的端口 msg := "" listener, err := net.Listen("tcp", ServerAddress()) if err != nil { msg = fmt.Sprintf("Listen Error: %s", err) } else { msg = fmt.Sprintf("Got listener for the server. (local address: %s)", listener.Addr()) } // 记录和显示日志,并且判断是否需要退出 logUtil.Log(msg, logUtil.Info, true) fmt.Println(msg) if err != nil { return } for { // 阻塞直至新连接到来 conn, err := listener.Accept() if err != nil { logUtil.Log(fmt.Sprintf("Accept Error: %s", err), logUtil.Error, true) continue } // 启动一个新协程来处理链接 go handleConn(conn) } }
// 将通道里面的所有内容写入到文件中 func SaveToFile() { logUtil.Log(fmt.Sprintf("通道中尚未被处理的数据量为:%d", len(dataChannel)), logUtil.Debug, true) // 组装数据到dataSlice中,并保存到日志文件 dataSlice := make([]string, 0, 1024) logUtil.Log("开始取通道中的数据", logUtil.Debug, true) if len(dataChannel) > 0 { Outer: for { select { case data := <-dataChannel: dataSlice = append(dataSlice, data) dataSlice = append(dataSlice, stringUtil.GetNewLineString()) default: break Outer } } } logUtil.Log("取通道中的数据结束", logUtil.Debug, true) logUtil.Log("开始保存数据到文件", logUtil.Debug, true) if len(dataSlice) > 0 { fileUtil.WriteFile(config.SAVE_DATA_PATH, fmt.Sprintf("%s.sql", timeUtil.Format(time.Now(), "yyyy-MM-dd")), true, dataSlice...) } logUtil.Log("保存数据到文件结束", logUtil.Debug, true) }
// 启动客户端 func startClient(ch chan int, loginSucceedCh chan int, currId int) { // 处理内部未处理的异常,以免导致主线程退出,从而导致系统崩溃 defer func() { if r := recover(); r != nil { logUtil.Log(fmt.Sprintf("通过recover捕捉到的未处理异常:%v", r), logUtil.Error, true) } }() conn, err := net.DialTimeout("tcp", ServerAddress, 5*time.Second) if err != nil { fmt.Printf("第%d个客户端启动失败,请检查\n", currId) ch <- currId return } defer func() { conn.Close() ch <- currId }() // 创建客户端对象,并添加到列表中 clientObj := client.NewClient(&conn, conn) ClientList[clientObj.Id] = false // 登陆 go login(clientObj, currId) // 发送心跳包 go heartBeat(clientObj, loginSucceedCh) // 死循环,不断地读取数据,解析数据,发送数据 for { // 先读取数据,每次读取1024个字节 readBytes := make([]byte, 1024) n, err := conn.Read(readBytes) if err != nil { var errMsg string // 判断是连接关闭错误,还是普通错误 if err == io.EOF { errMsg = fmt.Sprintf("另一端关闭了连接:%s", err) } else { errMsg = fmt.Sprintf("读取数据错误:%s", err) } logUtil.Log(errMsg, logUtil.Error, true) break } // 将读取到的数据追加到已获得的数据的末尾 clientObj.AppendContent(readBytes[:n]) // 已经包含有效的数据,处理该数据 handleClient(clientObj, loginSucceedCh) } }
// 启动客户端 func startClient(ch chan int, clientCh chan *client.Client, loginSucceedCh chan int) { // 处理内部未处理的异常,以免导致主线程退出,从而导致系统崩溃 defer func() { if r := recover(); r != nil { logUtil.Log(fmt.Sprintf("通过recover捕捉到的未处理异常:%v", r), logUtil.Error, true) } }() conn, err := net.DialTimeout(SERVER_NETWORK, ServerAddress, 2*time.Second) if err != nil { fmt.Printf("Dial Error: %s\n", err) ch <- 0 return } defer func() { conn.Close() ch <- 1 }() // 创建客户端对象 clientObj := client.NewClient(&conn, conn) // 打印日志 fmt.Printf("Connected to server. (remote address: %s, local address: %s)\n", conn.RemoteAddr(), conn.LocalAddr()) // 写入1表示启动成功,则main线程可以继续往下进行 ch <- 1 clientCh <- clientObj // 死循环,不断地读取数据,解析数据,发送数据 for { // 先读取数据,每次读取1024个字节 readBytes := make([]byte, 1024) n, err := conn.Read(readBytes) if err != nil { var errMsg string // 判断是连接关闭错误,还是普通错误 if err == io.EOF { errMsg = fmt.Sprintf("另一端关闭了连接:%s", err) } else { errMsg = fmt.Sprintf("读取数据错误:%s", err) } logUtil.Log(errMsg, logUtil.Error, true) break } // 将读取到的数据追加到已获得的数据的末尾 clientObj.AppendContent(readBytes[:n]) // 已经包含有效的数据,处理该数据 handleClient(clientObj, loginSucceedCh) } }
// 保存历史消息 func SaveHistoryMessage() { logUtil.Log("开始保存历史消息...", logUtil.Info, true) // 保存世界历史消息 saveWorldHistoryMessage() // 保存公会历史消息 saveUnionHistoryMessage() logUtil.Log("保存历史消息完成...", logUtil.Info, true) }
func writeRequestLog(apiName string, r *http.Request) error { log, err := json.Marshal(r.Form) if err != nil { logUtil.Log(fmt.Sprintf("序列化数据错误,原始数据位:%v,错误信息为:%s", r.Form, err), logUtil.Error, true) return err } logInfo := fmt.Sprintf("Web服务器收到请求。ApiName:%s, Param:%s", apiName, string(log)) logUtil.Log(logInfo, logUtil.Debug, true) return nil }
// 保存世界历史消息 func saveWorldHistoryMessage() { logUtil.Log("开始保存世界历史消息...", logUtil.Info, true) worldHistoryMessageMutex.RLock() defer worldHistoryMessageMutex.RUnlock() if bytes, err := json.Marshal(worldHistoryMessageList); err == nil { fileUtil.WriteFile(con_HistoryPath, getWorldHistoryMessageFileName(), false, string(bytes)) } logUtil.Log("保存世界历史消息完成...", logUtil.Info, true) }
// 停止服务器 func StopServer() { logUtil.Log("1、开始停止服务器", logUtil.Debug, true) // 断开客户端连接 logUtil.Log("2、开始断掉所有客户端连接", logUtil.Debug, true) for _, value := range ClientList() { value.conn.Close() } logUtil.Log("3、断掉所有客户端连接结束", logUtil.Debug, true) // 保存数据 logUtil.Log("4、开始保存未处理的数据", logUtil.Debug, true) data.SaveToFile() logUtil.Log("5、保存未处理的数据结束", logUtil.Debug, true) }
// 保存公会历史消息 func saveUnionHistoryMessage() { logUtil.Log("开始保存公会历史消息...", logUtil.Info, true) unionHistoryMessageMutex.RLock() defer unionHistoryMessageMutex.RUnlock() // 遍历所有的历史消息,每个公会保存为一个文件 for unionId, messageList := range unionHistoryMessageList { if bytes, err := json.Marshal(messageList); err == nil { fileUtil.WriteFile(con_HistoryPath, getUnionHistoryMessageFileName(unionId), false, string(bytes)) } } logUtil.Log("保存公会历史消息完成...", logUtil.Info, true) }
func handleClientContent(clientObj *Client) { validMessageList := make([]string, 0) for { content, ok := clientObj.GetValieMessage() if !ok { break } // 处理数据,如果长度为0则表示心跳包 if len(content) == 0 { continue } else { atomic.AddInt64(&totalSize, int64(len(content))) // 先进行解压缩 content, err := zlibUtil.Decompress(content) if err != nil { logUtil.Log(fmt.Sprintf("zlib解压缩错误,错误信息为:%s", err), logUtil.Error, true) break } validMessageList = append(validMessageList, string(content)) } } // 添加数据 if len(validMessageList) > 0 { data.AddData(validMessageList) } }
func GetPlayer(id string) (playerObj *player.Player, exists bool, err error) { command := "SELECT Name, UnionId, ExtraMsg, RegisterTime, LoginTime, IsForbidden, SilentEndTime FROM player WHERE Id = ?;" var name string var unionId string var extraMsg string var registerTime time.Time var loginTime time.Time var isForbidden bool var silentEndTime time.Time if err = dal.ChatDB().QueryRow(command, id).Scan(&name, &unionId, &extraMsg, ®isterTime, &loginTime, &isForbidden, &silentEndTime); err != nil { if err == sql.ErrNoRows { // 重置err,使其为nil;因为这代表的是没有查找到数据,而不是真正的错误 err = nil return } else { logUtil.Log(fmt.Sprintf("Scan失败,错误信息:%s,command:%s", err, command), logUtil.Error, true) return } } playerObj = player.NewPlayer(id, name, unionId, extraMsg, registerTime, loginTime, isForbidden, silentEndTime) exists = true return }
func GetGamePlayer(id string) (name string, unionId string, exists bool, err error) { command := "SELECT p.Name, g.GuildId FROM p_player p LEFT JOIN p_guild_info g ON p.Id = g.PlayerId WHERE p.Id = ?;" var guildId interface{} if err = dal.GameDB().QueryRow(command, id).Scan(&name, &guildId); err != nil { if err == sql.ErrNoRows { // 重置err,使其为nil;因为这代表的是没有查找到数据,而不是真正的错误 err = nil return } else { logUtil.Log(fmt.Sprintf("Scan失败,错误信息:%s,command:%s", err, command), logUtil.Error, true) return } } exists = true // 处理公会Id if guildId != nil { if unionIdArr, ok := guildId.([]byte); ok { unionId = string(unionIdArr) } } return }
func clearExpiredClient() { // 处理内部未处理的异常,以免导致主线程退出,从而导致系统崩溃 defer func() { if r := recover(); r != nil { logUtil.LogUnknownError(r) } }() for { // 休眠指定的时间(单位:秒)(放在此处是因为程序刚启动时并没有过期的客户端,所以先不用占用资源;) time.Sleep(CheckExpiredInterval() * time.Second) // 清理之前的客户端数量和玩家数量 beforeClientCount := len(clientList) beforePlayerCount := len(playerList) // 获取本次清理的客户端数量 expiredClientCount := 0 // 开始清理 for _, item := range clientList { if item.HasExpired() { expiredClientCount++ item.Quit() } } // 记录日志 if expiredClientCount > 0 { logUtil.Log(fmt.Sprintf("清理前的客户端数量为:%d, 清理前的玩家数量为:%d, 本次清理不活跃的数量为:%d", beforeClientCount, beforePlayerCount, expiredClientCount), logUtil.Debug, true) } } }
func init() { // 设置日志文件的存储目录 logUtil.SetLogPath(filepath.Join(fileUtil.GetCurrentPath(), LOG_PATH_SUFFIX)) // 记录启动成功日志 logUtil.Log("客户端启动成功", logUtil.Info, true) }
func request(clientObj *client.Client, requestMap map[string]interface{}) { b, err := json.Marshal(requestMap) if err != nil { logUtil.Log(fmt.Sprintf("序列化请求数据%v出错", requestMap), logUtil.Error, true) } else { clientObj.SendByteMessage(b) } }
// 提交事务 // tx:事务对象 // 返回值: // 错误对象 func CommitTransaction(tx *sql.Tx) error { err := tx.Commit() if err != nil { logUtil.Log(fmt.Sprintf("提交事务失败,错误信息:%s", err), logUtil.Error, true) } return err }
// 开始事务 // 返回值: // 事务对象 // 错误对象 func BeginTransaction() (*sql.Tx, error) { tx, err := GameDB.Begin() if err != nil { logUtil.Log(fmt.Sprintf("开启事务失败,错误信息:%s", err), logUtil.Error, true) } return tx, err }
// 发送响应结果 // clientObj:客户端对象 // responseObject:响应对象 func responseResult(clientObj *client.Client, responseObj *responseDataObject.SocketResponseObject) { b, err := json.Marshal(responseObj) if err != nil { logUtil.Log(fmt.Sprintf("序列化输出结果%v出错", responseObj), logUtil.Error, true) } else { clientObj.SendByteMessage(b) } }
// 处理系统信号 func signalProc() { sigs := make(chan os.Signal) signal.Notify(sigs, syscall.SIGTERM, syscall.SIGINT) for { // 准备接收信息 <-sigs logUtil.Log("收到退出程序的命令,开始退出……", logUtil.Info, true) // 做一些收尾的工作 logUtil.Log("收到退出程序的命令,退出完成……", logUtil.Info, true) // 一旦收到信号,则表明管理员希望退出程序,则先保存信息,然后退出 os.Exit(0) } }
func responseResult(w http.ResponseWriter, responseObj *ResponseObject) { b, err := json.Marshal(responseObj) if err != nil { logUtil.Log(fmt.Sprintf("序列化输出结果%v出错", responseObj), logUtil.Error, true) return } fmt.Fprintln(w, string(b)) }
func writeRequestLog(apiName string, r *http.Request) error { log, err := json.Marshal(r.Form) if err != nil { logUtil.Log(fmt.Sprintf("序列化数据错误,原始数据位:%v,错误信息为:%s", r.Form, err), logUtil.Error, true) return err } return requestLogDAL.Insert(apiName, string(log)) }
// 每分钟ping一次数据库 func ping() { for { time.Sleep(time.Minute) if err := GameDB.Ping(); err != nil { logUtil.Log(fmt.Sprintf("游戏数据库Ping失败,错误信息为:%s", err), logUtil.Error, true) } } }
func UpdateInfo(player *player.Player) error { command := "UPDATE player SET Name = ?, UnionId = ?, ExtraMsg = ? WHERE Id = ?" stmt, err := dal.ChatDB().Prepare(command) if err != nil { logUtil.Log(fmt.Sprintf("Prepare失败,错误信息:%s,command:%s", err, command), logUtil.Error, true) return err } // 最后关闭 defer stmt.Close() if _, err = stmt.Exec(player.Name, player.UnionId, player.ExtraMsg, player.Id); err != nil { logUtil.Log(fmt.Sprintf("Exec失败,错误信息:%s,command:%s", err, command), logUtil.Error, true) return err } return nil }
func UpdateForbiddenStatus(player *player.Player) error { command := "UPDATE player SET IsForbidden = ? WHERE Id = ?" stmt, err := dal.ChatDB().Prepare(command) if err != nil { logUtil.Log(fmt.Sprintf("Prepare失败,错误信息:%s,command:%s", err, command), logUtil.Error, true) return err } // 最后关闭 defer stmt.Close() if _, err = stmt.Exec(player.IsForbidden, player.Id); err != nil { logUtil.Log(fmt.Sprintf("Exec失败,错误信息:%s,command:%s", err, command), logUtil.Error, true) return err } return nil }
// 回滚事务 // tx:事务对象 // playerObj:玩家对象 // rollbackPlayer:回滚玩家数据的方法 func RollbackTransaction(tx *sql.Tx, playerObj *player.Player, rollbackPlayer func(*player.Player)) { err := tx.Rollback() if err != nil && err != sql.ErrTxDone { logUtil.Log(fmt.Sprintf("回滚事务失败,错误信息:%s", err), logUtil.Error, true) } // 并且回滚玩家数据 rollbackPlayer(playerObj) }
func Insert(apiName, content string) error { command := "INSERT INTO request_log(APIName, ServerGroupId, Content, Crdate) VALUES(?, ?, ?, ?);" stmt, err := dal.ChatDB().Prepare(command) if err != nil { logUtil.Log(fmt.Sprintf("Prepare失败,错误信息:%s,command:%s", err, command), logUtil.Error, true) return err } // 最后关闭 defer stmt.Close() if _, err = stmt.Exec(apiName, dal.ServerGroupId, content, time.Now()); err != nil { logUtil.Log(fmt.Sprintf("Exec失败,错误信息:%s,command:%s", err, command), logUtil.Error, true) return err } return nil }
// 处理系统信号 func signalProc() { sigs := make(chan os.Signal) signal.Notify(sigs, syscall.SIGTERM, syscall.SIGINT) for { // 准备接收信息 <-sigs logUtil.Log("接收到退出的系统命令,准备退出", logUtil.Info, true) // 做一些收尾工作 chatBLL.SaveHistoryMessage() logUtil.Log("接收到退出的系统命令,完成收尾工作,正式退出", logUtil.Info, true) // 一旦收到信号,则表明管理员希望退出程序,则先保存信息,然后退出 os.Exit(0) } }
// 显示数据大小信息(每5分钟更新一次) func displayDataSize() { for { // 刚启动时不需要显示信息,故将Sleep放在前面,而不是最后 time.Sleep(5 * time.Minute) // 组装需要记录的信息 msg := fmt.Sprintf("总共收到%s,发送%s.\t", mathUtil.GetSizeDesc(client.TotalReceiveSize()), mathUtil.GetSizeDesc(client.TotalSendSize())) msg += fmt.Sprintf("当前客户端数量:%d, 玩家数量:%d", clientBLL.GetClientCount(), playerBLL.GetPlayerCount()) logUtil.Log(msg, logUtil.Debug, true) } }
// Goroutine调度 func dispatch() { for { // 每分钟进行一次调度 time.Sleep(time.Minute) // 动态调整Goroutine的数量 dynamicAdjustGoroutine() logUtil.Log(fmt.Sprintf("当前Goroutine数量为:%d,收到的总数量为:%d,未处理的数据量为:%d", goroutineCount, totalDataCount, len(dataChannel)), logUtil.Debug, true) } }