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); } } }