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.
444 lines
14 KiB
444 lines
14 KiB
#if !NET20
|
|
|
|
using Apewer;
|
|
using Apewer.Network;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Net;
|
|
|
|
namespace Apewer.WebSocket
|
|
{
|
|
|
|
/// <summary></summary>
|
|
public sealed class ChatServer
|
|
{
|
|
|
|
#region public
|
|
|
|
/// <summary></summary>
|
|
public Action<string> ErrorAction { get; set; }
|
|
|
|
/// <summary></summary>
|
|
public Action<Exception> ExceptedAction { get; set; }
|
|
|
|
/// <summary></summary>
|
|
public Action DisposedAction { get; set; }
|
|
|
|
/// <summary></summary>
|
|
public bool WebSocketListening
|
|
{
|
|
get { return (_websocket != null && _websocket.Running); }
|
|
}
|
|
|
|
/// <summary>WebSocket 监听的地址,默认为 0.0.0.0。</summary>
|
|
public string WebSocketAddress
|
|
{
|
|
get { return _websocketaddress; }
|
|
set { if (!WebSocketListening) _websocketaddress = value; }
|
|
}
|
|
|
|
/// <summary>WebSocket 监听的端口,默认为 8000。</summary>
|
|
public int WebSocketPort
|
|
{
|
|
get { return _websocketport; }
|
|
set { if (!WebSocketListening) _websocketport = NumberUtility.Restrict(value, 0, ushort.MaxValue); }
|
|
}
|
|
|
|
/// <summary></summary>
|
|
public bool CommandListening
|
|
{
|
|
get { return false; }
|
|
}
|
|
|
|
/// <summary>命令服务监听的地址,默认为 0.0.0.0。</summary>
|
|
public string CommandAddress
|
|
{
|
|
get { return _commandaddress; }
|
|
set { if (!CommandListening) _commandaddress = value; }
|
|
}
|
|
|
|
/// <summary>命令服务监听的端口,默认为 8000。</summary>
|
|
public int CommandPort
|
|
{
|
|
get { return _commandport; }
|
|
set { if (!CommandListening) _commandport = NumberUtility.Restrict(value, 0, ushort.MaxValue); }
|
|
}
|
|
|
|
/// <summary></summary>
|
|
public void Start() { PrivateStart(); }
|
|
|
|
/// <summary></summary>
|
|
public void Dispose()
|
|
{
|
|
if (_websocket != null)
|
|
{
|
|
lock (_websocket)
|
|
{
|
|
_websocket.Close();
|
|
_websocket.Dispose();
|
|
}
|
|
_websocket = null;
|
|
}
|
|
|
|
DisposedAction?.Invoke();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region fields
|
|
|
|
private string _websocketaddress = "0.0.0.0";
|
|
private int _websocketport = 8000;
|
|
|
|
private string _commandaddress = "0.0.0.0";
|
|
private int _commandport = 8000;
|
|
|
|
private Dictionary<int, List<Connection>> _rooms = new Dictionary<int, List<Connection>>();
|
|
|
|
private List<Connection> _lobby = new List<Connection>();
|
|
|
|
private GenericServer _websocket = null;
|
|
|
|
private UdpServer _command = null;
|
|
|
|
#endregion
|
|
|
|
#region private
|
|
|
|
void RaiseError(params string[] text)
|
|
{
|
|
var merged = TextUtility.Merge(text);
|
|
if (!TextUtility.IsBlank(merged)) ErrorAction?.Invoke(merged);
|
|
}
|
|
|
|
void RaiseExcepted(Exception exception)
|
|
{
|
|
if (exception != null) ExceptedAction?.Invoke(exception);
|
|
}
|
|
|
|
void RaiseConsole(params string[] text)
|
|
{
|
|
var merged = TextUtility.Merge(text);
|
|
if (!TextUtility.IsBlank(merged)) Logger.Web.Text(this, merged);
|
|
}
|
|
|
|
private void PrivateStart()
|
|
{
|
|
if (_websocket != null) return;
|
|
_websocket = new GenericServer();
|
|
lock (_websocket)
|
|
{
|
|
try
|
|
{
|
|
_websocket.Start(_websocketport, IPAddress.Parse(_websocketaddress));
|
|
|
|
_websocket.OnMessage += WebSocketReceived;
|
|
_websocket.OnOpen += WebsocketOnOpen;
|
|
_websocket.OnClose += WebsocketOnClose;
|
|
RaiseConsole("WebSocket 服务已启动。");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_websocket = null;
|
|
RaiseError($"WebSocket 服务启动失败:{ex.Message()}");
|
|
return;
|
|
}
|
|
|
|
_command = new UdpServer();
|
|
_command.Port = _commandport;
|
|
_command.Excepted += (s, ex) => RaiseExcepted(ex);
|
|
_command.Quitted += (s) => RaiseConsole("命令服务已退出。");
|
|
_command.Started += (s) => RaiseConsole("命令服务已启动。");
|
|
_command.Received += (s, e) => CommandReceived(s, e.IP, e.Port, e.Bytes);
|
|
_command.Start();
|
|
}
|
|
}
|
|
|
|
void WebsocketOnClose(Connection socket)
|
|
{
|
|
var room = GetRoomId(socket.QueryPath);
|
|
var connections = LeaveRoom(socket, room);
|
|
Send(connections, PackLeft(socket, room).ToString());
|
|
}
|
|
|
|
void WebsocketOnOpen(Connection socket)
|
|
{
|
|
var room = GetRoomId(socket.QueryPath);
|
|
var connections = JoinRoom(socket, room);
|
|
Send(connections, PackJoined(socket, room).ToString());
|
|
}
|
|
|
|
void WebSocketReceived(Connection socket, string content)
|
|
{
|
|
if (socket == null || string.IsNullOrEmpty(content))
|
|
{
|
|
RaiseConsole("WebSocket 服务接收到空消息。");
|
|
return;
|
|
}
|
|
|
|
var input = Json.From(content);
|
|
if (input == null)
|
|
{
|
|
RaiseConsole("WebSocket 服务接收到无效消息。");
|
|
return;
|
|
}
|
|
|
|
var room = GetRoomId(socket.QueryPath);
|
|
|
|
switch (input["type"])
|
|
{
|
|
case "command":
|
|
{
|
|
switch (input["command"])
|
|
{
|
|
case "exit":
|
|
Dispose();
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case "broadcast":
|
|
{
|
|
var json = PackMessage(socket, 0, input["text"], input.GetProperty("addition"));
|
|
if (_websocket != null)
|
|
{
|
|
lock (_websocket)
|
|
{
|
|
_websocket.Send(json.ToString());
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case "message":
|
|
{
|
|
var connections = JoinRoom(socket, room);
|
|
var json = PackMessage(socket, room, input["text"], input.GetProperty("addition"));
|
|
Send(connections, json.ToString());
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void Send(List<Connection> connections, string message)
|
|
{
|
|
if (connections == null) return;
|
|
if (string.IsNullOrEmpty(message)) return;
|
|
foreach (var connection in connections)
|
|
{
|
|
if (connection == null) continue;
|
|
try
|
|
{
|
|
connection.Send(message);
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
if (ExceptedAction == null)
|
|
{
|
|
var a = connection.Address;
|
|
var p = connection.Port.ToString();
|
|
var e = exception.ToString();
|
|
Logger.Web.Error(this, TextUtility.Merge($"对 {a}:{p} 发送消息失败。"), exception.Message);
|
|
}
|
|
else
|
|
{
|
|
ExceptedAction(exception);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int GetRoomId(string path)
|
|
{
|
|
if (TextUtility.IsBlank(path)) return 0;
|
|
var array = path.Split('/');
|
|
if (array.Length > 1)
|
|
{
|
|
var caption = array[1];
|
|
var room = NumberUtility.Int32(caption);
|
|
var connections = new List<Connection>();
|
|
if (room == 0 && !TextUtility.IsBlank(caption)) room = caption.GetHashCode();
|
|
return room;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
List<Connection> JoinRoom(Connection socket, int room)
|
|
{
|
|
var connections = new List<Connection>();
|
|
if (room == 0)
|
|
{
|
|
lock (_lobby)
|
|
{
|
|
if (!_lobby.Contains(socket)) _lobby.Add(socket);
|
|
connections.AddRange(_lobby);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lock (_rooms)
|
|
{
|
|
if (_rooms.ContainsKey(room))
|
|
{
|
|
var value = _rooms[room];
|
|
lock (value)
|
|
{
|
|
if (!value.Contains(socket)) value.Add(socket);
|
|
connections.AddRange(value);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
var value = new List<Connection>();
|
|
value.Add(socket);
|
|
connections.AddRange(value);
|
|
_rooms.Add(room, value);
|
|
}
|
|
}
|
|
}
|
|
return connections;
|
|
}
|
|
|
|
List<Connection> LeaveRoom(Connection socket, int room)
|
|
{
|
|
var connections = new List<Connection>();
|
|
if (room == 0)
|
|
{
|
|
lock (_lobby)
|
|
{
|
|
if (_lobby.Contains(socket)) _lobby.Remove(socket);
|
|
connections.AddRange(_lobby);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
lock (_rooms)
|
|
{
|
|
if (_rooms.ContainsKey(room))
|
|
{
|
|
var value = _rooms[room];
|
|
var empty = false;
|
|
lock (value)
|
|
{
|
|
if (value.Contains(socket)) value.Remove(socket);
|
|
empty = value.Count == 0;
|
|
if (!empty) connections.AddRange(value);
|
|
if (empty) _rooms.Remove(room);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return connections;
|
|
}
|
|
|
|
Json PackJoined(Connection socket, int room = 0)
|
|
{
|
|
var json = Json.NewObject();
|
|
json.SetProperty("utcstamp", ClockUtility.UtcStamp);
|
|
json.SetProperty("nowstamp", ClockUtility.NowStamp);
|
|
json.SetProperty("nowlucid", ClockUtility.LucidNow);
|
|
json.SetProperty("address", socket.Address);
|
|
json.SetProperty("port", socket.Port);
|
|
json.SetProperty("room", room);
|
|
json.SetProperty("type", "joined");
|
|
return json;
|
|
}
|
|
|
|
Json PackLeft(Connection socket, int room = 0)
|
|
{
|
|
var json = Json.NewObject();
|
|
json.SetProperty("utcstamp", ClockUtility.UtcStamp);
|
|
json.SetProperty("nowstamp", ClockUtility.NowStamp);
|
|
json.SetProperty("nowlucid", ClockUtility.LucidNow);
|
|
json.SetProperty("address", socket.Address);
|
|
json.SetProperty("port", socket.Port);
|
|
json.SetProperty("room", room);
|
|
json.SetProperty("type", "left");
|
|
return json;
|
|
}
|
|
|
|
Json PackMessage(Connection socket, int room = 0, string text = null, Json addition = null)
|
|
{
|
|
var json = Json.NewObject();
|
|
json.SetProperty("utcstamp", ClockUtility.UtcStamp);
|
|
json.SetProperty("nowstamp", ClockUtility.NowStamp);
|
|
json.SetProperty("nowlucid", ClockUtility.LucidNow);
|
|
json.SetProperty("address", socket.Address);
|
|
json.SetProperty("port", socket.Port);
|
|
json.SetProperty("room", room);
|
|
json.SetProperty("type", "message");
|
|
json.SetProperty("text", text);
|
|
json.SetProperty("addition", addition);
|
|
return json;
|
|
}
|
|
|
|
void CommandReceived(object sender, string ip, int port, byte[] bytes)
|
|
{
|
|
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region static
|
|
|
|
/// <summary></summary>
|
|
public static LogLevel LogLevel
|
|
{
|
|
get { return GenericServer.LogLevel; }
|
|
set { GenericServer.LogLevel = value; }
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region program
|
|
|
|
/// <summary>运行示例实例。</summary>
|
|
public static void RunDemo(int chatTcpPort = 8000, int commandUdpPort = 8000)
|
|
{
|
|
var cs = new ChatServer();
|
|
cs.WebSocketPort = chatTcpPort;
|
|
cs.CommandPort = commandUdpPort;
|
|
cs.Start();
|
|
ChatServer.LogLevel = LogLevel.Debug;
|
|
while (true)
|
|
{
|
|
var input = Console.ReadLine();
|
|
if (input == "exit") break;
|
|
}
|
|
cs.Dispose();
|
|
}
|
|
|
|
static void RunLite()
|
|
{
|
|
var ws = new GenericServer();
|
|
ws.OnOpen += (s) =>
|
|
{
|
|
ws.Send(s.Address, ":", s.Port.ToString(), "\n已连接\nHost: ", s.Host, "\nOrigin: ", s.Origin + "\nPath: ", s.QueryPath);
|
|
};
|
|
ws.OnClose += (s) =>
|
|
{
|
|
ws.Send(s.Address, ":", s.Port.ToString(), "\n已断开。");
|
|
};
|
|
ws.OnMessage += (s, m) =>
|
|
{
|
|
s.Send(s.Address, ":", s.Port.ToString(), "\n", m);
|
|
};
|
|
ws.Start();
|
|
|
|
while (true)
|
|
{
|
|
var input = Console.ReadLine();
|
|
if (input == "exit") break;
|
|
ws.Send(ws.Address.ToString(), ":", ws.Port.ToString(), "\n", input);
|
|
}
|
|
|
|
ws.Close();
|
|
ws.Dispose();
|
|
}
|
|
|
|
#endregion
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|