ASP.NET Core内gRPC服务间通信及跨进程请求转发方案实现咨询
方案可行性与实现步骤
这个方案完全可行。Windows服务默认运行在会话0,无法访问用户会话的桌面环境,通过同机用户会话中的代理端执行截图操作是解决这类问题的标准思路,结合gRPC双向流式通信可以完美实现请求转发与结果回传的闭环。
一、定义gRPC服务契约
首先编写.proto文件,定义两个独立的gRPC服务(客户端-服务端、代理端-服务端),以及请求/响应消息:
syntax = "proto3"; option csharp_namespace = "ScreenCaptureGrpc"; // 客户端与服务端通信的服务 service ClientScreenService { rpc CaptureScreen (stream CaptureRequest) returns (stream CaptureResponse); } // 服务端与代理端通信的服务 service AgentScreenService { rpc HandleCapture (stream CaptureRequest) returns (stream CaptureResponse); } message CaptureRequest { string request_id = 1; // 用于关联请求与结果的唯一标识 int32 quality = 2; // 截图质量参数 string area = 3; // 截图区域(如"FullScreen"或自定义坐标) } message CaptureResponse { string request_id = 1; // 关联对应请求的ID bytes image_data = 2; // 截图字节数据 string status = 3; // 请求状态(成功/失败信息) }
在各.NET项目中添加该.proto文件,通过Grpc.Tools自动生成对应的C#服务与消息类。
二、服务端(ASP.NET Core gRPC Windows服务)实现
1. 项目基础配置
创建ASP.NET Core gRPC项目,安装Microsoft.Extensions.Hosting.WindowsServices包,将项目配置为Windows服务:
var builder = WebApplication.CreateBuilder(args); builder.Services.AddGrpc(); // 注册代理端连接管理器,维护活跃的代理端流 builder.Services.AddSingleton<AgentConnectionManager>(); // 启用Windows服务托管 builder.Host.UseWindowsService(); var app = builder.Build(); // 映射两个gRPC服务 app.MapGrpcService<ClientScreenServiceImpl>(); app.MapGrpcService<AgentScreenServiceImpl>(); app.Run();
2. 代理端连接管理器
实现一个单例类,用于保存代理端的双向流连接,方便服务端转发请求:
public class AgentConnectionManager { private readonly ConcurrentDictionary<string, IServerStreamWriter<CaptureRequest>> _agentStreams = new(); public void RegisterAgent(string agentId, IServerStreamWriter<CaptureRequest> stream) { _agentStreams.TryAdd(agentId, stream); } public IServerStreamWriter<CaptureRequest>? GetActiveAgentStream() { // 取第一个活跃的代理端,可根据需求扩展为多代理端调度 return _agentStreams.Values.FirstOrDefault(); } public void UnregisterAgent(string agentId) { _agentStreams.TryRemove(agentId, out _); } }
3. 实现代理端通信服务
处理代理端的长连接,接收代理端返回的截图结果,并回传给对应的客户端:
public class AgentScreenServiceImpl : AgentScreenService.AgentScreenServiceBase { private readonly AgentConnectionManager _connectionManager; private readonly ILogger<AgentScreenServiceImpl> _logger; // 保存客户端流与请求ID的映射,用于结果回传 private readonly ConcurrentDictionary<string, IServerStreamWriter<CaptureResponse>> _clientStreamMap = new(); public AgentScreenServiceImpl(AgentConnectionManager connectionManager, ILogger<AgentScreenServiceImpl> logger) { _connectionManager = connectionManager; _logger = logger; } public override async Task HandleCapture(IAsyncStreamReader<CaptureResponse> requestStream, IServerStreamWriter<CaptureRequest> responseStream, ServerCallContext context) { var agentId = context.GetHttpContext().Connection.Id; _connectionManager.RegisterAgent(agentId, responseStream); _logger.LogInformation("代理端[{AgentId}]已连接", agentId); try { // 监听代理端返回的截图结果 await foreach (var response in requestStream.ReadAllAsync()) { if (_clientStreamMap.TryGetValue(response.RequestId, out var clientStream)) { await clientStream.WriteAsync(response); _clientStreamMap.TryRemove(response.RequestId, out _); } } } catch (Exception ex) { _logger.LogError(ex, "代理端[{AgentId}]连接异常", agentId); } finally { _connectionManager.UnregisterAgent(agentId); _logger.LogInformation("代理端[{AgentId}]已断开", agentId); } } }
4. 实现客户端通信服务
接收客户端的截图请求,转发给代理端,并等待结果回传:
public class ClientScreenServiceImpl : ClientScreenService.ClientScreenServiceBase { private readonly AgentConnectionManager _connectionManager; private readonly ILogger<ClientScreenServiceImpl> _logger; private readonly ConcurrentDictionary<string, IServerStreamWriter<CaptureResponse>> _clientStreamMap = new(); public ClientScreenServiceImpl(AgentConnectionManager connectionManager, ILogger<ClientScreenServiceImpl> logger) { _connectionManager = connectionManager; _logger = logger; } public override async Task CaptureScreen(IAsyncStreamReader<CaptureRequest> requestStream, IServerStreamWriter<CaptureResponse> responseStream, ServerCallContext context) { await foreach (var request in requestStream.ReadAllAsync()) { var agentStream = _connectionManager.GetActiveAgentStream(); if (agentStream == null) { await responseStream.WriteAsync(new CaptureResponse { Status = "错误:无可用代理端" }); continue; } // 生成唯一请求ID,关联客户端流 var requestId = Guid.NewGuid().ToString(); _clientStreamMap.TryAdd(requestId, responseStream); // 转发请求给代理端 await agentStream.WriteAsync(new CaptureRequest { RequestId = requestId, Quality = request.Quality, Area = request.Area }); } } }
三、代理端(.NET控制台应用)实现
创建.NET控制台项目,安装Grpc.Net.Client与System.Drawing.Common包,实现截图逻辑与gRPC通信:
class Program { static async Task Main(string[] args) { // 连接服务端的AgentScreenService var channel = GrpcChannel.ForAddress("https://localhost:5001"); var client = new AgentScreenService.AgentScreenServiceClient(channel); using var call = client.HandleCapture(); // 监听服务端的截图请求 var requestListenerTask = Task.Run(async () => { await foreach (var request in call.ResponseStream.ReadAllAsync()) { // 执行截图操作 var imageBytes = CaptureScreen(request.Quality, request.Area); // 返回结果给服务端 await call.RequestStream.WriteAsync(new CaptureResponse { RequestId = request.RequestId, ImageData = imageBytes, Status = "Success" }); } }); Console.WriteLine("代理端已启动,等待服务端请求..."); await requestListenerTask; } private static byte[] CaptureScreen(int quality, string area) { // 示例:截取全屏 var screenBounds = Screen.PrimaryScreen.Bounds; using var bitmap = new Bitmap(screenBounds.Width, screenBounds.Height); using var g = Graphics.FromImage(bitmap); g.CopyFromScreen(screenBounds.X, screenBounds.Y, 0, 0, screenBounds.Size); using var ms = new MemoryStream(); bitmap.Save(ms, ImageFormat.Jpeg); return ms.ToArray(); } }
将代理端设置为用户登录时自动启动(可放入用户Startup文件夹,或通过任务计划程序配置),确保运行在当前用户会话中。
四、客户端(WPF应用)实现
创建WPF项目,安装Grpc.Net.Client包,实现请求发送与截图显示:
1. XAML界面
<Grid> <Button x:Name="CaptureBtn" Content="获取远程截图" Click="CaptureBtn_Click" Margin="10"/> <Image x:Name="ScreenImage" Margin="10,50,10,10" Stretch="Uniform"/> </Grid>
2. 后台逻辑
public partial class MainWindow : Window { private readonly ClientScreenService.ClientScreenServiceClient _grpcClient; public MainWindow() { InitializeComponent(); var channel = GrpcChannel.ForAddress("https://localhost:5001"); _grpcClient = new ClientScreenService.ClientScreenServiceClient(channel); } private async void CaptureBtn_Click(object sender, RoutedEventArgs e) { using var call = _grpcClient.CaptureScreen(); // 发送截图请求 await call.RequestStream.WriteAsync(new CaptureRequest { Quality = 80, Area = "FullScreen" }); await call.RequestStream.CompleteAsync(); // 接收并显示截图 await foreach (var response in call.ResponseStream.ReadAllAsync()) { if (response.Status == "Success") { using var ms = new MemoryStream(response.ImageData); var image = BitmapFrame.Create(ms, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); ScreenImage.Source = image; } else { MessageBox.Show(response.Status); } } } }
五、部署与注意事项
- 服务端部署:
- 发布项目:
dotnet publish -c Release -r win-x64 --self-contained true - 安装为Windows服务:
sc create ScreenCaptureService binPath="C:\发布路径\ScreenCaptureService.exe" - 启动服务:
sc start ScreenCaptureService
- 发布项目:
- 通信配置:
- 服务端需配置HTTPS(生产环境),开放对应端口的防火墙规则
- 代理端与服务端同机,可直接使用
localhost访问
- 异常处理:
- 服务端需定期清理无效的代理端连接,处理代理端重连场景
- 客户端需捕获服务端无响应、代理端不可用等异常情况
内容的提问来源于stack exchange,提问作者vitkuz573
相关产品推荐
相关产品推荐

