[toc] # 簡介 - 高性能、開源的通用 RPC 框架 - 實現不同語言相互調用 - [官網](https://grpc.io/) - [Protobuf 消息參考](https://learn.microsoft.com/zh-cn/aspnet/core/grpc/protobuf?view ...
目錄
簡介
-
高性能、開源的通用 RPC 框架
-
實現不同語言相互調用
創建gRPC
創建服務端
- vs2022直接搜索grpc預設下一步創建
創建控制台測試
- 創建控制台
- 引入以下dll
<PackageReference Include="Google.Protobuf" Version="3.23.4" />
<PackageReference Include="Grpc.Net.Client" Version="2.55.0" />
<PackageReference Include="Grpc.Tools" Version="2.56.2">
- 打開服務端.csproj文件,複製以下內容粘貼到客戶端的.csproj文件中並修改GrpcServices=Client,客戶端.csproj文件出現 None Update="Protos\greet.proto" 這組ItemGroup是可以刪除的
<ItemGroup>
<Protobuf Include="Protos\greet.proto" GrpcServices="Client" />
</ItemGroup>
4.將服務端Protos文件夾及內容全部拷貝到客戶端項目下
5.在客戶端創建gRpcRequest.cs文件並增加下列代碼,埠號填寫服務端埠
using Grpc.Net.Client;
using GrpcService;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using static GrpcService.Greeter;
namespace gRpcConsoleTest
{
public static class gRpcRequest
{
public static async Task SayHello()
{
using(var channel = GrpcChannel.ForAddress("https://localhost:7166"))
{
GreeterClient client = new GreeterClient(channel);
HelloReply reply = await client.SayHelloAsync(new HelloRequest() {Name = "jjjjj" });
Console.WriteLine(reply.Message);
}
}
}
}
6.客戶端Program.cs文件中加入測試代碼 await gRpcRequest.SayHello();
7.服務端和客戶端在想要調試的位置打上斷點
8.運行服務端
9.選中客戶端項目右鍵 -> 調試 -> 啟動新實例 即可兩個項目全部命中斷點進行調試測試
創建自定義服務
-
服務端
- Protos文件夾添加 custom.proto文件並添加下列代碼,重新生成項目打開項目所在文件夾,打開路徑:obj\Debug\net6.0\Protos 查看CustomGrpc.cs是否存在,如果沒有存在則在.csproj文件中添加:
syntax = "proto3"; option csharp_namespace = "Custom.Service"; package custom; service CustomGreeter { rpc Plus(Number) returns (NumberResult) ; } message Number { int32 leftNumber = 1; int32 rightNumber = 2; } message NumberResult{ int32 result = 1; }
- Services文件夾下添加CustomGreeterService.cs文件,namespace 與 .protos中的csharp_namespace對應
using Grpc.Core; namespace Custom.Service { public class CustomGreeterService : CustomGreeter.CustomGreeterBase { public override async Task<NumberResult> Plus(Number request, ServerCallContext context) => await Task.FromResult<NumberResult>(new NumberResult() { Result = request.LeftNumber + request.RightNumber, }); } }
3.在Program.cs 中註冊新創建的服務,加入下列代碼:
app.MapGrpcService(); - Protos文件夾添加 custom.proto文件並添加下列代碼,重新生成項目打開項目所在文件夾,打開路徑:obj\Debug\net6.0\Protos 查看CustomGrpc.cs是否存在,如果沒有存在則在.csproj文件中添加:
-
客戶端
- 將服務端的custom.proto文件拷貝到Protos文件夾內併在.csproj文件中添加(註意這裡GrpcServices="Client"):
- 重新生成項目並檢查路徑: obj\Debug\net6.0\Protos 是否生成對應的CustomGrpc.cs文件
- 加入測試代碼:
public static async Task Plus(int leftNumber,int rightNumber) { using (var channel = GrpcChannel.ForAddress("https://localhost:7166")) { CustomGreeterClient client = new CustomGreeterClient(channel); NumberResult number = await client.PlusAsync(new Custom.Service.Number() { LeftNumber = leftNumber,RightNumber = rightNumber}); Console.WriteLine(number.Result); } }
- 測試步驟與上面控制台測試一樣,調試方式也一樣
- 將服務端的custom.proto文件拷貝到Protos文件夾內併在.csproj文件中添加(註意這裡GrpcServices="Client"):
伺服器流式處理方法
custom.proto
syntax = "proto3";
option csharp_namespace = "Custom.Service";
package custom;
service CustomGreeter {
rpc SelfIncreaseServer(IntArrayModel) returns (stream BathTheCatResp); //服務端流
}
message BathTheCatResp{
string message = 1;
}
message IntArrayModel{
repeated int32 number = 1;
}
CustomGreeterService.cs
using Grpc.Core;
namespace Custom.Service
{
public class CustomGreeterService : CustomGreeter.CustomGreeterBase
{
public override async Task SelfIncreaseServer(IntArrayModel request, IServerStreamWriter<BathTheCatResp> responseStream, ServerCallContext context)
{
foreach (var item in request.Number)
{
Console.WriteLine($"客戶端傳入參數: {item}");
await responseStream.WriteAsync(new BathTheCatResp() { Message = item.ToString()});
await Task.Delay(1000);
}
}
}
}
gRpcRequest.cs
using Custom.Service;
using Grpc.Core;
using Grpc.Net.Client;
using GrpcService;
using static Custom.Service.CustomGreeter;
namespace gRpcConsoleTest
{
public static class gRpcRequest
{
public static async Task SelfIncreaseServe()
{
using (var channel = GrpcChannel.ForAddress("https://localhost:7166"))
{
CustomGreeterClient client = new CustomGreeterClient(channel);
IntArrayModel intArray = new IntArrayModel();
for (int i = 0; i < 10; i++)
{
intArray.Number.Add(i);
}
var batch = client.SelfIncreaseServer(intArray);
Task batchTask = Task.Run(async ()=>
{
await foreach (var item in batch.ResponseStream.ReadAllAsync())
{
Console.WriteLine($"服務端相應數據: {item.Message}");
}
});
await batchTask;
}
}
}
}
客戶端流式處理方法
custom.proto
syntax = "proto3";
option csharp_namespace = "Custom.Service";
package custom;
service CustomGreeter {
rpc SelfIncreaseClient(stream BathTheCatReq) returns (IntArrayModel); //客戶端流
}
message BathTheCatResp{
string message = 1;
}
message IntArrayModel{
repeated int32 number = 1;
}
CustomGreeterService.cs
using Grpc.Core;
namespace Custom.Service
{
public class CustomGreeterService : CustomGreeter.CustomGreeterBase
{
public override async Task<IntArrayModel> SelfIncreaseClient(IAsyncStreamReader<BathTheCatReq> requestStream, ServerCallContext context)
{
IntArrayModel result = new IntArrayModel();
while (await requestStream.MoveNext())
{
var message = requestStream.Current;
Console.WriteLine($"客戶端流傳入消息: {message}");
result.Number.Add(message.Id + 1);
}
return result;
}
}
}
gRpcRequest.cs
using Custom.Service;
using Grpc.Core;
using Grpc.Net.Client;
using GrpcService;
using static Custom.Service.CustomGreeter;
namespace gRpcConsoleTest
{
public static class gRpcRequest
{
public static async Task SelfIncreaseClient()
{
using (var channel = GrpcChannel.ForAddress("https://localhost:7166"))
{
CustomGreeterClient client = new CustomGreeterClient(channel);
var batch = client.SelfIncreaseClient();
for (int i = 0; i < 10; i++)
{
await batch.RequestStream.WriteAsync(new BathTheCatReq() { Id = i });
await Task.Delay(1000);
}
await batch.RequestStream.CompleteAsync();
foreach (var item in batch.ResponseAsync.Result.Number)
{
Console.WriteLine($"響應數據: {item}");
}
}
}
}
}
雙向流式處理方法
custom.proto
syntax = "proto3";
option csharp_namespace = "Custom.Service";
package custom;
service CustomGreeter {
rpc SelfIncreaseDouble(stream BathTheCatReq) returns (stream BathTheCatResp);//雙端流
}
message BathTheCatReq{
int32 id = 1;
}
message BathTheCatResp{
string message = 1;
}
CustomGreeterService.cs
using Grpc.Core;
namespace Custom.Service
{
public class CustomGreeterService : CustomGreeter.CustomGreeterBase
{
public override async Task SelfIncreaseDouble(IAsyncStreamReader<BathTheCatReq> requestStream, IServerStreamWriter<BathTheCatResp> responseStream, ServerCallContext context)
{
while (await requestStream.MoveNext())
{
var message = requestStream.Current.Id;
Console.WriteLine($"客戶端流傳入消息: {message}");
await responseStream.WriteAsync(new BathTheCatResp() { Message=(message+1).ToString()});
}
}
}
}
gRpcRequest.cs
using Custom.Service;
using Grpc.Core;
using Grpc.Net.Client;
using GrpcService;
using static Custom.Service.CustomGreeter;
using static GrpcService.Greeter;
namespace gRpcConsoleTest
{
public static class gRpcRequest
{
public static async Task SelfIncreaseDouble()
{
using (var channel = GrpcChannel.ForAddress("https://localhost:7166"))
{
CustomGreeterClient client = new CustomGreeterClient(channel);
var batch = client.SelfIncreaseDouble();
Task batchTask = Task.Run(async () =>
{
await foreach (var item in batch.ResponseStream.ReadAllAsync())
{
Console.WriteLine($"服務端相應數據: {item.Message}");
}
});
for (int i = 0; i < 10; i++)
{
await batch.RequestStream.WriteAsync(new BathTheCatReq() { Id = i });
await Task.Delay(1000);
}
await batchTask;
}
}
}
}
.Net Core 調用gRpc
項目引用
<PackageReference Include="Google.Protobuf" Version="3.24.0" />
<PackageReference Include="Grpc.Net.Client" Version="2.55.0" />
<PackageReference Include="Grpc.Net.ClientFactory" Version="2.55.0" />
<PackageReference Include="Grpc.Tools" Version="2.56.2">
<ItemGroup>
<Protobuf Include="Protos\custom.proto" GrpcServices="Client" />
<Protobuf Include="Protos\greet.proto" GrpcServices="Client" />
</ItemGroup>
Program.cs
//CustomGreeterClient grpc連接類
builder.Services.AddGrpcClient<CustomGreeterClient>(options =>
{
options.Address = new Uri("https://localhost:7166"); //grpc 服務地址
});
gRpcController.cs
using Custom.Service;
using Microsoft.AspNetCore.Mvc;
using static Custom.Service.CustomGreeter;
namespace gRpcWebAPI.Controllers
{
[Route("api/[controller]/[action]")]
[ApiController]
public class gRpcController : ControllerBase
{
CustomGreeterClient _client; //使用構造函數註入
public gRpcController(CustomGreeterClient client)
{
_client = client;
}
[HttpGet]
public async Task<IActionResult> Plus(int leftNumber, int rightNumber)
{
NumberResult number = await _client.PlusAsync(new Custom.Service.Number() { LeftNumber = leftNumber, RightNumber = rightNumber });
return new JsonResult(number);
}
}
}
支持Aop
- 服務端, 客戶端 都需要繼承 Interceptor
- 重新需要實現Aop的方法,如服務端: UnaryServerHandler,客戶端: AsyncUnaryCall 針對一元調用的Aop
- 我這裡使用的是NLog寫的日誌,NLog可以使用可以翻閱我先前的博客
.Net Core NLog+oracel
服務端 Program.cs
builder.Services.AddGrpc(options =>
{
options.Interceptors.Add<LogInterceptor>();
});
服務端 LogInterceptor.cs
using Grpc.Core;
using Grpc.Core.Interceptors;
namespace GrpcService.Interceptors
{
public class LogInterceptor : Interceptor
{
ILogger<LogInterceptor> _logger;
public LogInterceptor(ILogger<LogInterceptor> logger)
{
_logger = logger;
}
public override Task<TResponse> UnaryServerHandler<TRequest, TResponse>(TRequest request, ServerCallContext context, UnaryServerMethod<TRequest, TResponse> continuation)
{
_logger.LogInformation("===========UnaryServerHandler==========");
return continuation(request, context);
}
}
}
客戶端 Program.cs
builder.Services.AddGrpcClient<CustomGreeterClient>(options =>
{
options.Address = new Uri("https://localhost:7166"); //服務端地址
}).AddInterceptor<LogInterceptor>();
客戶端 LogInterceptor.cs
using Grpc.Core;
using Grpc.Core.Interceptors;
namespace GrpcService.Interceptors
{
public class LogInterceptor : Interceptor
{
ILogger<LogInterceptor> _logger;
public LogInterceptor(ILogger<LogInterceptor> logger)
{
_logger = logger;
}
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(TRequest request, ClientInterceptorContext<TRequest, TResponse> context, AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
{
_logger.LogInformation("===========AsyncUnaryCall===========");
return continuation(request, context);
}
}
}
jwt+gRPC驗證
- 準備單獨的一個網站發佈jwt Token (授權中心)
- 然後在gRPC項目中jwt鑒權 和 獲取到角色信息之後授權
- webapi測試gRPC調用, 先在授權中心獲取token然後在 ConfigureChannel 方法中設置gRPC全局的jwt token
- 返回401: jwt鑒權不通過
- 返回403: jwt授權不通過
準備Jwt Token發佈中心
Program.cs
//讀取Jwt配置
builder.Services.Configure<JwtConfig>(builder.Configuration.GetSection("JwtTokenOptions"));
JwtConfig.cs
namespace AuthenorizationCenter.Tools.Model
{
public class JwtConfig
{
public string? Audience { get; set; }
public string? Issuer { get; set; }
public string? SecurityKey { get; set; }
public int ExpiresMinutes { get; set; }
}
}
AuthenorizationController.cs
using AuthenorizationCenter.Tools;
using AuthenorizationCenter.Tools.Model;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
namespace AuthenorizationCenter.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class AuthenorizationController : ControllerBase
{
JwtConfig _jwtconfig;
public AuthenorizationController(IOptions<JwtConfig> jwtconfig)
{
_jwtconfig = jwtconfig.Value;
}
[HttpGet]
public async Task<string> GetToken(string userName, string passWord)
{
string token = JwtHeleper.GetToken(new()
{
UserName = userName,
Extended1 = "無信息",
Role = new List<RoleInfo>()
{
new RoleInfo() { Id = "1",Role="系統管理員"} ,
new RoleInfo() { Id = "2",Role="用戶管理員"} ,
}
}, new()
{
Audience = _jwtconfig.Audience,
Issuer = _jwtconfig.Issuer,
SecurityKey = _jwtconfig.SecurityKey,
ExpiresMinutes = 5,
});
await Task.CompletedTask;
return token;
}
}
}
JwtHeleper.cs
using AuthenorizationCenter.Tools.Model;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
namespace AuthenorizationCenter.Tools
{
public class JwtHeleper
{
public static string GetToken(UserInfo user, JwtConfig jwtConfig)
{
List<Claim> claims = new List<Claim>
{
new Claim(ClaimTypes.Name, user.UserName ?? ""),
new Claim("Extended1", user.Extended1 ?? ""),
new Claim("Extended2", user.Extended2 ?? ""),
new Claim("Extended3", user.Extended3 ?? ""),
new Claim("Extended4", user.Extended4 ?? ""),
new Claim("Extended5", user.Extended5 ?? ""),
};
if (user.Role is not null)
{
foreach (var item in user.Role)
{
claims.Add(new Claim(item.Id.ToString(), item.Role));
}
}
if (jwtConfig.SecurityKey == null)
{
throw new Exception("JwtConfig.SecurityKey 不能為空");
}
SymmetricSecurityKey key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtConfig.SecurityKey));
SigningCredentials creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256);
JwtSecurityToken token = new JwtSecurityToken(
issuer: jwtConfig.Issuer,
audience: jwtConfig.Audience,
claims: claims,
expires: DateTime.UtcNow.AddMinutes(jwtConfig.ExpiresMinutes),
signingCredentials: creds
);
string resultToken = new JwtSecurityTokenHandler().WriteToken(token);
return resultToken;
}
}
}
RoleInfo.cs
namespace AuthenorizationCenter.Tools.Model
{
public class RoleInfo
{
public string Id { get; set; }
public string Role { get; set; }
}
}
UserInfo.cs
namespace AuthenorizationCenter.Tools.Model
{
public class UserInfo
{
public string? UserName { get; set; }
public List<RoleInfo>? Role { get; set; }
public string? Extended1 { get; set; }
public string? Extended2 { get; set; }
public string? Extended3 { get; set; }
public string? Extended4 { get; set; }
public string? Extended5 { get; set; }
}
}
appsetting.json
{
"JwtTokenOptions": {
"Issuer": "https://localhost:7117",
"Audience": "https://localhost:7117",
"SecurityKey": "kq4DY5N1eFJhscOkI7Zp4Nd0WNy9d9AEsN6Yjgdv9OxLyol66tzGBKT_7vwolN7GZ8EDwqJBwccjDJfb81ws5s3sbbP5wUzQ3-PcTSsD-Rueiu2rsOUZwg_NR3RBCwmtouV-832YV2trCjNTawLB1z0LMukWGFNaAJVZ8WdQcrYn6a0ko5oVhZqaHBgsCLEGiqPtoFsiCcrJTz1IvXHk9_cDSr2hwEmSl18GlkOtgCHFH8aidYth3aQHRHuClTi6Y9mYRJtqqK-FNQYq4ZP23DSGZGFejJFTnM9YMpppuTMLklhSGySwX8rfjZ_0L5ac18nHaykTaiC2fvH00W42qQ"
}
}
gRPC準備
Program.cs
JwtConfig jwtConfig = new JwtConfig();
builder.Configuration.Bind("JwtTokenOptions", jwtConfig);
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuer = true,
ValidIssuer = jwtConfig.Issuer, //發行人
ValidateAudience = true,
ValidAudience = jwtConfig.Audience,//訂閱人
ValidateIssuerSigningKey = true,
//對稱加密密鑰
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(jwtConfig.SecurityKey!)),
ValidateLifetime = true, //驗證失效時間
ClockSkew = TimeSpan.FromSeconds(30), //過期時間容錯值
RequireExpirationTime = true,
AudienceValidator = (audiences, securityToken, validationParameters) =>
{
return true;
},
LifetimeValidator = (notBefore, expires, securityToken, validationParameters) =>
{
return true;
}
};
});
builder.Services.AddTransient<IUserServices, UserServices>();
builder.Services.AddTransient<IAuthorizationHandler, JwtAuthorization>();
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("JwtPolicy", policy =>
{
//jwt 授權
policy.AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme)
//這裡為自定義授權指定一下類
.AddRequirements(new UserRoleRequirement(JwtBearerDefaults.AuthenticationScheme));
});
});
CustomGreeterService.cs
using Grpc.Core;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
namespace Custom.Service
{
public class CustomGreeterService : CustomGreeter.CustomGreeterBase
{
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme,Policy = "JwtPolicy")]
public override async Task<NumberResult> Plus(Number request, ServerCallContext context) =>
await Task.FromResult<NumberResult>(new NumberResult()
{
Result = request.LeftNumber + request.RightNumber,
});
}
}
JwtAuthorization.cs
using Cnpc.Com.Ioc.IBll;
using Microsoft.AspNetCore.Authorization;
using System.Security.Claims;
namespace GrpcService.Authorization
{
public class UserRoleRequirement : IAuthorizationRequirement
{
public string AuthenticateScheme;
public UserRoleRequirement(string authenticateScheme)
{
AuthenticateScheme = authenticateScheme;
}
}
public class JwtAuthorization : AuthorizationHandler<UserRoleRequirement>
{
IUserServices userSercices;
public JwtAuthorization(IUserServices userSercices)
{
this.userSercices = userSercices;
}
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, UserRoleRequirement requirement)
{
string? userName = context.User.FindFirst(it => it.Type == ClaimTypes.Name)?.Value;
if (userSercices.IsAdmin(userName!))
{
context.Succeed(requirement);
}
else
{
context.Fail();
}
return Task.CompletedTask;
}
}
}
客戶端調用
Program.cs
builder.Services.AddGrpcClient<CustomGreeterClient>(options =>
{
options.Address = new Uri("https://localhost:7166");
}).AddInterceptor<LogInterceptor>().ConfigureChannel(async config =>
{
//所有調用自動添加 Authorization
CallCredentials credentials = CallCredentials.FromInterceptor(async (context, metadata) =>
{
string token = await HttpClientHelper.HttpGetAsync("https://localhost:7117/api/Authenorization?userName=admin&passWord=666");
metadata.Add("Authorization", $"Bearer {token}");
});
config.Credentials = ChannelCredentials.Create(new SslCredentials(), credentials);
});
HttpClientHelper.cs
using Newtonsoft.Json;
using System.Text;
namespace gRpcWebAPI.Utility
{
public static class HttpClientHelper
{
public static async Task<string> HttpGetAsync(string url, string contentType = "application/json", Dictionary<string, string> headers = null)
{
using (System.Net.Http.HttpClient client = new System.Net.Http.HttpClient())
{
if (contentType != null)
client.DefaultRequestHeaders.Add("ContentType", contentType);
if (headers != null)
{
foreach (var header in headers)
client.DefaultRequestHeaders.Add(header.Key, header.Value);
}
HttpResponseMessage response = await client.GetAsync(url);
return await response.Content.ReadAsStringAsync();
}
}
}
}
gRpcController.cs
using Custom.Service;
using Grpc.Core;
using gRpcWebAPI.Utility;
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
using System.Security.Cryptography.X509Certificates;
using static Custom.Service.CustomGreeter;
namespace gRpcWebAPI.Controllers
{
[Route("api/[controller]/[action]")]
[ApiController]
public class gRpcController : ControllerBase
{
CustomGreeterClient _client;
public gRpcController(CustomGreeterClient client)
{
_client = client;
}
[HttpGet]
public async Task<IActionResult> AsyncPlus(int leftNumber, int rightNumber)
{
try
{
//string token = await HttpClientHelper.HttpGetAsync("https://localhost:7117/api/Authenorization?userName=admin&passWord=666");
//Metadata jwtCode = new Metadata { { "Authorization", $"Bearer {token}" } };
NumberResult number = await _client.PlusAsync(new Custom.Service.Number() { LeftNumber = leftNumber, RightNumber = rightNumber });
return new JsonResult(number);
}
catch (Exception ex)
{
return new JsonResult(ex.Message);
}
}
[HttpGet]
public IActionResult Plus(int leftNumber, int rightNumber)
{
try
{
string token = HttpClientHelper.HttpGet("https://localhost:7117/api/Authenorization?userName=admin&passWord=666");
Metadata jwtCode = new Metadata { { "Authorization",$"Bearer {token}"} };
NumberResult number = _client.Plus(new Custom.Service.Number() { LeftNumber = leftNumber, RightNumber = rightNumber },headers: jwtCode);
return new JsonResult(number);
}
catch (Exception ex)
{
return new JsonResult(ex.Message);
}
}
}
}
Rpc 與 Restful 區別
-
RPC是以一種調用本地方法的思路來調用遠程方法,通過各種RPC框架隱藏調用遠程方法的細節,讓用戶以為調用的就是本地方法。RPC隱藏了底層網路通信的複雜度,讓我們更專註於業務邏輯的開發。
-
REST通過HTTP實現,把用戶的需求抽象成對資源的操作,用戶必須通過HTTP協議的GET、HEAD、POST、PUT、DELETE、TRACE、OPTIONS七種基本操作去和伺服器交互。
-
RPC通常是伺服器和伺服器之間的通信,比如和中間件的通信,MQ、分散式緩存、分散式資料庫等等。
-
而REST通常是面向客戶端的(一般是瀏覽器),他們的使用場景也是不一樣的。