寫在前面:
RPC,聽過很有段時間了,但是一直都不太清楚是干嘛的,今天我們來捋一捋,
解釋:
【Remote Procedure Call Protocol】遠程程序呼叫(就是說,A程式要呼叫一個b方法,然而這個b方法的實作在B程式內部,B程式還可能和A不在一個電腦上面,怎么呼叫?http可以呼叫/rpc也可以,讓他像呼叫本地方法一樣呼叫)
使用初探:
用了一下市面上的,rpc框架,步驟如下:
1、寫一個基本的代碼,告訴有哪些方法,
2、然后服務端集成,
3、客戶端集成,
4、OK呼叫生效了,
感覺有點像TCP在傳輸資料,從A服務器傳遞,傳遞類名,方法名,引數,值,然后B服務器拿到資料,計算結果,然后把資料在回傳給A,,,這樣理解一下的話,就很簡單了,
下面動手寫一個吧,
自己動手:
服務端:
既然服務端是實作的地方,我們寫一個算是實作類的方法試試:寫了一個介面和一個實作,為了演示效果,寫了兩個方法,
public interface IMyTestService { int calc(int x, int y); bool login(string name, string pwd); } public class MyTestServiceImpl : IMyTestService { public int calc(int x, int y) { return x + y; } public bool login(string name, string pwd) { if (name == "test" && pwd == "123456") { return true; } return false; } }
OK,服務端的大部分完成了,
然后就是TCP服務器,TCP服務器對大家來說,就太簡單不過了,不就是創建一個Socket物件,系結一個埠,獲取客戶端請求的Socket物件,然后和他互動么,沒啥多說的,
class Program { static void Main(string[] args) { Socket server = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); server.Bind(new IPEndPoint(IPAddress.Any, 10000)); server.Listen(1000); Thread t = new Thread(Execute); t.IsBackground = true; t.Start(server); Console.WriteLine("rpc服務器已啟動"); Console.ReadLine(); } private static void Execute(Object obj) { Socket server = obj as Socket; while (true) { Socket client = server.Accept(); Thread t = new Thread(SingleExecute); t.IsBackground = true; t.Start(client); } } private static void SingleExecute(object obj) { // 讀取 Socket client = obj as Socket; byte[] buffer = new byte[8192]; int count = client.Receive(buffer); if (count > 0) { var data =https://www.cnblogs.com/Supperlitt/archive/2021/02/04/ ServiceHelpercs.Handle(buffer); client.Send(data); } client.Shutdown(SocketShutdown.Both); } }
我們假定,所有的客戶端資料,可以在一個請求包里面決議掉,因為如果一次的資料接收不能決議,那就還要添加一個大小了,客戶端要告訴我你給我了多少訊息,然后我再讀取指定資料,拿到所有的內容
這里創建,一個ServiceHelpers來幫助對,真實演算法的呼叫,如下:
public class ServiceHelpercs { public static byte[] Handle(byte[] buffer) { MemoryStream ms = new MemoryStream(buffer); BinaryReader br = new BinaryReader(ms); int inter_len = br.ReadByte(); string inter_name = Encoding.UTF8.GetString(br.ReadBytes(inter_len)); int method_len = br.ReadByte(); string method_name = Encoding.UTF8.GetString(br.ReadBytes(method_len)); int args_length = br.ReadByte(); int return_type = br.ReadByte(); List<object> list = new List<object>(); for (int i = 0; i < args_length; i++) {
// 0:void 忽略 1:int 2:bool 3:string int arg_type = br.ReadByte(); if (arg_type == 1) { byte[] values = br.ReadBytes(4); list.Add(bytes2int(values)); } else if (arg_type == 2) { bool value = https://www.cnblogs.com/Supperlitt/archive/2021/02/04/br.ReadByte() == 1; list.Add(value); } else if (arg_type == 3) { int str_len = bytes2int(br.ReadBytes(4)); string str = Encoding.UTF8.GetString(br.ReadBytes(str_len)); list.Add(str); } } Type inter_type = null; var types = Assembly.GetExecutingAssembly().GetTypes(); foreach (var type in types) { var ts = type.GetInterfaces(); foreach (var t in ts) { if (t.Name == inter_name) { inter_type = type; break; } } } MethodInfo invokeMethod = null; if (inter_type != null) { var methods = inter_type.GetMethods(); foreach (var method in methods) { if (method.Name == method_name) { invokeMethod = method; break; } } } if (invokeMethod != null) { Object thisObj = Activator.CreateInstance(inter_type); object result = invokeMethod.Invoke(thisObj, list.ToArray()); if (return_type == 1) { int value =https://www.cnblogs.com/Supperlitt/archive/2021/02/04/ Convert.ToInt32(result); return int2bytes(value); } else if (return_type == 2) { return new byte[1] { Convert.ToBoolean(result) ? (byte)1 : (byte)0 }; } else if (return_type == 2) { List<byte> result_data = https://www.cnblogs.com/Supperlitt/archive/2021/02/04/new List<byte>(); var str = (result == null ? "" : result.ToString()); var data =https://www.cnblogs.com/Supperlitt/archive/2021/02/04/ Encoding.UTF8.GetBytes(str); result_data.AddRange(int2bytes(data.Length)); result_data.AddRange(data); return result_data.ToArray(); } } return new byte[1] { 0xFF }; } public static byte[] int2bytes(int len) { byte[] data_len = new byte[4]; data_len[0] = (byte)((len >> 8 * 3) & 0xFF); data_len[1] = (byte)((len >> 8 * 2) & 0xFF); data_len[2] = (byte)((len >> 8 * 1) & 0xFF); data_len[3] = (byte)(len & 0xFF); return data_len; } public static int bytes2int(byte[] buffer) { int value = https://www.cnblogs.com/Supperlitt/archive/2021/02/04/0; value += (int)(buffer[0] << (8 * 3)); value += (int)(buffer[1] << (8 * 2)); value += (int)(buffer[2] << (8 * 1)); value += (int)(buffer[3]); return value; } }
決議的類很簡單,因為這里創建的資料結構很簡單,

按照我們的約定,這里,對資料按照我定義的方式來進行解包即可,
服務器就完成了,是不是很簡單,當然客戶端也需要按照一樣的方式處理打包即可
客戶端:
客戶端就很簡單了,只需要連接到服務器,通過我們自動生成的代碼(這里沒有寫自動生成,就手動了),然后就直接可以回傳結果了
class Program { static void Main(string[] args) { IMyService service = new MyServiceProxy(); DateTime startTime = DateTime.Now; int result = service.add(123, 321); int min_seconds = (int)(DateTime.Now - startTime).TotalMilliseconds; Console.WriteLine(result + " 耗時 " + min_seconds); Console.ReadLine(); } }
上面直接呼叫了,介面,至于介面的實作,這里的步驟就三個:1、構造需要請求的資料,2、連接服務器并發送資料,3、接識訓傳內容,并決議結果,
public class MyServiceProxy : IMyService { public int add(int x, int y) { List<ArgInfo> argList = new List<ArgInfo>(); argList.Add(new ArgInfo(TypeEnu.Int, x)); argList.Add(new ArgInfo(TypeEnu.Int, y)); byte[] send_data = https://www.cnblogs.com/Supperlitt/archive/2021/02/04/create_send_package("IMyService", "add", 2, TypeEnu.Int, argList); Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); client.Connect(new IPEndPoint(IPAddress.Parse("192.168.0.105"), 10000)); client.Send(send_data); byte[] buffer = new byte[4]; int count = client.Receive(buffer); if (count > 0) { return bytes2int(buffer); } throw new Exception("系統例外"); } public bool login(string name, string pwd) { List<ArgInfo> argList = new List<ArgInfo>(); argList.Add(new ArgInfo(TypeEnu.String, name)); argList.Add(new ArgInfo(TypeEnu.String, pwd)); byte[] send_data = https://www.cnblogs.com/Supperlitt/archive/2021/02/04/create_send_package("IMyService", "login", 2, TypeEnu.Bool, argList); Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); client.Connect(new IPEndPoint(IPAddress.Parse("192.168.0.105"), 10000)); client.Send(send_data); byte[] buffer = new byte[1]; int count = client.Receive(buffer); if (count > 0) { return buffer[0] == 1; } throw new Exception("系統例外"); } private byte[] create_send_package(string inter_name, string method_name, int arg_length, TypeEnu return_type, List<ArgInfo> argList) { List<byte> list = new List<byte>(); list.Add((byte)inter_name.Length); list.AddRange(Encoding.UTF8.GetBytes(inter_name)); list.Add((byte)method_name.Length); list.AddRange(Encoding.UTF8.GetBytes(method_name)); list.Add((byte)arg_length); list.Add((byte)return_type); foreach (var arg in argList) { list.Add((byte)arg.type); if (arg.type == TypeEnu.Int) { list.AddRange(int2bytes(Convert.ToInt32(arg.value))); } else if (arg.type == TypeEnu.Bool) { bool value =https://www.cnblogs.com/Supperlitt/archive/2021/02/04/ Convert.ToBoolean(arg.value); list.Add(value ? (byte)1 : (byte)0); } else if (arg.type == TypeEnu.String) { string value =https://www.cnblogs.com/Supperlitt/archive/2021/02/04/ arg.value.ToString(); list.AddRange(int2bytes(value.Length)); list.AddRange(Encoding.UTF8.GetBytes(value)); } } return list.ToArray(); } public byte[] int2bytes(int len) { byte[] data_len = new byte[4]; data_len[0] = (byte)((len >> 8 * 3) & 0xFF); data_len[1] = (byte)((len >> 8 * 2) & 0xFF); data_len[2] = (byte)((len >> 8 * 1) & 0xFF); data_len[3] = (byte)(len & 0xFF); return data_len; } public int bytes2int(byte[] buffer) { int value = https://www.cnblogs.com/Supperlitt/archive/2021/02/04/0; value += (int)(buffer[0] << (8 * 3)); value += (int)(buffer[1] << (8 * 2)); value += (int)(buffer[2] << (8 * 1)); value += (int)(buffer[3]); return value; } } public class ArgInfo { public TypeEnu type { get; set; } public object value { get; set; } public ArgInfo(TypeEnu type, object value) { this.type = type; this.value =https://www.cnblogs.com/Supperlitt/archive/2021/02/04/ value; } } public enum TypeEnu { Void = 0, Int = 1, Bool = 2, String = 3 }
介面的定義沿用服務端的即可,說明一點:MyServiceProxy這個類,這里我是手寫的,真實的環境,這個類,應該是由我們定義的某種格式,然后寫一個代碼生成器,讓他自動生成,然后就可以不用費力,兼容所有的呼叫了,
當然這里只支持了四種型別,我們還可以擴充更多型別,只需要找到傳遞資料的方式即可,譬如一種物件,我們不知道如何傳遞,可以直接把物件定義成一個json字串,或者序列化成二進制,只要兩端,都知道了這個型別,就可以了,
相當于設計模式里面的(約定大于配置了)
知識點梳理
這里有一些知識點,是不常用的,這里梳理出來了,
1、MemoryStream ms = new MemoryStream(buffer); BinaryReader br = new BinaryReader(ms); 通過binaryReader的方式,可以像C/C++指標一樣取資料
2、var types = Assembly.GetExecutingAssembly().GetTypes(); 通過Assembly可以得到當前exe或者dll的所有型別(類介面都是一種型別)
3、Object thisObj = Activator.CreateInstance(inter_type); 通過Activator呼叫默認構造,實作物件的初始化
總結:
這樣一個rpc框架,本身并沒有優化,還有很多地方是可以優化的,比如:快取(不用每次遍歷查詢型別等),udp支持(這里僅僅只是對tcp進行了支持),
自動代碼生成(定義一種規范和支持程式,進行支持),錯誤重試,資料唯一性,資料包的大小處理,等等,所以想要開發一個易用的框架,還需要不斷演進,這里只是對他的原理進行了簡單剖析,
最后還原大家拍磚,,,,,動起來
代碼:git:https://github.com/supperlitt/tcp_all
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/256580.html
標籤:.NET技术
