using System.Net; using System.Net.WebSockets; using System.Text; using Microsoft.Extensions.Options; namespace DeviceAgent.Services; /// /// 屏幕流服务 - 通过 WebSocket 实时推送屏幕画面 /// public class ScreenStreamService : IDisposable { private readonly ILogger _logger; private readonly ScreenCaptureService _screenCaptureService; private readonly AgentConfig _config; private HttpListener? _httpListener; private readonly List _clients = new(); private readonly object _clientsLock = new(); private CancellationTokenSource? _cts; private Task? _streamTask; private bool _isRunning; public ScreenStreamService( ILogger logger, ScreenCaptureService screenCaptureService, IOptions config) { _logger = logger; _screenCaptureService = screenCaptureService; _config = config.Value; } /// /// 启动 WebSocket 服务器 /// public async Task StartAsync(CancellationToken cancellationToken) { if (!_config.ScreenStreamEnabled) { _logger.LogInformation("屏幕流服务已禁用"); return; } try { _cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); _httpListener = new HttpListener(); // 尝试使用 localhost,不需要管理员权限 _httpListener.Prefixes.Add($"http://localhost:{_config.ScreenStreamPort}/"); _httpListener.Prefixes.Add($"http://127.0.0.1:{_config.ScreenStreamPort}/"); // 尝试添加通配符(需要管理员权限) try { _httpListener.Prefixes.Add($"http://*:{_config.ScreenStreamPort}/"); } catch { } _httpListener.Start(); _isRunning = true; _logger.LogInformation("屏幕流 WebSocket 服务已启动,端口: {Port}", _config.ScreenStreamPort); // 启动接受连接的任务 _ = AcceptConnectionsAsync(_cts.Token); // 启动屏幕推送任务 _streamTask = StreamScreenAsync(_cts.Token); } catch (HttpListenerException ex) when (ex.ErrorCode == 5) { _logger.LogError("启动 WebSocket 服务失败: 需要管理员权限或运行 netsh 命令添加 URL 保留"); _logger.LogError("请以管理员身份运行: netsh http add urlacl url=http://+:{Port}/ user=Everyone", _config.ScreenStreamPort); } catch (Exception ex) { _logger.LogError(ex, "启动屏幕流服务失败"); } } /// /// 接受 WebSocket 连接 /// private async Task AcceptConnectionsAsync(CancellationToken cancellationToken) { while (!cancellationToken.IsCancellationRequested && _isRunning) { try { var context = await _httpListener!.GetContextAsync(); if (context.Request.IsWebSocketRequest) { _ = HandleWebSocketAsync(context, cancellationToken); } else { // 返回简单的状态页面 context.Response.StatusCode = 200; context.Response.ContentType = "text/html"; var html = $"

Screen Stream Service

Clients: {_clients.Count}

"; var buffer = Encoding.UTF8.GetBytes(html); await context.Response.OutputStream.WriteAsync(buffer, cancellationToken); context.Response.Close(); } } catch (ObjectDisposedException) { break; } catch (Exception ex) { if (!cancellationToken.IsCancellationRequested) { _logger.LogError(ex, "接受连接时发生错误"); } } } } /// /// 处理 WebSocket 连接 /// private async Task HandleWebSocketAsync(HttpListenerContext context, CancellationToken cancellationToken) { WebSocket? webSocket = null; try { var wsContext = await context.AcceptWebSocketAsync(null); webSocket = wsContext.WebSocket; lock (_clientsLock) { _clients.Add(webSocket); } _logger.LogInformation("新的屏幕流客户端连接,当前客户端数: {Count}", _clients.Count); // 保持连接,等待客户端断开 var buffer = new byte[1024]; while (webSocket.State == WebSocketState.Open && !cancellationToken.IsCancellationRequested) { try { var result = await webSocket.ReceiveAsync(new ArraySegment(buffer), cancellationToken); if (result.MessageType == WebSocketMessageType.Close) { break; } } catch { break; } } } catch (Exception ex) { _logger.LogError(ex, "处理 WebSocket 连接时发生错误"); } finally { if (webSocket != null) { lock (_clientsLock) { _clients.Remove(webSocket); } _logger.LogInformation("屏幕流客户端断开,当前客户端数: {Count}", _clients.Count); try { if (webSocket.State == WebSocketState.Open) { await webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closed", CancellationToken.None); } webSocket.Dispose(); } catch { } } } } /// /// 持续推送屏幕画面 /// private async Task StreamScreenAsync(CancellationToken cancellationToken) { var frameInterval = TimeSpan.FromMilliseconds(1000.0 / _config.ScreenStreamFps); while (!cancellationToken.IsCancellationRequested && _isRunning) { try { List clientsCopy; lock (_clientsLock) { clientsCopy = _clients.ToList(); } if (clientsCopy.Count > 0) { // 截取屏幕 var screenshot = _screenCaptureService.CaptureScreen( _config.ScreenStreamQuality, _config.ScreenStreamMaxWidth); if (screenshot.Length > 0) { // 发送给所有客户端 var sendTasks = clientsCopy .Where(ws => ws.State == WebSocketState.Open) .Select(ws => SendFrameAsync(ws, screenshot, cancellationToken)); await Task.WhenAll(sendTasks); } } await Task.Delay(frameInterval, cancellationToken); } catch (OperationCanceledException) { break; } catch (Exception ex) { _logger.LogError(ex, "推送屏幕画面时发生错误"); await Task.Delay(1000, cancellationToken); } } } /// /// 发送一帧画面 /// private async Task SendFrameAsync(WebSocket webSocket, byte[] frame, CancellationToken cancellationToken) { try { await webSocket.SendAsync( new ArraySegment(frame), WebSocketMessageType.Binary, true, cancellationToken); } catch (Exception ex) { _logger.LogDebug(ex, "发送帧失败"); } } /// /// 停止服务 /// public async Task StopAsync() { _isRunning = false; _cts?.Cancel(); // 关闭所有客户端连接 List clientsCopy; lock (_clientsLock) { clientsCopy = _clients.ToList(); _clients.Clear(); } foreach (var ws in clientsCopy) { try { if (ws.State == WebSocketState.Open) { await ws.CloseAsync(WebSocketCloseStatus.NormalClosure, "Server shutting down", CancellationToken.None); } ws.Dispose(); } catch { } } _httpListener?.Stop(); _httpListener?.Close(); if (_streamTask != null) { try { await _streamTask; } catch { } } _logger.LogInformation("屏幕流服务已停止"); } public void Dispose() { _cts?.Dispose(); _httpListener?.Close(); } }