You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
176 lines
6.0 KiB
176 lines
6.0 KiB
#if !NET20
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Net;
|
|
using System.Text;
|
|
|
|
namespace Apewer.WebSocket
|
|
{
|
|
|
|
/// <summary>简易的 WebSocket 服务器。</summary>
|
|
public class SimpleServer
|
|
{
|
|
|
|
static GenericServer _server = null;
|
|
static Dictionary<string, Connection> _clients = new Dictionary<string, Connection>();
|
|
|
|
/// <summary>服务端实例。</summary>
|
|
public static GenericServer Server { get => _server; }
|
|
|
|
/// <summary>消息日志记录程序。</summary>
|
|
public static Logger Logger { get; set; }
|
|
|
|
/// <summary>当前已连接的客户端。</summary>
|
|
public static string[] Clients
|
|
{
|
|
get
|
|
{
|
|
lock (_clients)
|
|
{
|
|
var endpoints = _clients.Keys.ToArray();
|
|
return endpoints;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>收到消息时的回调。</summary>
|
|
public static Action<Connection, string> OnMessage { get; set; }
|
|
|
|
/// <summary>收到消息时的回调。</summary>
|
|
public static Action<Connection, byte[]> OnBytes { get; set; }
|
|
|
|
/// <summary>客户端断开的回调</summary>
|
|
public static Action<Connection> OnClose { get; set; }
|
|
|
|
/// <summary>客户端连接的回调。</summary>
|
|
public static Action<Connection> OnOpen { get; set; }
|
|
|
|
/// <summary>发生错误触发的事件。</summary>
|
|
public static Action<Connection, Exception> OnError { get; set; }
|
|
|
|
/// <summary>启动服务端。</summary>
|
|
/// <exception cref="Exception" />
|
|
public static GenericServer Run(IPEndPoint endpoint)
|
|
{
|
|
if (_server != null) throw new InvalidOperationException($"已经存在服务端实例,无法再次启动。");
|
|
if (endpoint == null) throw new ArgumentNullException(nameof(endpoint));
|
|
|
|
var port = endpoint.Port;
|
|
if (NetworkUtility.ActiveTcpPorts().Contains(port))
|
|
{
|
|
var error = $"端口 {port} 已被占用,无法启动 WebSocket 服务器。";
|
|
Logger?.Text($"WebSocket {port}", "Run", error);
|
|
throw new Exception(error);
|
|
}
|
|
|
|
_server = new GenericServer();
|
|
_server.OnPing += (s, e) => s.Send("PONG");
|
|
_server.OnError += (conn, ex) => RuntimeUtility.InBackground(() =>
|
|
{
|
|
Logger?.Text($"WebSocket {conn.Port}", "OnError", $"{conn.Address}:{conn.Port}", ex.GetType().Name, ex.Message);
|
|
OnError?.Invoke(conn, ex);
|
|
});
|
|
_server.OnOpen += (conn) =>
|
|
{
|
|
lock (_clients)
|
|
{
|
|
var endpoint = $"{conn.Address}:{conn.Port}";
|
|
if (endpoint.Contains(endpoint)) _clients[endpoint] = conn;
|
|
else _clients.Add(endpoint, conn);
|
|
}
|
|
RuntimeUtility.InBackground(() =>
|
|
{
|
|
Logger?.Text($"WebSocket {port}", "OnOpen", $"{conn.Address}:{conn.Port}");
|
|
OnOpen?.Invoke(conn);
|
|
});
|
|
};
|
|
_server.OnClose += (conn) =>
|
|
{
|
|
lock (_clients)
|
|
{
|
|
var endpoint = $"{conn.Address}:{conn.Port}";
|
|
if (_clients.ContainsKey(endpoint)) _clients.Remove(endpoint);
|
|
}
|
|
RuntimeUtility.InBackground(() =>
|
|
{
|
|
Logger?.Text($"WebSocket {port}", "OnClose", $"{conn.Address}:{conn.Port}");
|
|
OnClose?.Invoke(conn);
|
|
});
|
|
};
|
|
_server.OnBytes += (conn, bytes) =>
|
|
{
|
|
if (bytes.IsEmpty()) return;
|
|
RuntimeUtility.InBackground(() =>
|
|
{
|
|
Logger?.Text($"WebSocket {port}", "OnBytes", $"{conn.Address}:{conn.Port}", $"Length = {bytes.Length}", bytes.ToX2(" "));
|
|
OnBytes?.Invoke(conn, bytes);
|
|
});
|
|
};
|
|
_server.OnMessage += (conn, text) =>
|
|
{
|
|
if (text.IsEmpty()) return;
|
|
switch (text.Lower())
|
|
{
|
|
case "ping":
|
|
conn.Send("pong");
|
|
return;
|
|
case "pong":
|
|
return;
|
|
}
|
|
RuntimeUtility.InBackground(() =>
|
|
{
|
|
Logger?.Text($"WebSocket {port}", "OnMessage", $"{conn.Address}:{conn.Port}", text);
|
|
OnMessage?.Invoke(conn, text);
|
|
});
|
|
};
|
|
_server.Start(port);
|
|
|
|
Logger?.Text($"WebSocket {port}", "Run", "已启动。");
|
|
return _server;
|
|
}
|
|
|
|
/// <summary>向单个客户端发送文本消息。</summary>
|
|
public static void Send(string endpoint, string message)
|
|
{
|
|
if (endpoint.IsEmpty()) return;
|
|
if (message.IsEmpty()) return;
|
|
|
|
Connection conn;
|
|
lock (_clients)
|
|
{
|
|
if (!_clients.TryGetValue(endpoint, out conn))
|
|
{
|
|
return;
|
|
// throw new Exception($"终结点【{endpoint}】不存在。");
|
|
}
|
|
}
|
|
|
|
Logger?.Text($"WebSocket {conn.Port}", "Send", endpoint, message);
|
|
conn.Send(message);
|
|
}
|
|
|
|
/// <summary>向所有客户端广播文本消息。</summary>
|
|
public static void Broadcast(string message)
|
|
{
|
|
if (message.IsEmpty()) return;
|
|
var endpoints = Clients;
|
|
foreach (var endpoint in endpoints)
|
|
{
|
|
try
|
|
{
|
|
Send(endpoint, message);
|
|
}
|
|
catch { }
|
|
}
|
|
}
|
|
|
|
/// <summary>向所有客户端广播文本消息。</summary>
|
|
public static void Broadcast(object json) => Broadcast(Json.From(json));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|