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

#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