第一篇 《連接配置》
第二篇 《連接池》
第三篇 標準通信
一、TCP的連接是無狀態的,怎樣知道我的請求被服務端接受並且正確執行了呢?
我的解決方案是使用自己定義的標準輸入輸出,Push操作和Delete操作都要返回Json的字符串,也就是說,每一個Thrift接口方法的輸入參數和返回參數都是Json字符串。標準返回,Code表示狀態碼,Desc表示對執行結果的描述,如果Code表示服務端出錯,Desc爲錯誤信息。
1 public class StandResponse<T> 2 { 3 /// <summary> 4 /// 服務端成功調用:0 5 /// 服務端業務異常:-1 6 /// </summary> 7 public string Code { get; set; } 8 9 public string Desc { get; set; } 10 11 public T Data { get; set; } 12 }
二、一般情況,服務端和客戶端服務器應在同一個內網,所以可以不用進行接口調用的身份驗證,只需保證服務端不被外網訪問即可。當然也可以簡單的,採用對客戶端調用時間戳加密,並把時間戳和密文發到服務端後,用相同的加密算法對時間戳加密,對密文進行比較來驗證。
//標準請求,包括客戶端服務器的IP及主機名,如果客戶端是Web服務器,用戶請求的URL和用戶請求IP也會一併帶到服務器端,用於做驗證或請求限制。 public class StandRequest<T> { private string SIGN_KEY = ConfigurationManager.AppSettings["ThriftSignKey"] ?? "Thrift_Sign"; public string HostName { get; set; } public string IP { get; set; } public string RequestUrl { get; set; } public string UserRequestIP { get; set; } public string TimeStamp { get; set; } public string Sign { get; set; } public T Data { get; set; } public string SignTimestamp(string timeStamp) { MD5 md5 = new MD5CryptoServiceProvider(); byte[] array = md5.ComputeHash(Encoding.UTF8.GetBytes(timeStamp + SIGN_KEY)); return Convert.ToBase64String(array); } //驗證客戶請求是否合法 public bool IsValid() { MD5 md5 = new MD5CryptoServiceProvider(); byte[] array = md5.ComputeHash(Encoding.UTF8.GetBytes(TimeStamp + SIGN_KEY)); string sign = Convert.ToBase64String(array); return Sign.Equals(sign); } }
可通過在客戶端和服務端,配置相同的ThriftSignKey來確保請求來自自己開發的客戶端。其實在標準請求裏還應該定義一個參數,就是當前請求的時間,這樣到服務端時可以監控請求在傳輸時的時延。
三、使用了標準輸入輸出來傳輸數據,但每次讓開發者去序列化標準輸入輸出進行這些重複性勞動也是不能忍的。以及讓開發者去處理超時異常和服務端如果拋出未經處理的異常,客戶端和服務端會失去連接,客戶端拋TTransportException,每請求一次都讓開發者來處理這些異常,讓人難以接受。我再次想到使用反射,這樣我可以在結果返回給開發者之前做一些處理。相當於攔截器,或者理解爲面向切面的處理。
1 private string serviceName; 2 3 private TTransport transport; 4 5 private T instance; 6 7 private bool disposed; 8 9 public ThriftClient(string serviceName) 10 { 11 disposed = false; 12 this.serviceName = serviceName; 13 transport = ThriftFactory.BorrowInstance(serviceName); 14 TProtocol protocol = new TBinaryProtocol(transport); 15 //使用Invoker創建實例,InvokerEmitter拋異常“找不到公開實例構造方法”,可能是內部類獲取的類名有點奇怪導致,例如“HelloWorldService.Client”。 16 //instance = (T)Invoker.CreateInstance<T>(protocol); 17 instance = (T)Activator.CreateInstance(typeof(T),protocol); 18 }
初始化得到一個服務的Client,也就是instance。
真正的訪問服務端的方法被封裝成一個使用標準輸入輸出的私有化方法,完成服務端和客戶端通信的方法就只有Invoker.MethodInvoke一行,其餘的都是對異常進行處理。
1 private StandResponse<P> Invoke<Q, P>(string methodName, StandRequest<Q> request) 2 { 3 StandResponse<P> res = null; 4 StandResponse<string> response = null; 5 try 6 { 7 string result = (string)Invoker.MethodInvoke(instance, methodName, SerializeHelper.JsonSerialize2(request)); 8 response = SerializeHelper.JsonDeserialize2<StandResponse<string>>(result); 9 } 10 catch (IOException idEx) 11 { 12 throw new ThriftException(string.Format("請求超時。\r請求服務:{0}\r請求方法:{1}\r請求數據:{2}", serviceName, methodName, SerializeHelper.JsonSerialize2(request))); 13 } 14 catch (TTransportException transEx) 15 { 16 throw new ThriftException(string.Format("服務端未處理系統異常。\r請求服務:{0}\r請求方法:{1}\r請求數據:{2}", serviceName, methodName, SerializeHelper.JsonSerialize2(request))); 17 } 18 catch(Exception ex) 19 { 20 throw ex; 21 } 22 if (response != null && response.Code == "-1") 23 { 24 throw new ThriftException(string.Format("請求數據異常。\r請求服務:{0}\r請求方法:{1}\r請求數據:{2}", serviceName, methodName, SerializeHelper.JsonSerialize2(request))); 25 } 26 if (response != null && response.Code == "-2") 27 { 28 throw new ThriftException(string.Format("服務端系統異常。\r請求服務:{0}\r請求方法:{1}\r請求數據:{2}", serviceName, methodName, SerializeHelper.JsonSerialize2(request))); 29 } 30 if (response != null) 31 { 32 res = new StandResponse<P>() 33 { 34 Code = response.Code, 35 Desc = response.Desc, 36 Data = SerializeHelper.JsonDeserialize2<P>(response.Data) 37 }; 38 } 39 return res; 40 }
然後再對這個私有的方法進一步封裝,返回開發者看得懂的數據結構。
/// <summary> /// 無返回接口調用 /// </summary> /// <typeparam name="Q"></typeparam> /// <typeparam name="P"></typeparam> /// <param name="methodName"></param> /// <param name="request"></param> public void Invoke<Q>(string methodName, Q request) { Invoke<Q, string>(methodName, BuildRequest<Q>(request)); } /// <summary> /// 無參接口調用,服務端一定是有請求參數的,這裏的無參是指Service方法無參 /// </summary> /// <typeparam name="P"></typeparam> /// <param name="methodName"></param> /// <returns></returns> public P Invoke<P>(string methodName) { StandResponse<P> response = Invoke<string, P>(methodName, BuildRequest<string>(string.Empty)); if (response != null) { return response.Data; } return default(P); } public P Invoke<Q, P>(string methodName, Q request) { StandResponse<P> response = Invoke<Q, P>(methodName, BuildRequest<Q>(request)); if (response != null) { return response.Data; } return default(P); }
關於Tcp連接的歸還釋放,請自行下載源碼來看。
下一篇會用一個Demo來示範怎樣使用Thrift.Utility來快速暴露接口,讓客戶端和服務端通信。
Thrift微服務代碼下載Thrift.Utility