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

#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