using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Text;
using UnityEngine;
namespace BF
{
///
/// Current version only support one connection
///
internal class RpcService : MonoBehaviour
{
internal class ResponseMessage
{
///
/// 是否请求成功
///
public bool isSuccess { get; set; }
///
/// 请求失败后的错误消息;成功时,没有此消息
///
public string errorMsg { get; set; }
public string payload { get; set; }
}
private class RequestMessage
{
public byte group;
public byte cmd;
public byte[] data;
public Action callback;
}
public enum RpcState
{
None,
Connecting,
PrepareSend,
WaitingRsp,
}
private const string ConnectId = nameof(RpcService);
private RpcState rpcState;
private RequestMessage currentMessage = new RequestMessage();
private ResponseMessage replyMessage = new ResponseMessage();
private bool netClientInitialized = false;
private NetConfiguration configuration;
private int remainRetryCount;
private float requestTimeout = 6;
private string ipAddress;
private int port;
#region Instance
private static RpcService instance;
public static RpcService Instance
{
get
{
if (!instance)
{
var gameObj = new GameObject(nameof(RpcService));
instance = gameObj.AddComponent();
}
return instance;
}
}
private void Awake()
{
DontDestroyOnLoad(gameObject);
}
private void OnDestroy()
{
instance = null;
}
#endregion
internal void SendRequest(string address, int port, byte group, byte cmd, string args, Action response,
int retryCount,float timeout)
{
// service is busy directly response error
if (rpcState != RpcState.None)
{
replyMessage.isSuccess = false;
replyMessage.errorMsg = "Current rpc service is busy";
replyMessage.payload = string.Empty;
response?.Invoke(replyMessage);
return;
}
currentMessage.cmd = cmd;
currentMessage.@group = group;
currentMessage.data = Encoding.UTF8.GetBytes(args);
currentMessage.callback = response;
// set state to connecting
rpcState = RpcState.Connecting;
// setup retry
remainRetryCount = retryCount;
requestTimeout = timeout;
ipAddress = address;
this.port = port;
StartRpcService(address, port);
// start timeout
Invoke(nameof(OnRequestTimeout),requestTimeout);
}
private void StartRpcService(string address, int port)
{
// lazy initialize
if (configuration == null)
{
configuration = new NetConfiguration
{
ServiceType = NetServiceType.TCPService,
ThreadName = "TcpService"
};
}
var netClient = NetClient.Instance;
netClient.Init();
netClient.SetupService(configuration);
netClient.Start();
Connect(ConnectId, address, port);
}
private void Connect(string connectId, string ip, int port)
{
var netClient = NetClient.Instance;
NetConnectConfiguration configuration =
new NetConnectConfiguration(NetServiceType.TCPService, connectId);
configuration.EnableMessageType(NetIncomingMessageType.DebugMessage);
configuration.EnableMessageType(NetIncomingMessageType.WarningMessage);
configuration.EnableMessageType(NetIncomingMessageType.ErrorMessage);
// disable reconnect
// if connection disconnected when sending data
// we will directly response error and close connection
configuration.EnableSilenceReconnect = false;
configuration.HeartBeatInterval = 20;
configuration.MaxHeartBeatMissCount = 3;
// check address is domain or ip address
if (IPAddress.TryParse(ip, out IPAddress ipAddress))
{
netClient.Connect(configuration, ipAddress, port);
}
else
{
netClient.Connect(configuration, ip, port);
}
}
private void Update()
{
// when connected , message send next frame
if (rpcState == RpcState.PrepareSend)
{
// send data
NetClient.Instance.Send(ConnectId, currentMessage.group, currentMessage.cmd,
currentMessage.data);
rpcState = RpcState.WaitingRsp;
}
var netClient = NetClient.Instance;
INetIncomingMessage incomingMessage = null;
while ((incomingMessage = netClient.ReadMessage(ConnectId)) != null)
{
HandleIncomingMessage(incomingMessage);
netClient.RecycleMessage(ConnectId, incomingMessage);
}
}
private void HandleIncomingMessage(INetIncomingMessage message)
{
switch (message.MessageType)
{
case NetIncomingMessageType.DebugMessage:
case NetIncomingMessageType.WarningMessage:
Debug($"Debug Info : {message.ErrorCode} : {message.ErrorMsg}");
break;
case NetIncomingMessageType.ErrorMessage:
HandleErrorMessage(message);
break;
case NetIncomingMessageType.Data:
HandleDataMessage(message);
break;
case NetIncomingMessageType.StatusChange:
HandleStatusMessage(message);
break;
case NetIncomingMessageType.HeartBeatMessage:
// don't care
break;
default:
throw new NotImplementedException();
}
}
private void HandleErrorMessage(INetIncomingMessage message)
{
UnityEngine.Debug.LogError($"Debug Info : {message.ErrorCode} : {message.ErrorMsg}");
replyMessage.isSuccess = false;
replyMessage.errorMsg = message.ErrorMsg;
replyMessage.payload = string.Empty;
currentMessage.callback?.Invoke(replyMessage);
ResetState();
}
private void HandleDataMessage(INetIncomingMessage message)
{
if (currentMessage.cmd != message.CMD || currentMessage.@group != message.Group)
{
// ignore not matched data
replyMessage.isSuccess = false;
replyMessage.errorMsg = "Message type dis-matched";
}
else
{
replyMessage.isSuccess = true;
replyMessage.errorMsg = string.Empty;
replyMessage.payload = Encoding.UTF8.GetString(message.Data);
}
currentMessage.callback?.Invoke(replyMessage);
ResetState();
}
private void HandleStatusMessage(INetIncomingMessage message)
{
NetConnectStatus status = message.ConnectStatus;
// connect success
// will send data in next frame
if (status == NetConnectStatus.Connected && rpcState == RpcState.Connecting)
{
rpcState = RpcState.PrepareSend;
}
// waiting server reply but the connection disconnected
// reply error
else if (status == NetConnectStatus.Disconnected && rpcState == RpcState.WaitingRsp)
{
replyMessage.isSuccess = false;
replyMessage.errorMsg = "Connection disconnected when waiting for reply;Maybe server not support the request";
replyMessage.payload = string.Empty;
currentMessage.callback?.Invoke(replyMessage);
ResetState();
}
}
private void ResetState()
{
CancelInvoke(nameof(OnRequestTimeout));
currentMessage.callback = null;
rpcState = RpcState.None;
NetClient.Instance.Close(ConnectId);
NetClient.Instance.Shutdown();
}
private void OnRequestTimeout()
{
replyMessage.isSuccess = false;
replyMessage.errorMsg = "Waiting reply timeout";
replyMessage.payload = string.Empty;
remainRetryCount--;
if (remainRetryCount < 0)
{
currentMessage?.callback.Invoke(replyMessage);
ResetState();
}
else
{
CancelInvoke(nameof(OnRequestTimeout));
// start timeout
Invoke(nameof(OnRequestTimeout),requestTimeout);
rpcState = RpcState.None;
NetClient.Instance.Close(ConnectId);
// try reconnect
Connect(ConnectId, ipAddress, port);
}
}
[Conditional("BF_DEBUG")]
private static void Debug(string message)
{
UnityEngine.Debug.Log(message);
}
}
}