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.
462 lines
14 KiB
462 lines
14 KiB
#if !NET20
|
|
|
|
using Apewer;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Net;
|
|
|
|
namespace Apewer.WebSocket
|
|
{
|
|
|
|
/// <summary></summary>
|
|
public sealed class GenericServer : IDisposable
|
|
{
|
|
|
|
private Dictionary<int, Connection> _connections = new Dictionary<int, Connection>();
|
|
private WebSocketServer _server = null;
|
|
|
|
private bool _running = false;
|
|
private IPAddress _address = null;
|
|
private int _port = 0;
|
|
|
|
/// <summary></summary>
|
|
public event SocketEvent OnOpen;
|
|
|
|
/// <summary></summary>
|
|
public event SocketEvent OnClose;
|
|
|
|
/// <summary></summary>
|
|
public event SocketEvent<string> OnMessage;
|
|
|
|
/// <summary></summary>
|
|
public event SocketEvent<byte[]> OnBytes;
|
|
|
|
/// <summary></summary>
|
|
public event SocketEvent<byte[]> OnPing;
|
|
|
|
/// <summary></summary>
|
|
public event SocketEvent<byte[]> OnPong;
|
|
|
|
/// <summary></summary>
|
|
public event SocketEvent<Exception> OnError;
|
|
|
|
/// <summary></summary>
|
|
public event ServerEvent<Exception> Excepted;
|
|
|
|
#region Properties
|
|
|
|
/// <summary>正在监听。</summary>
|
|
public bool Running
|
|
{
|
|
get { return _running; }
|
|
}
|
|
|
|
/// <summary>监听的地址。</summary>
|
|
public IPAddress Address
|
|
{
|
|
get { return _address; }
|
|
}
|
|
|
|
/// <summary>监听的端口号。</summary>
|
|
public int Port
|
|
{
|
|
get { return _port; }
|
|
}
|
|
|
|
/// <summary>当前 Sockets 数量。</summary>
|
|
public int Count
|
|
{
|
|
get
|
|
{
|
|
var count = 0;
|
|
lock (_connections) { count = _connections.Count; }
|
|
return count;
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Methods
|
|
|
|
/// <summary></summary>
|
|
public GenericServer() { }
|
|
|
|
/// <summary>关闭服务,并释放系统资源。</summary>
|
|
public void Dispose()
|
|
{
|
|
lock (_connections)
|
|
{
|
|
_connections.Clear();
|
|
}
|
|
lock (_server)
|
|
{
|
|
if (_server != null)
|
|
{
|
|
_server.Dispose();
|
|
}
|
|
}
|
|
_server = null;
|
|
_running = false;
|
|
}
|
|
|
|
/// <summary>启动监听,如果监听正在运行则失败。在所有 IPv4 网络接口上自动选择可用的端口号。</summary>
|
|
/// <returns>已监听的端口号。</returns>
|
|
/// <exception cref="FormatException" />
|
|
/// <exception cref="InvalidOperationException" />
|
|
public void Start() => Start(0, null);
|
|
|
|
/// <summary>启动监听,如果监听正在运行则失败。</summary>
|
|
/// <param name="endPoint">要监听的终结点。指定为 NULL 时将在所有 IPv4 网络接口上自动选择可用的端口号。</param>
|
|
/// <returns>已监听的端口号。</returns>
|
|
/// <exception cref="ArgumentOutOfRangeException" />
|
|
/// <exception cref="FormatException" />
|
|
/// <exception cref="InvalidOperationException" />
|
|
public void Start(IPEndPoint endPoint)
|
|
{
|
|
if (endPoint == null) Start(0, null);
|
|
else Start(endPoint.Port, endPoint.Address);
|
|
}
|
|
|
|
/// <summary>启动监听,如果监听正在运行则失败。</summary>
|
|
/// <param name="port">要监听的端口号。指定为 0 时将自动选择可用的端口号。</param>
|
|
/// <param name="address">要监听的网络接口。指定为 NULL 时将在所有 IPv4 网络接口监听,等同于 <see cref="IPAddress.Any"/>。</param>
|
|
/// <returns>已监听的端口号。</returns>
|
|
/// <exception cref="ArgumentOutOfRangeException" />
|
|
/// <exception cref="FormatException" />
|
|
/// <exception cref="InvalidOperationException" />
|
|
public void Start(int port, IPAddress address = null)
|
|
{
|
|
if (address == null) address = IPAddress.Any;
|
|
if (port < 0) throw new ArgumentOutOfRangeException(nameof(port));
|
|
if (port > 65535) throw new ArgumentOutOfRangeException(nameof(port));
|
|
if (_running) throw new InvalidOperationException("示例已经在运行中,无法再次启动。");
|
|
|
|
_address = address;
|
|
_port = port;
|
|
|
|
var location = "ws://" + _address.ToString() + ":" + port.ToString();
|
|
_server = new WebSocketServer(location);
|
|
_server.Start(Initialize);
|
|
_port = _server.Port;
|
|
_running = true;
|
|
}
|
|
|
|
/// <summary>对所有连接发送文本。</summary>
|
|
/// <exception cref="InvalidOperationException"></exception>
|
|
public int Send(params char[] message)
|
|
{
|
|
var text = null as string;
|
|
if (message == null || message.Length < 1) return 0;
|
|
if (message.Length == 1)
|
|
{
|
|
text = message[0].ToString();
|
|
}
|
|
else
|
|
{
|
|
var sb = new System.Text.StringBuilder();
|
|
foreach (var i in message)
|
|
{
|
|
if ((object)i != null) sb.Append(i);
|
|
}
|
|
text = sb.ToString();
|
|
}
|
|
if (text.Length < 1) return 0;
|
|
|
|
var connections = GetConnections();
|
|
var count = 0;
|
|
foreach (var connection in connections)
|
|
{
|
|
try
|
|
{
|
|
var sent = connection.Send(message);
|
|
if (sent != null) count += 1;
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
RaiseExcepted(exception);
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
/// <summary>发送文本。</summary>
|
|
/// <exception cref="InvalidOperationException"></exception>
|
|
public int Send(params string[] message)
|
|
{
|
|
var text = null as string;
|
|
if (message == null || message.Length < 1) return 0;
|
|
if (message.Length == 1)
|
|
{
|
|
text = message[0].ToString();
|
|
}
|
|
else
|
|
{
|
|
var sb = new System.Text.StringBuilder();
|
|
foreach (var i in message)
|
|
{
|
|
if (i != null) sb.Append(i);
|
|
}
|
|
text = sb.ToString();
|
|
}
|
|
if (text.Length < 1) return 0;
|
|
|
|
var connections = GetConnections();
|
|
var count = 0;
|
|
foreach (var connection in connections)
|
|
{
|
|
try
|
|
{
|
|
var sent = connection.Send(message);
|
|
if (sent != null) count += 1;
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
RaiseExcepted(exception);
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
/// <summary>发送字节数组。</summary>
|
|
/// <exception cref="InvalidOperationException"></exception>
|
|
public int Send(params byte[] message)
|
|
{
|
|
var connections = GetConnections();
|
|
var count = 0;
|
|
foreach (var connection in connections)
|
|
{
|
|
try
|
|
{
|
|
var sent = connection.Send(message);
|
|
if (sent != null) count += 1;
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
RaiseExcepted(exception);
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
/// <summary>发送 PING。</summary>
|
|
/// <exception cref="InvalidOperationException"></exception>
|
|
public int Ping(params byte[] message)
|
|
{
|
|
var connections = GetConnections();
|
|
var count = 0;
|
|
foreach (var connection in connections)
|
|
{
|
|
try
|
|
{
|
|
var sent = connection.Ping(message);
|
|
if (sent != null) count += 1;
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
RaiseExcepted(exception);
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
/// <summary>发送 PONG。</summary>
|
|
/// <exception cref="InvalidOperationException"></exception>
|
|
public int Pong(params byte[] message)
|
|
{
|
|
var connections = GetConnections();
|
|
var count = 0;
|
|
foreach (var connection in connections)
|
|
{
|
|
try
|
|
{
|
|
var sent = connection.Pong(message);
|
|
if (sent != null) count += 1;
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
RaiseExcepted(exception);
|
|
}
|
|
}
|
|
return count;
|
|
}
|
|
|
|
/// <summary>关闭 Socket 连接。</summary>
|
|
public void Close()
|
|
{
|
|
lock (_server)
|
|
{
|
|
try
|
|
{
|
|
if (_server != null && _server.ListenerSocket != null)
|
|
{
|
|
_server.ListenerSocket.Close();
|
|
}
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
RaiseExcepted(exception);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Private
|
|
|
|
List<Connection> GetConnections()
|
|
{
|
|
var list = new List<Connection>();
|
|
lock (_connections)
|
|
{
|
|
foreach (var i in _connections.Values)
|
|
{
|
|
if (i != null) list.Add(i);
|
|
}
|
|
}
|
|
return list;
|
|
}
|
|
|
|
void RaiseExcepted(Exception exception)
|
|
{
|
|
if (exception == null) return;
|
|
if (Excepted == null) return;
|
|
try
|
|
{
|
|
InBackground(() => Excepted(this, exception));
|
|
}
|
|
catch { }
|
|
}
|
|
|
|
void Initialize(Connection socket)
|
|
{
|
|
if (socket == null) return;
|
|
|
|
socket.OnOpen = () => InBackground(() =>
|
|
{
|
|
var hashcode = socket.GetHashCode();
|
|
lock (_connections)
|
|
{
|
|
if (!_connections.ContainsKey(hashcode))
|
|
{
|
|
_connections.Add(hashcode, socket);
|
|
}
|
|
}
|
|
try
|
|
{
|
|
OnOpen?.Invoke(socket);
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
RaiseExcepted(exception);
|
|
}
|
|
});
|
|
|
|
socket.OnClose = () => InBackground(() =>
|
|
{
|
|
var hashcode = socket.GetHashCode();
|
|
try
|
|
{
|
|
OnClose?.Invoke(socket);
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
RaiseExcepted(exception);
|
|
}
|
|
});
|
|
|
|
socket.OnBytes = (content) => InBackground(() =>
|
|
{
|
|
try
|
|
{
|
|
OnBytes?.Invoke(socket, content);
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
RaiseExcepted(exception);
|
|
}
|
|
});
|
|
|
|
socket.OnError = (content) => InBackground(() =>
|
|
{
|
|
try
|
|
{
|
|
OnError?.Invoke(socket, content);
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
RaiseExcepted(exception);
|
|
}
|
|
});
|
|
|
|
socket.OnMessage = (content) => InBackground(() =>
|
|
{
|
|
try
|
|
{
|
|
OnMessage?.Invoke(socket, content);
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
RaiseExcepted(exception);
|
|
}
|
|
});
|
|
|
|
socket.OnPing = (content) => InBackground(() =>
|
|
{
|
|
try
|
|
{
|
|
if (OnPing == null) socket.Pong(content);
|
|
else OnPing?.Invoke(socket, content);
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
RaiseExcepted(exception);
|
|
}
|
|
});
|
|
|
|
socket.OnPong = (content) => InBackground(() =>
|
|
{
|
|
try
|
|
{
|
|
OnPong?.Invoke(socket, content);
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
RaiseExcepted(exception);
|
|
}
|
|
});
|
|
}
|
|
|
|
int GetHashCode(Connection connection)
|
|
{
|
|
if (connection == null) return 0;
|
|
if (connection.ConnectionInfo == null) return 0;
|
|
return connection.ConnectionInfo.Id.GetHashCode();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region static
|
|
|
|
/// <summary></summary>
|
|
public static LogLevel LogLevel
|
|
{
|
|
get { return WebSocketLog.Level; }
|
|
set { WebSocketLog.Level = value; }
|
|
}
|
|
|
|
[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
|
|
private static void InBackground(System.Action action)
|
|
{
|
|
if (action == null) return;
|
|
var thread = new System.Threading.Thread(delegate (object v) { ((System.Action)v)(); });
|
|
thread.IsBackground = true;
|
|
thread.Start(action);
|
|
}
|
|
|
|
#endregion
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|