通过其他地方调用http-post登陆请求,发送token,使用token进行验证,验证后进行socket-tcp长连接
cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using Cysharp.Threading.Tasks;
using Game.Protocol;
using Game.Runtime.Migu;
using Google.Protobuf;
using UnityEngine;
using UnityEngine.Networking;
public class NetworkManager : MonoSingleton<NetworkManager>
{
[Header("网络设置")]
// 本地 xxx.xx.xxx.xx
// 公网 xxx.xxx.x.xx
public string serverIP = "xxx.xxx.x.xx";
public int serverPort = xxxx;
public string httpServerUrl = "http://xxx.xxx.x.xx:xxxx"; // HTTP服务器地址
[Header("心跳设置")]
public float heartbeatInterval = 30f; // 心跳间隔30秒
private long _lastHeartbeatSendTime; // 上次心跳发送时间戳
[Header("协议设置")]
public string protocolVersion = "1.0.0"; // 协议版本
public uint clientSeq = 1; // 客户端序列号计数器
private Socket clientSocket; // 本机客户端Socket
private int _expectedBodyLength = -1; // 期望的消息体长度
private List<byte> _receiveBuffer = new(); // 累积接收缓冲区
private bool isConnected; // 连接状态标志位
private Queue<BasePacket> messageQueue = new(); // 处理消息队列(改为BasePacket)
private object queueLock = new(); // 消息队列线程锁
private Thread receiveThread; // 后台持续接收消息线程
private Dictionary<uint, Action<BasePacket>> pendingRequests = new(); // 待处理请求的映射表
private object pendingLock = new(); // 待处理请求锁
public bool isUserInfoReceived = false; // 是否已收到用户信息
#region 生命周期
protected override void Awake()
{
base.Awake();
DontDestroyOnLoad(gameObject);
}
private void Start()
{
//Connect();
//ConnectServerWithMiguInfo();
}
private void Update()
{
ProcessMessageQueue();
}
protected override void OnDestroy()
{
Disconnect();
}
protected override void OnApplicationQuit()
{
base.OnApplicationQuit();
Disconnect();
}
#endregion
/// <summary>
/// 向服务端发送从快游拿到的信息建立连接
/// </summary>
public void ConnectServerWithMiguInfo()
{
PlayerData data = SDKManager.Instance.MiguPlayerData;
LoginAndConnect(data.token, data.userId, Application.version);
}
// 初始化连接
private void Connect()
{
try
{
clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
clientSocket.Connect(serverIP, serverPort);
isConnected = true;
receiveThread = new Thread(ReceiveData);
receiveThread.IsBackground = true;
receiveThread.Start();
Debug.Log("连接至服务器成功");
// 发送握手请求
SendHandshakeRequest();
// 持续发送心跳消息
StartHeartbeat();
// 把userId,token,accountId带过去
AccountInfo accountInfo = GameManager.Instance.accountInfo;
// 验证accountInfo是否为空
if (accountInfo == null)
{
Debug.LogError("AccountInfo为空,无法发送登录请求");
return;
}
// Socket的登录请求
SendLoginRequest(accountInfo.userId, accountInfo.token, accountInfo.secretUserId, accountInfo.accountId,
accountInfo.deviceId);
// 拿到当前玩家的信息并储存
SendUserInfoReq(GameManager.Instance.accountInfo.accountId);
}
catch (Exception e)
{
Debug.Log(e.Message);
}
}
// 断开连接
private void Disconnect()
{
try
{
isConnected = false;
StopHeartbeat();
if (clientSocket != null)
{
if (clientSocket.Connected) clientSocket.Shutdown(SocketShutdown.Both);
clientSocket.Close();
clientSocket.Dispose();
clientSocket = null;
}
receiveThread?.Join(1000); // 等待接收线程结束
Debug.Log("与服务器断开连接");
}
catch (Exception e)
{
Debug.LogError($"断开连接错误: {e.Message}");
}
}
// 持续接收来自服务器的消息 后台线程
private void ReceiveData()
{
// 创建接收缓冲区,每次最多接收1024字节
var buffer = new byte[1024];
while (isConnected && clientSocket != null && clientSocket.Connected)
{
try
{
// 从socket接收数据,该方法会阻塞直到有数据到达或连接关闭
var bytesRead = clientSocket.Receive(buffer);
Debug.Log($"收到{bytesRead}字节数的消息");
// 检查接收到的字节数
if (bytesRead > 0)
{
// 创建正确大小的数组存放本次接收的数据
var newData = new byte[bytesRead];
Array.Copy(buffer, newData, bytesRead);
// 将新数据添加到累积缓冲区
_receiveBuffer.AddRange(newData);
// 处理累积缓冲区中的数据
ProcessReceivedData();
}
else
{
// 连接已关闭
Debug.Log("服务器连接已关闭");
isConnected = false;
break;
}
}
catch (SocketException se)
{
Debug.LogError($"网络接收错误: {se.Message}");
isConnected = false;
break;
}
catch (Exception e)
{
Debug.LogError($"接收数据异常: {e.Message}");
isConnected = false;
break;
}
}
Debug.Log("接收线程结束");
Disconnect();
}
// 处理接收到的数据,解析完整消息
private void ProcessReceivedData()
{
try
{
// 循环处理所有完整消息
while (_receiveBuffer.Count >= 4) // 至少要有消息头
{
// 如果还没有解析出消息体长度,先解析消息头
if (_expectedBodyLength == -1)
{
// 读取消息头(4字节)
var headBytes = _receiveBuffer.GetRange(0, 4).ToArray();
// 手动读取并转换大端序消息头
_expectedBodyLength = (headBytes[0] << 24) |
(headBytes[1] << 16) |
(headBytes[2] << 8) |
headBytes[3];
// 添加长度验证
if (_expectedBodyLength < 0 || _expectedBodyLength > 10 * 1024 * 1024) // 限制10MB
{
Debug.LogError($"无效的消息长度: {_expectedBodyLength}");
Disconnect();
return;
}
}
// 检查是否收到了完整的消息体
if (_receiveBuffer.Count >= 4 + _expectedBodyLength)
{
// 提取消息体
var bodyBytes = _receiveBuffer.GetRange(4, _expectedBodyLength).ToArray();
// 使用protobuf解析BasePacket
var packet = BasePacket.Parser.ParseFrom(bodyBytes);
lock (queueLock)
{
messageQueue.Enqueue(packet);
}
// 从缓冲区中移除已处理的数据
_receiveBuffer.RemoveRange(0, 4 + _expectedBodyLength);
// 重置期望长度,准备处理下一条消息
_expectedBodyLength = -1;
}
else
{
// 数据不完整,等待下次接收
break;
}
}
}
catch (Exception e)
{
Debug.LogError($"处理接收数据错误: {e.Message}");
Disconnect();
}
}
// 处理消息队列
private void ProcessMessageQueue()
{
lock (queueLock)
{
while (messageQueue.Count > 0)
{
var packet = messageQueue.Dequeue();
HandlePacket(packet);
}
}
}
// 根据消息类型处理消息
private void HandlePacket(BasePacket packet)
{
try
{
var header = packet.Header;
var command = header.Command;
var seq = header.Seq;
var errorCode = header.ErrorCode;
var errorMsg = header.ErrorMsg;
// 检查错误码
if (errorCode != 0)
{
Debug.LogError($"收到错误响应: command={command}, errorCode={errorCode}, errorMsg={errorMsg}");
// 触发对应的错误回调
lock (pendingLock)
{
if (pendingRequests.ContainsKey(seq))
{
pendingRequests[seq]?.Invoke(packet);
pendingRequests.Remove(seq);
}
}
return;
}
Debug.Log($"接收到消息,命令字{command}");
// 根据命令字处理不同类型的消息
switch (command)
{
// 系统消息
// 心跳消息
case (int)Command.HeartbeatResp:
HandleHeartbeatResp(packet);
break;
// 握手消息回应
case (int)Command.HandshakeResp:
HandleHandshakeResp(packet);
break;
// 认证消息
case (int)Command.LoginResp:
HandleLoginResp(packet);
break;
// 处理玩家信息
case (int)Command.UserInfoResp:
HandleUserInfoResp(packet);
break;
// 登出消息
case (int)Command.LogoutResp:
HandleLogoutResp(packet);
break;
// 玩家数据消息
// 切换关卡
// case (int)Command.ChangeLevelResp:
// HandleChangeLevelResp(packet);
// break;
// 切换角色
// case (int)Command.ChangePlayerResp:
// HandleChangeCharacterResp(packet);
// break;
// 通过关卡
// case (int)Command.PassLevelResp:
// HandlePassLevelResp(packet);
// break;
// ?? 预留 玩家升级推送通知
case (int)Command.PlayerLevelUpNotify:
HandlePlayerLevelUpNotify(packet);
break;
// ?? 玩家某个角色升星推送
case (int)Command.CharacterStarUpNotify:
HandlePlayerStarUpNotify(packet);
break;
// ?? 已废弃?? 角色升星
// case (int)Command.PlayerStarUpResp:
// HandlePlayerStarUpResp(packet);
// break;
// 解锁角色
// case (int)Command.UnlockPlayerResp:
// HandleUnlockPlayerResp(packet);
// break;
// ?? 预留 使用道具
case (int)Command.UseItemResp:
HandleUseItemResp(packet);
break;
// 红点系统消息
case (int)Command.RedDotResp:
HandleRedDotResp(packet);
break;
case (int)Command.RedDotUpdate:
HandleRedDotUpdate(packet);
break;
default:
Debug.LogWarning($"未知的命令字: {command}");
break;
}
// 触发对应的请求回调
lock (pendingLock)
{
if (pendingRequests.ContainsKey(seq))
{
pendingRequests[seq]?.Invoke(packet);
pendingRequests.Remove(seq);
}
}
}
catch (Exception e)
{
Debug.LogError($"处理消息时出错: {e.Message}, 堆栈: {e.StackTrace}");
}
}
#region 消息处理函数
// 处理心跳响应
private void HandleHeartbeatResp(BasePacket packet)
{
var heartbeatResp = HeartbeatResp.Parser.ParseFrom(packet.Body);
Debug.Log($"收到心跳响应: 客户端时间={heartbeatResp.ClientTime}, 服务器时间={heartbeatResp.ServerTime}");
// 计算单向延迟(可能不准)
long oneWayDelay = heartbeatResp.ServerTime - heartbeatResp.ClientTime;
// 计算往返延迟(更准确)
long currentTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
long roundTripDelay = currentTime - heartbeatResp.ClientTime;
Debug.Log($"单向延迟: {oneWayDelay}ms");
Debug.Log($"往返延迟(RTT): {roundTripDelay}ms");
}
// 处理握手响应
private void HandleHandshakeResp(BasePacket packet)
{
var handshakeResp = HandshakeResp.Parser.ParseFrom(packet.Body);
Debug.Log($"握手成功: 服务器版本={handshakeResp.ServerVersion}, 会话密钥={handshakeResp.SessionKey}");
// 握手成功后可以发送登录请求
// SendPlayerConnectRequest();
}
// 处理玩家连接响应
private void HandleLoginResp(BasePacket packet)
{
var loginResp = LoginResp.Parser.ParseFrom(packet.Body);
Debug.Log($"玩家连接成功: AccountID={loginResp.AccountId}, 账号ID={loginResp.UserId}, 昵称={loginResp.Nickname}, 等级={loginResp.Level}, 登录时间={loginResp.LoginTime}");
// 处理红点信息
foreach (var redDot in loginResp.RedDots)
{
Debug.Log($"红点系统: ID={redDot.SystemId}, 状态={redDot.HasRedDot}");
}
}
// 请求获得玩家业务信息
private void HandleUserInfoResp(BasePacket packet)
{
var userInfo = UserInfoResp.Parser.ParseFrom(packet.Body);
// 更新玩家信息 在本地保存一份临时变量
GameManager.Instance.userInfo = new UserInfo()
{
accountId = userInfo.AccountId,
userName = userInfo.UserName,
userId = userInfo.UserId,
rankLevel = userInfo.RankLevel,
level = userInfo.Level,
ownedCharacterList = new List<PlayerMessage>(userInfo.OwnedCharacterList),
avatarFrameId = userInfo.AvatarFrameId,
lastLoginTime = userInfo.LastLoginTime,
gold = userInfo.Gold,
lastChosenLevelId = userInfo.LastChosenLevelId,
maxLevelId = userInfo.MaxLevelId,
};
// 狗策划需求 进来上次选择关卡是玩过的最大关卡
GameManager.Instance.userInfo.lastChosenLevelId = userInfo.MaxLevelId;
GameManager.Instance.CurrentGameData.lastChosenLevelId = userInfo.MaxLevelId;
Debug.Log($"从服务器拿角色数据,收到的数据:{userInfo.ToString()}\n, {GameManager.Instance.userInfo.ToString()}");
isUserInfoReceived = true;
}
// 处理玩家断开响应
private void HandleLogoutResp(BasePacket packet)
{
var disconnectResp = LogoutResp.Parser.ParseFrom(packet.Body);
Debug.Log($"玩家断开连接: 用户ID={disconnectResp.UserId}, 时间={disconnectResp.LogoutTime}");
}
// 处理切换关卡响应
private void HandleChangeLevelResp(BasePacket packet)
{
var changeLevelResp = ChangeLevelResp.Parser.ParseFrom(packet.Body);
bool canPlay = changeLevelResp.CanPlay;
}
// 处理切换角色响应
private void HandleChangeCharacterResp(BasePacket packet)
{
var changePlayerResp = ChangePlayerResp.Parser.ParseFrom(packet.Body);
string unlockedCharacters = string.Join(", ", changePlayerResp.UnlockedPlayers);
}
// 处理通过关卡响应
private void HandlePassLevelResp(BasePacket packet)
{
var passLevelResp = PassLevelResp.Parser.ParseFrom(packet.Body);
}
// 处理玩家升级通知
private void HandlePlayerLevelUpNotify(BasePacket packet)
{
var levelUpNotify = PlayerLevelUpNotify.Parser.ParseFrom(packet.Body);
string unlockedSystems = string.Join(", ", levelUpNotify.UnlockedSystems);
Debug.Log($"玩家升级通知: {levelUpNotify.OldLevel}→{levelUpNotify.NewLevel}, " +
$"当前经验={levelUpNotify.CurrentExp}, 当前金币={levelUpNotify.CurrentCoins}, " +
$"解锁系统={unlockedSystems}");
}
// 处理玩家某个角色升星
private void HandlePlayerStarUpNotify(BasePacket packet)
{
Debug.Log("接收到角色升星推送");
var starUpResp = PlayerStarUpResp.Parser.ParseFrom(packet.Body);
Debug.Log($"接收到的原始消息:{starUpResp}");
Debug.Log($"升星角色的id:{starUpResp.PlayerId}, 原星级:{starUpResp.OldStar}, 升级到的星级:{starUpResp.NewStar}({(int)starUpResp.NewStar})");
PlayerMessage data =
GameManager.Instance.userInfo.ownedCharacterList
.Find(data => data.PlayerId == starUpResp.PlayerId);
// 更新本地角色星级
if (data != null)
{
data.PlayerStar = starUpResp.NewStar;
// 如果直接进入可能会出问题
if (GameManager.Instance.CurrentGameData.currentSelectedCharacter.id == starUpResp.PlayerId)
{
GameManager.Instance.CurrentGameData.currentSelectedCharacter.star = (int)starUpResp.NewStar;
}
Debug.Log($"更新本地记录:角色id:{data.PlayerId}升星为:{starUpResp}星");
}
else
{
Debug.Log($"发的东西在我本地找不到这个角色,升个蛋的星:playerId:{starUpResp.PlayerId}");
}
UIManager.Instance.ShowPanel<AddLevelPanel>("addLevelPanel", (panel) =>
{
panel.SetStarUpData(starUpResp.PlayerId,(int)starUpResp.OldStar, (int)starUpResp.NewStar);
});
}
// 处理角色升星响应
private void HandlePlayerStarUpResp(BasePacket packet)
{
var starUpResp = PlayerStarUpResp.Parser.ParseFrom(packet.Body);
}
// 处理解锁角色响应
private void HandleUnlockPlayerResp(BasePacket packet)
{
var unlockResp = UnlockPlayerResp.Parser.ParseFrom(packet.Body);
}
// 处理使用道具响应
private void HandleUseItemResp(BasePacket packet)
{
var useItemResp = UseItemResp.Parser.ParseFrom(packet.Body);
Debug.Log($"使用道具响应: 道具ID={useItemResp.ItemId}, 结果={useItemResp.Success}, " +
$"使用数量={useItemResp.UsedCount}, 剩余数量={useItemResp.RemainingCount}");
}
// 处理红点状态响应
private void HandleRedDotResp(BasePacket packet)
{
var redDotResp = RedDotResp.Parser.ParseFrom(packet.Body);
foreach (var redDot in redDotResp.RedDots)
{
Debug.Log($"红点状态: 系统ID={redDot.SystemId}, 状态={redDot.HasRedDot}");
}
}
// 处理红点状态更新
private void HandleRedDotUpdate(BasePacket packet)
{
var redDotUpdate = RedDotUpdate.Parser.ParseFrom(packet.Body);
foreach (var redDot in redDotUpdate.RedDots)
{
Debug.Log($"红点更新: 系统ID={redDot.SystemId}, 状态={redDot.HasRedDot}");
}
}
#endregion
#region 消息发送函数
/// <summary>
/// 发送BasePacket消息到服务器
/// </summary>
/// <param name="packet">封装好的BasePacket消息</param>
/// <param name="callback">响应回调(可选)</param>
public void Send(BasePacket packet, Action<BasePacket> callback = null)
{
try
{
if (!isConnected || clientSocket == null || !clientSocket.Connected)
{
Debug.LogWarning("连接已断开,无法发送消息");
return;
}
var header = packet.Header;
var seq = header.Seq;
// 注册回调
if (callback != null && seq > 0)
{
lock (pendingLock)
{
pendingRequests[seq] = callback;
}
}
// 使用 protobuf 序列化消息
var bodyData = packet.ToByteArray();
var bodyLength = bodyData.Length;
// 手动创建大端序的消息头
byte[] headData = new byte[4];
headData[0] = (byte)(bodyLength >> 24);
headData[1] = (byte)(bodyLength >> 16);
headData[2] = (byte)(bodyLength >> 8);
headData[3] = (byte)bodyLength;
// 合并消息头和消息体
var totalData = new byte[headData.Length + bodyData.Length];
Buffer.BlockCopy(headData, 0, totalData, 0, headData.Length);
Buffer.BlockCopy(bodyData, 0, totalData, headData.Length, bodyData.Length);
clientSocket.Send(totalData);
Debug.Log($"发送消息成功,命令字: {header.Command}, 序列号: {seq}, 消息体长度: {bodyLength}");
}
catch (Exception exception)
{
Debug.LogError($"发送消息错误: {exception.Message}");
// 清理待处理请求
lock (pendingLock)
{
if (packet.Header.Seq > 0)
{
pendingRequests.Remove(packet.Header.Seq);
}
}
Disconnect();
}
}
// 发送握手请求
private void SendHandshakeRequest()
{
var handshakeReq = new HandshakeReq
{
ClientVersion = protocolVersion,
Platform = Application.platform.ToString(),
DeviceId = SystemInfo.deviceUniqueIdentifier
};
uint seq = GetNextSeq();
var packet = CreateBasePacket((int)Command.HandshakeReq, seq, handshakeReq);
Send(packet, HandleHandshakeResp);
}
// 发送心跳包
private void SendHeartbeat()
{
if (!IsConnected())
{
Debug.LogWarning("连接已断开,跳过心跳发送");
StopHeartbeat();
return;
}
try
{
// 记录上次发送时间
_lastHeartbeatSendTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
var heartbeatReq = new HeartbeatReq
{
ClientTime = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()
};
uint seq = GetNextSeq();
var packet = CreateBasePacket((int)Command.HeartbeatReq, seq, heartbeatReq);
Send(packet, HandleHeartbeatResp);
}
catch (Exception e)
{
Debug.LogError($"发送心跳包失败: {e.Message}");
// 如果心跳发送失败,可能连接已断开
Disconnect();
}
}
// 发送玩家登录请求
public void SendLoginRequest(string userId, string token, string secretUserId, long accountId, string deviceId)
{
var connectReq = new LoginReq()
{
UserId = userId,
Token = token,
Platform = Application.platform.ToString(),
DeviceId = deviceId,
SecretUserId = secretUserId,
AccountId = accountId,
};
uint seq = GetNextSeq();
var packet = CreateBasePacket((int)Command.LoginReq, seq, connectReq);
Send(packet, HandleLoginResp);
}
// 获取玩家数据信息
public void SendUserInfoReq(long accountId)
{
var userInfoReq = new UserInfoReq()
{
AccountId = accountId
};
uint seq = GetNextSeq();
var packet = CreateBasePacket((int)Command.UserInfoReq, seq, userInfoReq);
Send(packet);
}
// 发送切换关卡请求
public void SendChangeLevelRequest(int targetLevelId, Action<BasePacket> callback = null)
{
var changeLevelReq = new ChangeLevelReq
{
TargetLevelId = targetLevelId
};
uint seq = GetNextSeq();
var packet = CreateBasePacket((int)Command.ChangeLevelReq, seq, changeLevelReq);
Send(packet, callback);
}
// 发送切换角色请求
public void SendChangeCharacterRequest(long targetCharacterId, Action<BasePacket> callback = null)
{
var changeCharacterReq = new ChangePlayerReq()
{
TargetPlayerId = targetCharacterId
};
uint seq = GetNextSeq();
var packet = CreateBasePacket((int)Command.ChangePlayerReq, seq, changeCharacterReq);
Send(packet, callback);
}
// 发送通过关卡请求
public void SendPassLevelRequest(int levelId, long playerId, int star, bool isPass,int score = 0, int timeUsed = 0, Action<BasePacket> callback = null)
{
var passLevelReq = new PassLevelReq
{
LevelId = levelId,
PlayerId = playerId,
PlayerStar = star,
IsPass = isPass,
Score = score,
TimeUsed = timeUsed
};
Debug.Log($"发送消息:{passLevelReq}");
uint seq = GetNextSeq();
var packet = CreateBasePacket((int)Command.PassLevelReq, seq, passLevelReq);
Send(packet, callback);
}
// 发送角色升星请求
public void SendCharacterStarUpRequest(long playerId, Action<BasePacket> callback = null)
{
var starUpReq = new PlayerStarUpReq()
{
PlayerId = playerId
};
uint seq = GetNextSeq();
var packet = CreateBasePacket((int)Command.PlayerStarUpReq, seq, starUpReq);
Send(packet, callback);
}
// 发送解锁角色请求
public void SendUnlockCharacterRequest(long characterId, Action<BasePacket> callback = null)
{
var unlockReq = new UnlockPlayerReq()
{
PlayerId = characterId
};
uint seq = GetNextSeq();
var packet = CreateBasePacket((int)Command.UnlockPlayerReq, seq, unlockReq);
Send(packet, callback);
}
// 发送使用道具请求
public void SendUseItemRequest(int itemId, int count = 1, int targetId = 0, Action<BasePacket> callback = null)
{
var useItemReq = new UseItemReq
{
ItemId = itemId,
Count = count,
TargetId = targetId
};
uint seq = GetNextSeq();
var packet = CreateBasePacket((int)Command.UseItemReq, seq, useItemReq);
Send(packet, callback);
}
// 发送红点状态请求
public void SendRedDotRequest(int[] systemIds = null)
{
var redDotReq = new RedDotReq();
if (systemIds != null && systemIds.Length > 0)
{
redDotReq.SystemIds.AddRange(systemIds);
}
uint seq = GetNextSeq();
var packet = CreateBasePacket((int)Command.RedDotReq, seq, redDotReq);
Send(packet, HandleRedDotResp);
}
// 游戏退出
public void SendLogoutRequest()
{
var logoutReq = new LogoutReq()
{
Reason = 1,
UserId = GameManager.Instance.accountInfo.userId
};
uint seq = GetNextSeq();
var packet = CreateBasePacket((int)Command.LogoutReq, seq, logoutReq);
Send(packet);
}
#endregion
#region 工具函数
/// <summary>
/// 创建BasePacket
/// </summary>
/// <param name="command">命令字</param>
/// <param name="seq">序列号</param>
/// <param name="businessMessage">业务消息</param>
/// <param name="routeKey">路由键(可选)</param>
/// <returns>BasePacket实例</returns>
private BasePacket CreateBasePacket(int command, uint seq, IMessage businessMessage, string routeKey = "")
{
// 构造协议头
var header = new PacketHeader
{
Command = (uint)command,
Seq = seq,
ErrorCode = 0,
ErrorMsg = "",
RouteKey = routeKey,
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(),
Version = protocolVersion,
Signature = "" // 签名
};
// 构造BasePacket
var packet = new BasePacket
{
Header = header,
Body = ByteString.CopyFrom(businessMessage.ToByteArray())
};
return packet;
}
/// <summary>
/// 获取下一个序列号
/// </summary>
private uint GetNextSeq()
{
uint seq = clientSeq;
clientSeq++;
// 防止溢出
if (clientSeq == 0)
{
clientSeq = 1;
}
return seq;
}
// 启动心跳机制
private void StartHeartbeat()
{
// 使用InvokeRepeating每30秒发送一次心跳
InvokeRepeating(nameof(SendHeartbeat), heartbeatInterval, heartbeatInterval);
Debug.Log($"心跳机制已启动,间隔: {heartbeatInterval}秒");
}
// 停止心跳机制
private void StopHeartbeat()
{
CancelInvoke(nameof(SendHeartbeat));
Debug.Log("心跳机制已停止");
}
/// <summary>
/// 获取当前连接状态
/// </summary>
public bool IsConnected()
{
return isConnected && clientSocket != null && clientSocket.Connected;
}
#endregion
#region HTTP请求功能
// HTTP登录请求的数据结构
[System.Serializable]
private class LoginRequestData
{
public string token;
public string userId;
public string version;
public string deviceId;
public string platform;
}
// 服务器返回的统一结果包装类
[System.Serializable]
private class ServerResult<T>
{
public int code;
public string message;
public T data;
public bool success;
}
// HTTP登录响应的具体数据类
[System.Serializable]
private class LoginResponseData
{
public long accountId;
public string userId;
public bool isNewUser;
public string message;
}
/// <summary>
/// 发送HTTP登录请求
/// </summary>
public void SendHttpLoginRequest(string token, string userId, string version = "0.01", string deviceId = "", string platform = "", Action<string> onSuccess = null, Action<string> onFailure = null)
{
// 构造登录URL
string loginUrl = $"{httpServerUrl}/api/auth/login";
// 创建登录请求数据
var loginData = new LoginRequestData
{
token = token,
deviceId = deviceId,
version = version,
platform = platform,
userId = userId
};
// 将数据序列化为JSON
string jsonData = JsonUtility.ToJson(loginData);
// 使用 UniTask 异步发送 HTTP 请求
SendHttpPostRequestAsync(loginUrl, jsonData, onSuccess, onFailure).Forget();
}
/// <summary>
/// 发送通用的 HTTP POST 请求 - 使用 UniTask 异步方法
/// </summary>
/// <param name="url">请求 URL</param>
/// <param name="jsonData">JSON 格式的请求体</param>
/// <param name="onSuccess">成功回调</param>
/// <param name="onFailure">失败回调</param>
/// <param name="contentType">Content-Type,默认为 application/json</param>
private async UniTask SendHttpPostRequestAsync(string url, string jsonData, Action<string> onSuccess, Action<string> onFailure, string contentType = "application/json")
{
Debug.Log($"发送 HTTP POST 请求到: {url}");
Debug.Log($"请求数据: {jsonData}");
try
{
// 创建 UnityWebRequest
using (UnityWebRequest request = new UnityWebRequest(url, "POST"))
{
// 设置请求头
request.SetRequestHeader("Content-Type", contentType);
// 将 JSON 数据转换为字节数组
byte[] bodyRaw = Encoding.UTF8.GetBytes(jsonData);
request.uploadHandler = new UploadHandlerRaw(bodyRaw);
request.downloadHandler = new DownloadHandlerBuffer();
// 设置超时时间
request.timeout = 10;
// 发送请求 - 使用 UniTask 异步等待
await request.SendWebRequest().ToUniTask();
// 处理响应
if (request.result == UnityWebRequest.Result.Success)
{
string responseText = request.downloadHandler.text;
Debug.Log($"HTTP 请求成功: {responseText}");
// 解析响应
try
{
// 尝试解析为 JSON
var responseData = JsonUtility.FromJson<LoginResponseData>(responseText);
if (responseData != null)
{
onSuccess?.Invoke(responseText);
}
else
{
// 直接返回原始响应
Debug.LogWarning($"json 为空: {responseData}");
}
}
catch (Exception e)
{
// JSON 解析失败,返回原始响应
Debug.LogWarning($"JSON 解析失败: {e.Message}");
onSuccess?.Invoke(responseText);
}
}
else
{
string errorMsg = $"HTTP 请求失败: {request.error}";
if (request.responseCode != 0)
{
errorMsg += $", 状态码: {request.responseCode}";
}
Debug.LogError(errorMsg);
onFailure?.Invoke(errorMsg);
}
}
}
catch (Exception e)
{
string errorMsg = $"HTTP 请求异常: {e.Message}";
Debug.LogError(errorMsg);
onFailure?.Invoke(errorMsg);
}
}
/// <summary>
/// 发送 HTTP GET 请求
/// </summary>
/// <param name="url">请求 URL</param>
/// <param name="onSuccess">成功回调</param>
/// <param name="onFailure">失败回调</param>
public void SendHttpGetRequest(string url, Action<string> onSuccess = null, Action<string> onFailure = null)
{
// 使用 UniTask 异步发送 HTTP GET 请求
SendHttpGetRequestAsync(url, onSuccess, onFailure).Forget();
}
/// <summary>
/// 发送 HTTP GET 请求 - 使用 UniTask 异步方法
/// </summary>
private async UniTask SendHttpGetRequestAsync(string url, Action<string> onSuccess, Action<string> onFailure)
{
Debug.Log($"发送 HTTP GET 请求到: {url}");
try
{
using (UnityWebRequest request = UnityWebRequest.Get(url))
{
request.timeout = 10;
// 发送请求 - 使用 UniTask 异步等待
await request.SendWebRequest().ToUniTask();
if (request.result == UnityWebRequest.Result.Success)
{
string responseText = request.downloadHandler.text;
Debug.Log($"HTTP GET 请求成功: {responseText}");
onSuccess?.Invoke(responseText);
}
else
{
string errorMsg = $"HTTP GET 请求失败: {request.error}";
if (request.responseCode != 0)
{
errorMsg += $", 状态码: {request.responseCode}";
}
Debug.LogError(errorMsg);
onFailure?.Invoke(errorMsg);
}
}
}
catch (Exception e)
{
string errorMsg = $"HTTP GET 请求异常: {e.Message}";
Debug.LogError(errorMsg);
onFailure?.Invoke(errorMsg);
}
}
#endregion
#region 公共方法
/// <summary>
/// 开始 Socket 连接
/// </summary>
public void StartSocketConnection()
{
Connect();
}
/// <summary>
/// 停止 Socket 连接
/// </summary>
public void StopSocketConnection()
{
Disconnect();
}
/// <summary>
/// 使用 HTTP 登录后连接 Socket
/// </summary>
public void LoginAndConnect(string token, string userId, string version, string deviceId = "", string platform = "")
{
// 通过 HTTP 登录获取 token
SendHttpLoginRequest(token, userId, version, deviceId, platform,
(response) =>
{
Debug.Log($"登录成功: {response}");
// 解析响应获取 token
try
{
// 使用 ServerResult<LoginResponseData> 来解析嵌套的 JSON 结构
var serverResult = JsonUtility.FromJson<ServerResult<LoginResponseData>>(response);
if (serverResult != null && serverResult.success && serverResult.data != null)
{
var loginResponse = serverResult.data;
Debug.Log($"获得服务器 http 返回消息: accountId:{loginResponse.accountId}, userId:{loginResponse.userId}, isNewUser:{loginResponse.isNewUser}");
// 确保 GameManager.Instance.accountInfo 不为空
if (GameManager.Instance.accountInfo == null)
{
GameManager.Instance.accountInfo = new AccountInfo();
}
// 设置账号信息
GameManager.Instance.accountInfo.accountId = loginResponse.accountId;
GameManager.Instance.accountInfo.userId = loginResponse.userId;
GameManager.Instance.accountInfo.isNewUser = loginResponse.isNewUser;
// 从 SDKManager 获取其他信息
if (SDKManager.Instance != null && SDKManager.Instance.MiguPlayerData != null)
{
var miguData = SDKManager.Instance.MiguPlayerData;
GameManager.Instance.accountInfo.token = miguData.token;
GameManager.Instance.accountInfo.secretUserId = miguData.secretUserId;
GameManager.Instance.accountInfo.deviceId = miguData.deviceId;
GameManager.Instance.accountInfo.platform = platform;
}
else
{
Debug.LogWarning("SDKManager.Instance.MiguPlayerData 为空,使用传入的参数");
GameManager.Instance.accountInfo.token = token;
GameManager.Instance.accountInfo.deviceId = deviceId;
GameManager.Instance.accountInfo.platform = platform;
}
StartSocketConnection();
}
else
{
Debug.LogError($"登录响应解析失败或返回错误: code={serverResult?.code}, message={serverResult?.message}");
}
}
catch (Exception e)
{
Debug.LogError($"解析登录响应失败: {e.Message}");
Debug.LogError($"堆栈跟踪: {e.StackTrace}");
}
},
(error) =>
{
Debug.LogError($"登录失败: {error}");
});
}
#endregion
}
proto文件如下
bash
syntax = "proto3";
package game.protocol;
option java_package = "game.protocol";
option java_multiple_files = true;
option csharp_namespace = "Game.Protocol";
// ====== 基础消息结构 (base.proto) ======
// 错误码枚举
enum ErrorCode {
SUCCESS = 0; // 成功
// 系统级错误 1-999
SYSTEM_ERROR = 1; // 系统错误
PROTOCOL_ERROR = 2; // 协议错误
SERVER_BUSY = 3; // 服务器繁忙
MAINTENANCE = 4; // 服务器维护中
// 连接相关错误 1000-1999
CONNECTION_EXPIRED = 1001; // 连接已过期
TOO_MANY_CONNECTIONS = 1002; // 连接数过多
INVALID_SESSION = 1003; // 无效会话
// 认证相关错误 2000-2999
AUTH_FAILED = 2001; // 认证失败
TOKEN_EXPIRED = 2002; // token过期
ACCOUNT_BANNED = 2003; // 账号被封禁
VERSION_TOO_OLD = 2004; // 版本过旧
// 业务逻辑错误 3000-3999
INSUFFICIENT_RESOURCES = 3001; // 资源不足
OPERATION_TOO_FREQUENT = 3002; // 操作过于频繁
TARGET_NOT_FOUND = 3003; // 目标不存在
CONDITION_NOT_MET = 3004; // 条件不满足
}
// 命令字枚举
enum Command {
NONE = 0;
// 系统消息 1-999
HEARTBEAT_REQ = 1; // 心跳请求
HEARTBEAT_RESP = 2; // 心跳响应
HANDSHAKE_REQ = 3; // 握手建立连接
HANDSHAKE_RESP = 4; // 握手响应
// 认证消息 1000-1999
LOGIN_REQ = 1001; // 登录请求
LOGIN_RESP = 1002; // 登录响应
LOGOUT_REQ = 1003; // 登出请求
LOGOUT_RESP = 1004; // 登出响应
// 玩家数据 2000-2999 请求
USER_INFO_REQ = 2001; // 玩家信息请求
CHANGE_LEVEL_REQ = 2003; // 切换关卡请求
CHANGE_PLAYER_REQ = 2005; // 切换角色请求
PASS_LEVEL_REQ = 2007; // 通过关卡请求
PLAYER_STAR_UP_REQ = 2009; // 角色升星请求
UNLOCK_PLAYER_REQ = 2011; // 解锁角色请求
USE_ITEM_REQ = 2013; // 使用道具请求
// 玩家数据 2000-2999 响应
USER_INFO_RESP = 2002; // 玩家信息响应
CHANGE_LEVEL_RESP = 2004; // 切换关卡响应
CHANGE_PLAYER_RESP = 2006; // 切换角色响应
PASS_LEVEL_RESP = 2008; // 通过关卡响应
PLAYER_STAR_UP_RESP = 2010; // 角色升星响应
UNLOCK_PLAYER_RESP = 2012; // 解锁角色响应
USE_ITEM_RESP = 2014; // 使用道具响应
PLAYER_LEVEL_UP_NOTIFY = 2015; // 玩家升级通知(推送)
CHARACTER_STAR_UP_NOTIFY = 2016; // 角色升星推送
// 红点系统 3000-3099
RED_DOT_REQ = 3001; // 红点状态请求
RED_DOT_RESP = 3002; // 红点状态响应
RED_DOT_UPDATE = 3003; // 红点状态更新(服务端推送)
// 匹配对战 4000-4999
MATCH_START_REQ = 4001; // 开始匹配
MATCH_START_RESP = 4002; // 开始匹配响应
MATCH_CANCEL_REQ = 4003; // 取消匹配
MATCH_CANCEL_RESP = 4004; // 取消匹配响应
MATCH_SUCCESS_NOTIFY = 4005; // 匹配成功通知
// 战斗相关 5000-5999
BATTLE_READY_REQ = 5001; // 战斗准备
BATTLE_ACTION_REQ = 5002; // 战斗操作
BATTLE_ACTION_RESP = 5003; // 战斗操作响应
BATTLE_FINISH_NOTIFY = 5004; // 战斗结束通知
}
// 通用协议头
message PacketHeader {
uint32 command = 1; // 指令号
uint32 seq = 2; // 序列号(请求-响应匹配)
int32 error_code = 3; // 错误码(0=成功)
string error_msg = 4; // 错误信息
string route_key = 5; // 路由键(用户ID等)
int64 timestamp = 6; // 时间戳
string version = 7; // 协议版本
string signature = 8; // 签名(防篡改)
}
// 基础数据包结构
message BasePacket {
PacketHeader header = 1;
bytes body = 2; // 具体业务消息的序列化结果
}
// ====== 通用数据结构 ======
// 查询玩家请求
message UserInfoReq {
int64 account_id = 1; // 目标玩家ID
}
// 一个角色的详细信息
message PlayerMessage{
int64 player_id = 1; // 角色id
PlayerStar player_star = 2; // 角色星级
int32 current_exp = 3; // 角色当前经验值
int32 next_star_max_exp = 4; // 角色到下一个星级的总经验值 150/300
}
// 玩家基础信息
message UserInfoResp {
int64 account_id = 1; // 玩家ID
string user_name = 2; // 玩家名称
int64 user_id = 3; // 账号ID
int32 rank_level = 4; // 段位等级 预留
int32 level = 5; // 玩家等级 预留
//repeated int32 owned_character_ids = 6; // 玩家已解锁/拥有的角色ID列表
//map<int64, PlayerStar> owned_character_dict = 6 ; // 玩家已解锁角色及其对应最高星级哈希表
repeated PlayerMessage owned_character_list = 6; // 玩家已解锁角色对应详细信息链表
int32 avatar_frame_id = 7; // 当前头像框ID(对应account表的avatar_frame_id)
int64 last_login_time = 8; // 最后登录时间戳(对应account表的last_login_time)
int32 gold = 9; // 玩家金币
int32 last_chosen_level_id = 10; // 上次选择的地图的id
int32 max_level_id = 11; // 最高通过关卡id
}
// 红点信息
message RedDotInfo {
int32 system_id = 1; // 系统ID
bool has_red_dot = 2; // 是否有红点
}
// ====== 系统消息模块 (system.proto) ======
// 心跳请求
message HeartbeatReq {
int64 client_time = 1; // 客户端时间戳
}
// 心跳响应
message HeartbeatResp {
int64 client_time = 1; // 客户端时间
int64 server_time = 2; // 服务器时间
}
// 握手请求
message HandshakeReq {
string client_version = 1; // 客户端版本
string platform = 2; // 平台(ios/android/pc)
string device_id = 3; // 设备ID
}
// 握手响应
message HandshakeResp {
string server_version = 1; // 服务器版本
int64 server_time = 2; // 服务器时间
string session_key = 3; // 会话密钥
}
// ====== 认证消息模块 (auth.proto) ======
// 登录请求
message LoginReq {
string user_id = 1; // 账号
string token = 2; // 登录令牌
string platform = 3; // 平台
string device_id = 4; // 设备ID
string secret_user_id = 5; // 用户ID_secrect
int64 account_id = 6; // 用户id
}
// 登录响应
message LoginResp {
uint64 account_id = 1; // 账号ID
uint64 user_id = 2; // 用户ID
string nickname = 3; // 昵称
int32 level = 4; // 等级
int64 login_time = 5; // 登录时间
repeated RedDotInfo red_dots = 6; // 红点信息
}
// 登出请求
message LogoutReq {
string user_id = 1;
int32 reason = 2; // 登出原因 1=主动退出 2=异地登录
}
// 登出响应
message LogoutResp {
string user_id = 1;
int64 logout_time = 2;
}
// ====== 玩家数据模块 (player.proto) ======
// 切换关卡请求
message ChangeLevelReq {
int32 target_level_id = 1; // 目标关卡ID
}
// 切换关卡响应
message ChangeLevelResp {
int32 max_level_id = 1; // 玩家最高解锁的关卡
int32 current_level_id = 2; // 当前选择的关卡
bool can_play = 3; // 能否游玩该关卡
}
// 切换角色请求
message ChangePlayerReq {
int64 target_player_id = 1; // 目标角色ID
}
// 切换角色响应
message ChangePlayerResp {
int64 current_player_id = 1; // 当前选择的角色ID
bool is_unlocked = 2; // 是否已解锁
PlayerStar current_player_star = 3; // 如果已解锁,当前选择角色星级
map<int64, PlayerStar> unlocked_players = 4; // 已解锁的所有角色
}
// 通过关卡请求
message PassLevelReq {
int32 level_id = 1; // 通过的关卡ID
int64 player_id = 2; // 当前关卡使用的角色id
int32 player_star = 3; // 当前关卡使用的角色星级
bool is_pass = 4; // 是否成功通关
int32 score = 5; // 得分(可选)
int32 time_used = 6; // 用时(秒,可选)
}
// 通过关卡响应
message PassLevelResp {
int32 level_id = 1; // 通过的关卡ID
int32 coins_earned = 2; // s获得的金币
int32 exp_earned = 3; // 获得的经验
bool is_new_record = 4; // 是否新记录
int32 new_max_level_id = 5; // 新的最高解锁关卡
int32 current_exp = 6; // 当前最新经验值
int32 next_star_max_exp = 7; // 当前星级最大经验值
}
// 玩家升级通知(服务端推送)
message PlayerLevelUpNotify {
int32 old_level = 1; // 原等级
int32 new_level = 2; // 新等级
int32 current_exp = 3; // 当前经验值
int32 current_coins = 4; // 当前金币
repeated int32 unlocked_systems = 5; // 解锁的新系统
}
// 角色升星通知(服务端推送)
message PlayerRoleStarUpNotify {
int64 player_id = 1; // 角色ID
PlayerStar old_star = 2; // 原星级
PlayerStar new_star = 3; // 新星级
int32 current_exp = 4; // 当前经验值
int32 exp_cost = 5; // 升星消耗的经验值
}
// 角色星级枚举
enum PlayerStar {
STAR_0 = 0; // 无星
STAR_1 = 1; // 一星
STAR_2 = 2; // 二星
STAR_3 = 3; // 三星
STAR_4 = 4; // 四星
STAR_5 = 5; // 五星
}
// 角色升星请求
message PlayerStarUpReq {
int64 player_id = 1; // 角色ID
}
// 角色升星响应
message PlayerStarUpResp {
int64 player_id = 1; // 角色ID
PlayerStar old_star = 2; // 原星级
PlayerStar new_star = 3; // 新星级
int32 coins_cost = 4; // 消耗的金币
int32 exp_cost = 5; // 消耗的经验
int32 remaining_coins = 6; // 剩余金币
int32 remaining_exp = 7; // 剩余经验
}
// 解锁角色请求
message UnlockPlayerReq {
int64 player_id = 1; // 角色ID
}
// 解锁角色响应
message UnlockPlayerResp {
int64 player_id = 1; // 角色ID
bool success = 2; // 是否成功
int32 coins_cost = 3; // 消耗的金币
int32 remaining_coins = 4; // 剩余金币
PlayerStar initial_star = 5; // 初始星级
int32 next_star_max_exp = 6; // 下一星级所需经验
}
// 道具信息
message ItemInfo {
int32 item_id = 1; // 道具ID
string item_name = 2; // 道具名称
int32 count = 3; // 数量
int32 effect_type = 4; // 效果类型
int32 effect_value = 5; // 效果值
int64 expire_time = 6; // 过期时间
}
// 使用道具请求
message UseItemReq {
int32 item_id = 1; // 道具ID
int32 count = 2; // 使用数量
int32 target_id = 3; // 目标ID(如角色ID,可选)
}
// 使用道具响应
message UseItemResp {
int32 item_id = 1; // 道具ID
bool success = 2; // 是否成功
int32 used_count = 3; // 使用的数量
int32 remaining_count = 4; // 剩余数量
string effect_description = 5; // 效果描述
int64 effect_end_time = 6; // 效果结束时间
}
// ====== 红点系统模块 (reddot.proto) ======
// 红点状态请求
message RedDotReq {
repeated int32 system_ids = 1; // 请求的系统ID列表(空表示请求所有)
}
// 红点状态响应
message RedDotResp {
repeated RedDotInfo red_dots = 1; // 红点状态列表
}
// 红点状态更新(服务端推送)
message RedDotUpdate {
repeated RedDotInfo red_dots = 1; // 需要更新的红点状态
}