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();
}
}