c# RPC框架的使用簡(jiǎn)介
寫(xiě)在前面:
RPC,聽(tīng)過(guò)很有段時(shí)間了,但是一直都不太清楚是干嘛的,今天我們來(lái)捋一捋。
解釋?zhuān)?br />
【Remote Procedure Call Protocol】遠(yuǎn)程過(guò)程調(diào)用(就是說(shuō),A程序要調(diào)用一個(gè)b方法,然而這個(gè)b方法的實(shí)現(xiàn)在B程序內(nèi)部,B程序還可能和A不在一個(gè)電腦上面,怎么調(diào)用?http可以調(diào)用/rpc也可以,讓他像調(diào)用本地方法一樣調(diào)用)
使用初探:
用了一下市面上的,rpc框架,步驟如下:
1、寫(xiě)一個(gè)基本的代碼,告訴有哪些方法。
2、然后服務(wù)端集成,
3、客戶(hù)端集成,
4、OK調(diào)用生效了。
感覺(jué)有點(diǎn)像TCP在傳輸數(shù)據(jù),從A服務(wù)器傳遞,傳遞類(lèi)名,方法名,參數(shù),值,然后B服務(wù)器拿到數(shù)據(jù),計(jì)算結(jié)果,然后把數(shù)據(jù)在回傳給A。。。這樣理解一下的話(huà),就很簡(jiǎn)單了。
下面動(dòng)手寫(xiě)一個(gè)吧。
自己動(dòng)手:
服務(wù)端:
既然服務(wù)端是實(shí)現(xiàn)的地方,我們寫(xiě)一個(gè)算是實(shí)現(xiàn)類(lèi)的方法試試:寫(xiě)了一個(gè)接口和一個(gè)實(shí)現(xiàn),為了演示效果,寫(xiě)了兩個(gè)方法。
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,服務(wù)端的大部分完成了。
然后就是TCP服務(wù)器,TCP服務(wù)器對(duì)大家來(lái)說(shuō),就太簡(jiǎn)單不過(guò)了,不就是創(chuàng)建一個(gè)Socket對(duì)象,綁定一個(gè)端口,獲取客戶(hù)端請(qǐng)求的Socket對(duì)象,然后和他交互么。沒(méi)啥多說(shuō)的。
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服務(wù)器已啟動(dòng)");
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 = ServiceHelpercs.Handle(buffer);
client.Send(data);
}
client.Shutdown(SocketShutdown.Both);
}
}
我們假定,所有的客戶(hù)端數(shù)據(jù),可以在一個(gè)請(qǐng)求包里面解析掉,因?yàn)槿绻淮蔚臄?shù)據(jù)接收不能解析,那就還要添加一個(gè)大小了,客戶(hù)端要告訴我你給我了多少消息,然后我再讀取指定數(shù)據(jù),拿到所有的內(nèi)容
這里創(chuàng)建,一個(gè)ServiceHelpers來(lái)幫助對(duì),真實(shí)算法的調(diào)用。如下:
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 = 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 = 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 = new List<byte>();
var str = (result == null ? "" : result.ToString());
var data = 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 = 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;
}
}
解析的類(lèi)很簡(jiǎn)單,因?yàn)檫@里創(chuàng)建的數(shù)據(jù)結(jié)構(gòu)很簡(jiǎn)單。

按照我們的約定,這里,對(duì)數(shù)據(jù)按照我定義的方式來(lái)進(jìn)行解包即可。
服務(wù)器就完成了,是不是很簡(jiǎn)單。當(dāng)然客戶(hù)端也需要按照一樣的方式處理打包即可
客戶(hù)端:
客戶(hù)端就很簡(jiǎn)單了,只需要連接到服務(wù)器,通過(guò)我們自動(dòng)生成的代碼(這里沒(méi)有寫(xiě)自動(dòng)生成,就手動(dòng)了),然后就直接可以返回結(jié)果了
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 + " 耗時(shí) " + min_seconds);
Console.ReadLine();
}
}
上面直接調(diào)用了,接口,至于接口的實(shí)現(xiàn),這里的步驟就三個(gè):1、構(gòu)造需要請(qǐng)求的數(shù)據(jù),2、連接服務(wù)器并發(fā)送數(shù)據(jù),3、接收返回內(nèi)容,并解析結(jié)果。
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 = 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("系統(tǒng)異常");
}
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 = 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("系統(tǒng)異常");
}
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 = Convert.ToBoolean(arg.value);
list.Add(value ? (byte)1 : (byte)0);
}
else if (arg.type == TypeEnu.String)
{
string value = 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 = 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 = value;
}
}
public enum TypeEnu
{
Void = 0,
Int = 1,
Bool = 2,
String = 3
}
接口的定義沿用服務(wù)端的即可。說(shuō)明一點(diǎn):MyServiceProxy這個(gè)類(lèi),這里我是手寫(xiě)的,真實(shí)的環(huán)境,這個(gè)類(lèi),應(yīng)該是由我們定義的某種格式,然后寫(xiě)一個(gè)代碼生成器,讓他自動(dòng)生成,然后就可以不用費(fèi)力,兼容所有的調(diào)用了,
當(dāng)然這里只支持了四種類(lèi)型,我們還可以擴(kuò)充更多類(lèi)型,只需要找到傳遞數(shù)據(jù)的方式即可。譬如一種對(duì)象,我們不知道如何傳遞,可以直接把對(duì)象定義成一個(gè)json字符串,或者序列化成二進(jìn)制,只要兩端,都知道了這個(gè)類(lèi)型,就可以了。
相當(dāng)于設(shè)計(jì)模式里面的(約定大于配置了)
知識(shí)點(diǎn)梳理
這里有一些知識(shí)點(diǎn),是不常用的,這里梳理出來(lái)了。
1、MemoryStream ms = new MemoryStream(buffer); BinaryReader br = new BinaryReader(ms); 通過(guò)binaryReader的方式,可以像C/C++指針一樣取數(shù)據(jù)
2、var types = Assembly.GetExecutingAssembly().GetTypes(); 通過(guò)Assembly可以得到當(dāng)前exe或者dll的所有類(lèi)型(類(lèi)接口都是一種類(lèi)型)
3、Object thisObj = Activator.CreateInstance(inter_type); 通過(guò)Activator調(diào)用默認(rèn)構(gòu)造,實(shí)現(xiàn)對(duì)象的初始化
總結(jié):
這樣一個(gè)rpc框架,本身并沒(méi)有優(yōu)化,還有很多地方是可以?xún)?yōu)化的,比如:緩存(不用每次遍歷查詢(xún)類(lèi)型等),udp支持(這里僅僅只是對(duì)tcp進(jìn)行了支持),
自動(dòng)代碼生成(定義一種規(guī)范和支持程序,進(jìn)行支持),錯(cuò)誤重試,數(shù)據(jù)唯一性,數(shù)據(jù)包的大小處理,等等,所以想要開(kāi)發(fā)一個(gè)易用的框架,還需要不斷演進(jìn),這里只是對(duì)他的原理進(jìn)行了簡(jiǎn)單剖析。
最后還原大家拍磚。。。。。動(dòng)起來(lái)
代碼:git:https://github.com/supperlitt/tcp_all
以上就是c# RPC框架的使用簡(jiǎn)介的詳細(xì)內(nèi)容,更多關(guān)于c# RPC框架的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
UGUI實(shí)現(xiàn)隨意調(diào)整Text中的字體間距
這篇文章主要為大家詳細(xì)介紹了UGUI實(shí)現(xiàn)隨意調(diào)整字體間距的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-03-03
c#實(shí)現(xiàn)用SQL池,多線程定時(shí)批量執(zhí)行SQL語(yǔ)句的方法
構(gòu)建SQL池,分離業(yè)務(wù)邏輯層和數(shù)據(jù)訪問(wèn)層,讓業(yè)務(wù)邏輯層從低效的數(shù)據(jù)庫(kù)操作解脫,以提高系統(tǒng)整體性能2013-10-10
C#中哈希表(HashTable)用法實(shí)例詳解(添加/移除/判斷/遍歷/排序等)
這篇文章主要介紹了C#中哈希表(HashTable)用法,簡(jiǎn)單講述了哈希表的原理并結(jié)合實(shí)例形式詳細(xì)分析了C#針對(duì)哈希表進(jìn)行添加、移除、判斷、遍歷、排序等操作的實(shí)現(xiàn)技巧,需要的朋友可以參考下2016-06-06
C#設(shè)置PDF表單不可編輯以及提取表單數(shù)據(jù)的操作
PDF表單是PDF中的可編輯區(qū)域,允許用戶(hù)填寫(xiě)指定信息,當(dāng)表單填寫(xiě)完成后,有時(shí)候我們可能需要將其設(shè)置為不可編輯,以保護(hù)表單內(nèi)容的完整性和可靠性,本文將給大家介紹C#設(shè)置PDF表單不可編輯以及提取表單數(shù)據(jù)的操作,需要的朋友可以參考下2024-06-06
C#中WPF顏色對(duì)話(huà)框控件的實(shí)現(xiàn)
在 C# WPF開(kāi)發(fā)中顏色對(duì)話(huà)框控件(ColorDialog)用于對(duì)界面中的背景、文字…(擁有顏色屬性的所有控件)設(shè)置顏色,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03

