实现WebSocket功能
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||
<PropertyGroup>
|
||||
@@ -38,6 +38,9 @@
|
||||
</ApplicationIcon>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="Newtonsoft.Json, Version=13.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\Newtonsoft.Json.13.0.4\lib\net40\Newtonsoft.Json.dll</HintPath>
|
||||
</Reference>
|
||||
<Reference Include="System" />
|
||||
<Reference Include="System.Core" />
|
||||
<Reference Include="System.Xml.Linq" />
|
||||
@@ -45,18 +48,28 @@
|
||||
<Reference Include="Microsoft.CSharp" />
|
||||
<Reference Include="System.Data" />
|
||||
<Reference Include="System.Xml" />
|
||||
<Reference Include="websocket-sharp, Version=1.0.1.0, Culture=neutral, PublicKeyToken=5660b08a1845a91e, processorArchitecture=MSIL">
|
||||
<HintPath>..\packages\WebSocketSharp-netstandard.1.0.1\lib\net35\websocket-sharp.dll</HintPath>
|
||||
<Reference Include="websocket-sharp">
|
||||
<HintPath>..\..\..\..\..\..\References\websocket-sharp\websocket-sharp\bin\Debug\websocket-sharp.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||
<Compile Include="WebSocket\IWebSocketClient.cs" />
|
||||
<Compile Include="WebSocket\IWebSocketServer.cs" />
|
||||
<Compile Include="WebSocket\WebSocketConfig.cs" />
|
||||
<Compile Include="WebSocket\WebSocketClient.cs" />
|
||||
<Compile Include="WebSocket\WebSocketServer.cs" />
|
||||
<Compile Include="WebSocket\WebSocketTest.cs" />
|
||||
<Compile Include="WebSocket\WebSocketMessageEventArgs.cs" />
|
||||
<Compile Include="WebSocket\WebSocketErrorEventArgs.cs" />
|
||||
<Compile Include="WebSocket\WebSocketStatusChangedEventArgs.cs" />
|
||||
<Compile Include="WebSocket\WebSocketMessageFailedEventArgs.cs" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Content Include="CubeLib.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Folder Include="WebSocket\" />
|
||||
<None Include="packages.config" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||
</Project>
|
||||
@@ -0,0 +1,108 @@
|
||||
using System;
|
||||
|
||||
namespace JoyD.Windows.CS.WebSocket
|
||||
{
|
||||
/// <summary>
|
||||
/// WebSocket客户端接口
|
||||
/// </summary>
|
||||
public interface IWebSocketClient
|
||||
{
|
||||
/// <summary>
|
||||
/// 连接WebSocket服务器
|
||||
/// </summary>
|
||||
void Connect();
|
||||
|
||||
/// <summary>
|
||||
/// 断开WebSocket连接
|
||||
/// </summary>
|
||||
void Disconnect();
|
||||
|
||||
/// <summary>
|
||||
/// 重连WebSocket服务器
|
||||
/// </summary>
|
||||
void Reconnect();
|
||||
|
||||
/// <summary>
|
||||
/// 发送消息
|
||||
/// </summary>
|
||||
/// <param name="type">消息类型</param>
|
||||
/// <param name="data">消息数据</param>
|
||||
void Send(string type, object data);
|
||||
|
||||
/// <summary>
|
||||
/// 发送二进制消息
|
||||
/// </summary>
|
||||
/// <param name="data">二进制数据</param>
|
||||
void Send(byte[] data);
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前连接状态
|
||||
/// </summary>
|
||||
/// <returns>连接状态</returns>
|
||||
string GetStatus();
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否已连接
|
||||
/// </summary>
|
||||
/// <returns>是否已连接</returns>
|
||||
bool IsConnected();
|
||||
|
||||
/// <summary>
|
||||
/// 获取消息队列大小
|
||||
/// </summary>
|
||||
/// <returns>队列大小</returns>
|
||||
int GetQueueSize();
|
||||
|
||||
/// <summary>
|
||||
/// 连接成功事件
|
||||
/// </summary>
|
||||
event EventHandler Connected;
|
||||
|
||||
/// <summary>
|
||||
/// 连接断开事件
|
||||
/// </summary>
|
||||
event EventHandler Disconnected;
|
||||
|
||||
/// <summary>
|
||||
/// 错误事件
|
||||
/// </summary>
|
||||
event EventHandler<WebSocketErrorEventArgs> Error;
|
||||
|
||||
/// <summary>
|
||||
/// 收到消息事件
|
||||
/// </summary>
|
||||
event EventHandler<WebSocketMessageEventArgs> Message;
|
||||
|
||||
/// <summary>
|
||||
/// 状态改变事件
|
||||
/// </summary>
|
||||
event EventHandler<WebSocketStatusChangedEventArgs> StatusChanged;
|
||||
|
||||
/// <summary>
|
||||
/// 正在连接事件
|
||||
/// </summary>
|
||||
event EventHandler Connecting;
|
||||
|
||||
/// <summary>
|
||||
/// 正在重连事件
|
||||
/// </summary>
|
||||
event EventHandler Reconnecting;
|
||||
|
||||
/// <summary>
|
||||
/// 消息已发送事件
|
||||
/// </summary>
|
||||
event EventHandler<WebSocketMessageEventArgs> MessageSent;
|
||||
|
||||
/// <summary>
|
||||
/// 消息已加入队列事件
|
||||
/// </summary>
|
||||
event EventHandler<WebSocketMessageEventArgs> MessageQueued;
|
||||
|
||||
/// <summary>
|
||||
/// 消息发送失败事件
|
||||
/// </summary>
|
||||
event EventHandler<WebSocketMessageFailedEventArgs> MessageFailed;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
using System;
|
||||
|
||||
namespace JoyD.Windows.CS.WebSocket
|
||||
{
|
||||
/// <summary>
|
||||
/// WebSocket服务器接口
|
||||
/// </summary>
|
||||
public interface IWebSocketServer
|
||||
{
|
||||
/// <summary>
|
||||
/// 启动服务器
|
||||
/// </summary>
|
||||
/// <param name="port">端口号</param>
|
||||
void Start(int port);
|
||||
|
||||
/// <summary>
|
||||
/// 停止服务器
|
||||
/// </summary>
|
||||
void Stop();
|
||||
|
||||
/// <summary>
|
||||
/// 广播消息
|
||||
/// </summary>
|
||||
/// <param name="type">消息类型</param>
|
||||
/// <param name="data">消息数据</param>
|
||||
void Broadcast(string type, object data);
|
||||
|
||||
/// <summary>
|
||||
/// 广播二进制消息
|
||||
/// </summary>
|
||||
/// <param name="data">二进制数据</param>
|
||||
void Broadcast(byte[] data);
|
||||
|
||||
/// <summary>
|
||||
/// 向指定客户端发送消息
|
||||
/// </summary>
|
||||
/// <param name="clientId">客户端ID</param>
|
||||
/// <param name="type">消息类型</param>
|
||||
/// <param name="data">消息数据</param>
|
||||
void SendToClient(string clientId, string type, object data);
|
||||
|
||||
/// <summary>
|
||||
/// 向指定客户端发送二进制消息
|
||||
/// </summary>
|
||||
/// <param name="clientId">客户端ID</param>
|
||||
/// <param name="data">二进制数据</param>
|
||||
void SendToClient(string clientId, byte[] data);
|
||||
|
||||
/// <summary>
|
||||
/// 检查服务器是否正在运行
|
||||
/// </summary>
|
||||
/// <returns>是否正在运行</returns>
|
||||
bool IsRunning();
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前连接的客户端数量
|
||||
/// </summary>
|
||||
/// <returns>客户端数量</returns>
|
||||
int GetClientCount();
|
||||
|
||||
/// <summary>
|
||||
/// 客户端连接事件
|
||||
/// </summary>
|
||||
event EventHandler<WebSocketClientConnectedEventArgs> ClientConnected;
|
||||
|
||||
/// <summary>
|
||||
/// 客户端断开事件
|
||||
/// </summary>
|
||||
event EventHandler<WebSocketClientDisconnectedEventArgs> ClientDisconnected;
|
||||
|
||||
/// <summary>
|
||||
/// 收到消息事件
|
||||
/// </summary>
|
||||
event EventHandler<WebSocketMessageEventArgs> MessageReceived;
|
||||
|
||||
/// <summary>
|
||||
/// 错误事件
|
||||
/// </summary>
|
||||
event EventHandler<WebSocketErrorEventArgs> Error;
|
||||
}
|
||||
|
||||
// 事件参数类
|
||||
/// <summary>
|
||||
/// 客户端连接事件参数
|
||||
/// </summary>
|
||||
public class WebSocketClientConnectedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// 客户端ID
|
||||
/// </summary>
|
||||
public string ClientId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 远程端点
|
||||
/// </summary>
|
||||
public string RemoteEndpoint { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 客户端断开事件参数
|
||||
/// </summary>
|
||||
public class WebSocketClientDisconnectedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// 客户端ID
|
||||
/// </summary>
|
||||
public string ClientId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 远程端点
|
||||
/// </summary>
|
||||
public string RemoteEndpoint { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 关闭代码
|
||||
/// </summary>
|
||||
public int CloseCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 关闭原因
|
||||
/// </summary>
|
||||
public string CloseReason { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,515 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Timers;
|
||||
using System.Threading;
|
||||
using WebSocketSharp;
|
||||
|
||||
namespace JoyD.Windows.CS.WebSocket
|
||||
{
|
||||
/// <summary>
|
||||
/// WebSocket客户端实现
|
||||
/// </summary>
|
||||
public class WebSocketClient : IWebSocketClient
|
||||
{
|
||||
private WebSocketSharp.WebSocket _webSocket;
|
||||
private WebSocketConfig _config;
|
||||
private System.Timers.Timer _heartbeatTimer;
|
||||
private System.Timers.Timer _reconnectTimer;
|
||||
private System.Timers.Timer _connectTimeoutTimer;
|
||||
private List<object> _messageQueue;
|
||||
private string _status = "disconnected";
|
||||
private int _reconnectAttempts = 0;
|
||||
private int _reconnectDelay = 1000;
|
||||
|
||||
// 事件
|
||||
/// <summary>
|
||||
/// 连接成功事件
|
||||
/// </summary>
|
||||
public event EventHandler Connected;
|
||||
|
||||
/// <summary>
|
||||
/// 连接断开事件
|
||||
/// </summary>
|
||||
public event EventHandler Disconnected;
|
||||
|
||||
/// <summary>
|
||||
/// 错误事件
|
||||
/// </summary>
|
||||
public event EventHandler<WebSocketErrorEventArgs> Error;
|
||||
|
||||
/// <summary>
|
||||
/// 收到消息事件
|
||||
/// </summary>
|
||||
public event EventHandler<WebSocketMessageEventArgs> Message;
|
||||
|
||||
/// <summary>
|
||||
/// 状态改变事件
|
||||
/// </summary>
|
||||
public event EventHandler<WebSocketStatusChangedEventArgs> StatusChanged;
|
||||
|
||||
/// <summary>
|
||||
/// 正在连接事件
|
||||
/// </summary>
|
||||
public event EventHandler Connecting;
|
||||
|
||||
/// <summary>
|
||||
/// 正在重连事件
|
||||
/// </summary>
|
||||
public event EventHandler Reconnecting;
|
||||
|
||||
/// <summary>
|
||||
/// 消息已发送事件
|
||||
/// </summary>
|
||||
public event EventHandler<WebSocketMessageEventArgs> MessageSent;
|
||||
|
||||
/// <summary>
|
||||
/// 消息已加入队列事件
|
||||
/// </summary>
|
||||
public event EventHandler<WebSocketMessageEventArgs> MessageQueued;
|
||||
|
||||
/// <summary>
|
||||
/// 消息发送失败事件
|
||||
/// </summary>
|
||||
public event EventHandler<WebSocketMessageFailedEventArgs> MessageFailed;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="config">配置参数</param>
|
||||
public WebSocketClient(WebSocketConfig config = null)
|
||||
{
|
||||
_config = config ?? new WebSocketConfig();
|
||||
_messageQueue = new List<object>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 连接WebSocket服务器
|
||||
/// </summary>
|
||||
public void Connect()
|
||||
{
|
||||
if (_webSocket != null && _webSocket.ReadyState == WebSocketSharp.WebSocketState.Open)
|
||||
{
|
||||
Log("WebSocket已连接,跳过本次连接");
|
||||
return;
|
||||
}
|
||||
|
||||
ChangeStatus("connecting");
|
||||
Connecting?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
try
|
||||
{
|
||||
_webSocket?.Close();
|
||||
_webSocket = new WebSocketSharp.WebSocket(_config.WsUrl);
|
||||
|
||||
// 设置事件处理
|
||||
_webSocket.OnOpen += WebSocket_OnOpen;
|
||||
_webSocket.OnMessage += WebSocket_OnMessage;
|
||||
_webSocket.OnClose += WebSocket_OnClose;
|
||||
_webSocket.OnError += WebSocket_OnError;
|
||||
|
||||
// 启动连接超时定时器
|
||||
StartConnectTimeoutTimer();
|
||||
|
||||
_webSocket.Connect();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
StopConnectTimeoutTimer();
|
||||
LogError("WebSocket连接失败: " + ex.Message);
|
||||
ChangeStatus("error");
|
||||
Error?.Invoke(this, new WebSocketErrorEventArgs { Exception = ex, Message = ex.Message });
|
||||
|
||||
if (_config.Reconnect)
|
||||
{
|
||||
ScheduleReconnect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 断开WebSocket连接
|
||||
/// </summary>
|
||||
public void Disconnect()
|
||||
{
|
||||
CancelReconnect();
|
||||
StopHeartbeat();
|
||||
StopConnectTimeoutTimer();
|
||||
|
||||
if (_webSocket != null)
|
||||
{
|
||||
_webSocket.Close();
|
||||
_webSocket = null;
|
||||
}
|
||||
|
||||
ChangeStatus("disconnected");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 重连WebSocket服务器
|
||||
/// </summary>
|
||||
public void Reconnect()
|
||||
{
|
||||
if (_webSocket != null && _webSocket.ReadyState == WebSocketSharp.WebSocketState.Connecting)
|
||||
{
|
||||
Log("WebSocket正在连接中,跳过本次重连");
|
||||
return;
|
||||
}
|
||||
|
||||
CancelReconnect();
|
||||
Connect();
|
||||
}
|
||||
|
||||
// 消息发送
|
||||
/// <summary>
|
||||
/// 发送消息
|
||||
/// </summary>
|
||||
/// <param name="type">消息类型</param>
|
||||
/// <param name="data">消息数据</param>
|
||||
public void Send(string type, object data)
|
||||
{
|
||||
var message = new { Type = type, Data = data };
|
||||
|
||||
if (_webSocket != null && _webSocket.ReadyState == WebSocketSharp.WebSocketState.Open)
|
||||
{
|
||||
try
|
||||
{
|
||||
var json = Newtonsoft.Json.JsonConvert.SerializeObject(message);
|
||||
_webSocket.Send(json);
|
||||
Log("发送消息: " + json);
|
||||
MessageSent?.Invoke(this, new WebSocketMessageEventArgs { Type = type, Data = data });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogError("发送消息失败: " + ex.Message);
|
||||
MessageFailed?.Invoke(this, new WebSocketMessageFailedEventArgs { Type = type, Data = data, Reason = "send_error", Exception = ex });
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (_messageQueue.Count < _config.MaxQueueSize)
|
||||
{
|
||||
_messageQueue.Add(message);
|
||||
Log("消息已加入队列: " + Newtonsoft.Json.JsonConvert.SerializeObject(message));
|
||||
MessageQueued?.Invoke(this, new WebSocketMessageEventArgs { Type = type, Data = data });
|
||||
}
|
||||
else
|
||||
{
|
||||
LogError("消息队列已满,丢弃消息: " + Newtonsoft.Json.JsonConvert.SerializeObject(message));
|
||||
MessageFailed?.Invoke(this, new WebSocketMessageFailedEventArgs { Type = type, Data = data, Reason = "queue_full" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送二进制消息
|
||||
/// </summary>
|
||||
/// <param name="data">二进制数据</param>
|
||||
public void Send(byte[] data)
|
||||
{
|
||||
if (_webSocket != null && _webSocket.ReadyState == WebSocketSharp.WebSocketState.Open)
|
||||
{
|
||||
try
|
||||
{
|
||||
_webSocket.Send(data);
|
||||
Log("发送二进制消息,长度: " + data.Length);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogError("发送二进制消息失败: " + ex.Message);
|
||||
MessageFailed?.Invoke(this, new WebSocketMessageFailedEventArgs { Data = data, Reason = "send_error", Exception = ex });
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// 二进制消息也加入队列
|
||||
if (_messageQueue.Count < _config.MaxQueueSize)
|
||||
{
|
||||
_messageQueue.Add(data);
|
||||
Log("二进制消息已加入队列,长度: " + data.Length);
|
||||
MessageQueued?.Invoke(this, new WebSocketMessageEventArgs { RawData = data });
|
||||
}
|
||||
else
|
||||
{
|
||||
LogError("消息队列已满,丢弃二进制消息,长度: " + data.Length);
|
||||
MessageFailed?.Invoke(this, new WebSocketMessageFailedEventArgs { Data = data, Reason = "queue_full" });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前连接状态
|
||||
/// </summary>
|
||||
/// <returns>连接状态</returns>
|
||||
public string GetStatus()
|
||||
{
|
||||
return _status;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查是否已连接
|
||||
/// </summary>
|
||||
/// <returns>是否已连接</returns>
|
||||
public bool IsConnected()
|
||||
{
|
||||
return _status == "connected";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取消息队列大小
|
||||
/// </summary>
|
||||
/// <returns>队列大小</returns>
|
||||
public int GetQueueSize()
|
||||
{
|
||||
return _messageQueue.Count;
|
||||
}
|
||||
|
||||
// 私有方法
|
||||
private void WebSocket_OnOpen(object sender, EventArgs e)
|
||||
{
|
||||
StopConnectTimeoutTimer();
|
||||
ChangeStatus("connected");
|
||||
_reconnectAttempts = 0;
|
||||
_reconnectDelay = 1000;
|
||||
|
||||
Log("WebSocket连接成功");
|
||||
Connected?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
StartHeartbeat();
|
||||
FlushQueue();
|
||||
}
|
||||
|
||||
private void WebSocket_OnMessage(object sender, MessageEventArgs e)
|
||||
{
|
||||
if (e.IsBinary)
|
||||
{
|
||||
Log("收到二进制消息,长度: " + e.RawData.Length);
|
||||
Message?.Invoke(this, new WebSocketMessageEventArgs { RawData = e.RawData });
|
||||
}
|
||||
else
|
||||
{
|
||||
Log("收到文本消息: " + e.Data);
|
||||
try
|
||||
{
|
||||
var message = Newtonsoft.Json.JsonConvert.DeserializeObject(e.Data);
|
||||
Message?.Invoke(this, new WebSocketMessageEventArgs { Data = message });
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogError("JSON解析错误: " + ex.Message);
|
||||
// 触发MessageFailed事件让上层感知
|
||||
MessageFailed?.Invoke(this, new WebSocketMessageFailedEventArgs { Data = e.Data, Reason = "json_parse_error", Exception = ex });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void WebSocket_OnClose(object sender, CloseEventArgs e)
|
||||
{
|
||||
StopConnectTimeoutTimer();
|
||||
StopHeartbeat();
|
||||
Log("WebSocket连接关闭: " + e.Code + " " + e.Reason);
|
||||
Disconnected?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
if (!e.WasClean && _config.Reconnect)
|
||||
{
|
||||
ChangeStatus("reconnecting");
|
||||
Reconnecting?.Invoke(this, EventArgs.Empty);
|
||||
ScheduleReconnect();
|
||||
}
|
||||
else
|
||||
{
|
||||
ChangeStatus("disconnected");
|
||||
}
|
||||
}
|
||||
|
||||
private void WebSocket_OnError(object sender, ErrorEventArgs e)
|
||||
{
|
||||
StopConnectTimeoutTimer();
|
||||
StopHeartbeat();
|
||||
LogError("WebSocket错误: " + e.Message);
|
||||
Error?.Invoke(this, new WebSocketErrorEventArgs { Exception = e.Exception, Message = e.Message });
|
||||
|
||||
if (_config.Reconnect)
|
||||
{
|
||||
ChangeStatus("reconnecting");
|
||||
Reconnecting?.Invoke(this, EventArgs.Empty);
|
||||
ScheduleReconnect();
|
||||
}
|
||||
else
|
||||
{
|
||||
ChangeStatus("error");
|
||||
}
|
||||
}
|
||||
|
||||
private void StartHeartbeat()
|
||||
{
|
||||
StopHeartbeat();
|
||||
_heartbeatTimer = new System.Timers.Timer(_config.HeartbeatInterval);
|
||||
_heartbeatTimer.Elapsed += (sender, e) =>
|
||||
{
|
||||
if (_webSocket != null && _webSocket.ReadyState == WebSocketSharp.WebSocketState.Open)
|
||||
{
|
||||
Send("ping", new { });
|
||||
}
|
||||
};
|
||||
_heartbeatTimer.Start();
|
||||
}
|
||||
|
||||
private void StopHeartbeat()
|
||||
{
|
||||
if (_heartbeatTimer != null)
|
||||
{
|
||||
_heartbeatTimer.Stop();
|
||||
_heartbeatTimer.Dispose();
|
||||
_heartbeatTimer = null;
|
||||
Log("已停止心跳");
|
||||
}
|
||||
}
|
||||
|
||||
private void StartConnectTimeoutTimer()
|
||||
{
|
||||
StopConnectTimeoutTimer();
|
||||
_connectTimeoutTimer = new System.Timers.Timer(_config.ConnectTimeout);
|
||||
_connectTimeoutTimer.Elapsed += (sender, e) =>
|
||||
{
|
||||
if (_webSocket != null && _webSocket.ReadyState == WebSocketSharp.WebSocketState.Connecting)
|
||||
{
|
||||
LogError("连接超时");
|
||||
_webSocket.Close();
|
||||
Error?.Invoke(this, new WebSocketErrorEventArgs { Message = "Connection timeout" });
|
||||
}
|
||||
};
|
||||
_connectTimeoutTimer.Start();
|
||||
}
|
||||
|
||||
private void StopConnectTimeoutTimer()
|
||||
{
|
||||
if (_connectTimeoutTimer != null)
|
||||
{
|
||||
_connectTimeoutTimer.Stop();
|
||||
_connectTimeoutTimer.Dispose();
|
||||
_connectTimeoutTimer = null;
|
||||
Log("已停止连接超时定时器");
|
||||
}
|
||||
}
|
||||
|
||||
private void ScheduleReconnect()
|
||||
{
|
||||
CancelReconnect();
|
||||
|
||||
var delay = Math.Min(_reconnectDelay, _config.MaxReconnectDelay);
|
||||
Log("准备重连,延迟: " + delay + "ms");
|
||||
|
||||
_reconnectTimer = new System.Timers.Timer(delay);
|
||||
_reconnectTimer.Elapsed += (sender, e) =>
|
||||
{
|
||||
_reconnectAttempts++;
|
||||
Log("开始重连,尝试次数: " + _reconnectAttempts);
|
||||
Connect();
|
||||
|
||||
if (_reconnectDelay < _config.MaxReconnectDelay)
|
||||
{
|
||||
_reconnectDelay *= 2;
|
||||
}
|
||||
};
|
||||
_reconnectTimer.Start();
|
||||
}
|
||||
|
||||
private void CancelReconnect()
|
||||
{
|
||||
if (_reconnectTimer != null)
|
||||
{
|
||||
_reconnectTimer.Stop();
|
||||
_reconnectTimer.Dispose();
|
||||
_reconnectTimer = null;
|
||||
Log("已取消重连计划");
|
||||
}
|
||||
}
|
||||
|
||||
private void FlushQueue()
|
||||
{
|
||||
int retryCount = 0;
|
||||
const int maxRetry = 3;
|
||||
|
||||
while (_messageQueue.Count > 0)
|
||||
{
|
||||
var msg = _messageQueue[0];
|
||||
_messageQueue.RemoveAt(0);
|
||||
|
||||
try
|
||||
{
|
||||
if (msg is byte[])
|
||||
{
|
||||
// 二进制消息
|
||||
_webSocket.Send((byte[])msg);
|
||||
Log("发送队列二进制消息,长度: " + ((byte[])msg).Length);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 文本消息
|
||||
var json = Newtonsoft.Json.JsonConvert.SerializeObject(msg);
|
||||
_webSocket.Send(json);
|
||||
Log("发送队列消息: " + json);
|
||||
}
|
||||
|
||||
// 触发MessageSent事件
|
||||
if (msg is byte[])
|
||||
{
|
||||
MessageSent?.Invoke(this, new WebSocketMessageEventArgs { RawData = (byte[])msg });
|
||||
}
|
||||
else
|
||||
{
|
||||
MessageSent?.Invoke(this, new WebSocketMessageEventArgs { Data = msg });
|
||||
}
|
||||
|
||||
retryCount = 0; // 重置重试计数
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
retryCount++;
|
||||
LogError("发送队列消息失败,重试次数: " + retryCount + "/" + maxRetry + ": " + ex.Message);
|
||||
|
||||
if (retryCount < maxRetry)
|
||||
{
|
||||
// 重试次数未达上限,重新加入队列
|
||||
_messageQueue.Insert(0, msg);
|
||||
Thread.Sleep(100 * retryCount); // 指数退避
|
||||
}
|
||||
else
|
||||
{
|
||||
// 重试次数达上限,触发失败事件
|
||||
MessageFailed?.Invoke(this, new WebSocketMessageFailedEventArgs { Data = msg, Reason = "send_error", Exception = ex });
|
||||
retryCount = 0; // 重置重试计数
|
||||
}
|
||||
|
||||
break; // 退出循环,避免持续失败
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ChangeStatus(string newStatus)
|
||||
{
|
||||
if (_status == newStatus)
|
||||
return;
|
||||
|
||||
var oldStatus = _status;
|
||||
_status = newStatus;
|
||||
StatusChanged?.Invoke(this, new WebSocketStatusChangedEventArgs { OldStatus = oldStatus, NewStatus = newStatus });
|
||||
}
|
||||
|
||||
private void Log(string message)
|
||||
{
|
||||
if (_config.DebugMode)
|
||||
{
|
||||
Console.WriteLine("[CubeWebSocket] " + message);
|
||||
}
|
||||
}
|
||||
|
||||
private void LogError(string message)
|
||||
{
|
||||
if (_config.DebugMode)
|
||||
{
|
||||
Console.Error.WriteLine("[CubeWebSocket] " + message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
|
||||
namespace JoyD.Windows.CS.WebSocket
|
||||
{
|
||||
/// <summary>
|
||||
/// WebSocket配置类
|
||||
/// </summary>
|
||||
public class WebSocketConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// WebSocket服务器URL
|
||||
/// </summary>
|
||||
public string WsUrl { get; set; } = "ws://localhost:8086/ws";
|
||||
|
||||
/// <summary>
|
||||
/// 是否自动连接
|
||||
/// </summary>
|
||||
public bool AutoConnect { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 是否自动重连
|
||||
/// </summary>
|
||||
public bool Reconnect { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// 最大重连延迟
|
||||
/// </summary>
|
||||
public int MaxReconnectDelay { get; set; } = 30000;
|
||||
|
||||
/// <summary>
|
||||
/// 是否启用调试模式
|
||||
/// </summary>
|
||||
public bool DebugMode { get; set; } = false;
|
||||
|
||||
/// <summary>
|
||||
/// 心跳间隔
|
||||
/// </summary>
|
||||
public int HeartbeatInterval { get; set; } = 30000;
|
||||
|
||||
/// <summary>
|
||||
/// 连接超时时间
|
||||
/// </summary>
|
||||
public int ConnectTimeout { get; set; } = 10000;
|
||||
|
||||
/// <summary>
|
||||
/// 消息队列最大大小
|
||||
/// </summary>
|
||||
public int MaxQueueSize { get; set; } = 100;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
|
||||
namespace JoyD.Windows.CS.WebSocket
|
||||
{
|
||||
/// <summary>
|
||||
/// WebSocket错误事件参数
|
||||
/// </summary>
|
||||
public class WebSocketErrorEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// 异常对象
|
||||
/// </summary>
|
||||
public Exception Exception { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误消息
|
||||
/// </summary>
|
||||
public string Message { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
|
||||
namespace JoyD.Windows.CS.WebSocket
|
||||
{
|
||||
/// <summary>
|
||||
/// WebSocket消息事件参数
|
||||
/// </summary>
|
||||
public class WebSocketMessageEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// 消息类型
|
||||
/// </summary>
|
||||
public string Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 消息数据
|
||||
/// </summary>
|
||||
public object Data { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 原始二进制数据
|
||||
/// </summary>
|
||||
public byte[] RawData { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
|
||||
namespace JoyD.Windows.CS.WebSocket
|
||||
{
|
||||
/// <summary>
|
||||
/// WebSocket消息失败事件参数
|
||||
/// </summary>
|
||||
public class WebSocketMessageFailedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// 消息类型
|
||||
/// </summary>
|
||||
public string Type { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 消息数据
|
||||
/// </summary>
|
||||
public object Data { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 失败原因
|
||||
/// </summary>
|
||||
public string Reason { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 异常对象
|
||||
/// </summary>
|
||||
public Exception Exception { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using WebSocketSharp.Server;
|
||||
|
||||
namespace JoyD.Windows.CS.WebSocket
|
||||
{
|
||||
/// <summary>
|
||||
/// WebSocket服务器实现
|
||||
/// </summary>
|
||||
public class WebSocketServer : IWebSocketServer
|
||||
{
|
||||
private WebSocketSharp.Server.WebSocketServer _server;
|
||||
private Dictionary<string, WebSocketBehavior> _sessions;
|
||||
|
||||
// 事件
|
||||
/// <summary>
|
||||
/// 客户端连接事件
|
||||
/// </summary>
|
||||
public event EventHandler<WebSocketClientConnectedEventArgs> ClientConnected;
|
||||
|
||||
/// <summary>
|
||||
/// 客户端断开事件
|
||||
/// </summary>
|
||||
public event EventHandler<WebSocketClientDisconnectedEventArgs> ClientDisconnected;
|
||||
|
||||
/// <summary>
|
||||
/// 收到消息事件
|
||||
/// </summary>
|
||||
public event EventHandler<WebSocketMessageEventArgs> MessageReceived;
|
||||
|
||||
/// <summary>
|
||||
/// 错误事件
|
||||
/// </summary>
|
||||
public event EventHandler<WebSocketErrorEventArgs> Error;
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
public WebSocketServer()
|
||||
{
|
||||
_sessions = new Dictionary<string, WebSocketBehavior>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 启动服务器
|
||||
/// </summary>
|
||||
/// <param name="port">端口号</param>
|
||||
public void Start(int port)
|
||||
{
|
||||
try
|
||||
{
|
||||
_server = new WebSocketSharp.Server.WebSocketServer(port);
|
||||
_server.AddWebSocketService<EasyWsBehavior>("/");
|
||||
|
||||
// 行为设置已在AddWebSocketService时完成
|
||||
|
||||
_server.Start();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Error?.Invoke(this, new WebSocketErrorEventArgs { Exception = ex, Message = ex.Message });
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 停止服务器
|
||||
/// </summary>
|
||||
public void Stop()
|
||||
{
|
||||
if (_server != null && _server.IsListening)
|
||||
{
|
||||
_server.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 广播消息
|
||||
/// </summary>
|
||||
/// <param name="type">消息类型</param>
|
||||
/// <param name="data">消息数据</param>
|
||||
public void Broadcast(string type, object data)
|
||||
{
|
||||
if (_server != null)
|
||||
{
|
||||
_server.WebSocketServices["/"].Sessions.Broadcast(Newtonsoft.Json.JsonConvert.SerializeObject(new { Type = type, Data = data }));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 广播二进制消息
|
||||
/// </summary>
|
||||
/// <param name="data">二进制数据</param>
|
||||
public void Broadcast(byte[] data)
|
||||
{
|
||||
if (_server != null)
|
||||
{
|
||||
_server.WebSocketServices["/"].Sessions.Broadcast(data);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 向指定客户端发送消息
|
||||
/// </summary>
|
||||
/// <param name="clientId">客户端ID</param>
|
||||
/// <param name="type">消息类型</param>
|
||||
/// <param name="data">消息数据</param>
|
||||
public void SendToClient(string clientId, string type, object data)
|
||||
{
|
||||
if (_server != null)
|
||||
{
|
||||
_server.WebSocketServices["/"].Sessions.SendTo(Newtonsoft.Json.JsonConvert.SerializeObject(new { Type = type, Data = data }), clientId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 向指定客户端发送二进制消息
|
||||
/// </summary>
|
||||
/// <param name="clientId">客户端ID</param>
|
||||
/// <param name="data">二进制数据</param>
|
||||
public void SendToClient(string clientId, byte[] data)
|
||||
{
|
||||
if (_server != null)
|
||||
{
|
||||
_server.WebSocketServices["/"].Sessions.SendTo(data, clientId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 检查服务器是否正在运行
|
||||
/// </summary>
|
||||
/// <returns>是否正在运行</returns>
|
||||
public bool IsRunning()
|
||||
{
|
||||
return _server != null && _server.IsListening;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前连接的客户端数量
|
||||
/// </summary>
|
||||
/// <returns>客户端数量</returns>
|
||||
public int GetClientCount()
|
||||
{
|
||||
return _sessions.Count;
|
||||
}
|
||||
|
||||
internal void OnClientConnected(EasyWsBehavior session)
|
||||
{
|
||||
// Replace existing session entry if ID is reused
|
||||
if (_sessions.ContainsKey(session.ID))
|
||||
{
|
||||
_sessions.Remove(session.ID);
|
||||
}
|
||||
_sessions[session.ID] = session;
|
||||
|
||||
var args = new WebSocketClientConnectedEventArgs
|
||||
{
|
||||
ClientId = session.ID,
|
||||
RemoteEndpoint = "unknown"
|
||||
};
|
||||
ClientConnected?.Invoke(this, args);
|
||||
}
|
||||
|
||||
internal void OnClientDisconnected(EasyWsBehavior session, int closeCode, string closeReason)
|
||||
{
|
||||
if (_sessions.ContainsKey(session.ID))
|
||||
{
|
||||
_sessions.Remove(session.ID);
|
||||
|
||||
var args = new WebSocketClientDisconnectedEventArgs
|
||||
{
|
||||
ClientId = session.ID,
|
||||
RemoteEndpoint = "unknown",
|
||||
CloseCode = closeCode,
|
||||
CloseReason = closeReason
|
||||
};
|
||||
ClientDisconnected?.Invoke(this, args);
|
||||
}
|
||||
}
|
||||
|
||||
internal void OnMessageReceived(EasyWsBehavior session, string message)
|
||||
{
|
||||
try
|
||||
{
|
||||
var data = Newtonsoft.Json.JsonConvert.DeserializeObject(message);
|
||||
var args = new WebSocketMessageEventArgs { Data = data };
|
||||
MessageReceived?.Invoke(this, args);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Error?.Invoke(this, new WebSocketErrorEventArgs { Exception = ex, Message = ex.Message });
|
||||
}
|
||||
}
|
||||
|
||||
internal void OnMessageReceived(EasyWsBehavior session, byte[] data)
|
||||
{
|
||||
var args = new WebSocketMessageEventArgs { RawData = data };
|
||||
MessageReceived?.Invoke(this, args);
|
||||
}
|
||||
}
|
||||
|
||||
// 内部使用的WebSocket行为类
|
||||
internal class EasyWsBehavior : WebSocketBehavior
|
||||
{
|
||||
internal WebSocketServer Server { get; set; }
|
||||
|
||||
protected override void OnOpen()
|
||||
{
|
||||
base.OnOpen();
|
||||
Server?.OnClientConnected(this);
|
||||
}
|
||||
|
||||
protected override void OnClose(WebSocketSharp.CloseEventArgs e)
|
||||
{
|
||||
base.OnClose(e);
|
||||
Server?.OnClientDisconnected(this, e.Code, e.Reason);
|
||||
}
|
||||
|
||||
protected override void OnMessage(WebSocketSharp.MessageEventArgs e)
|
||||
{
|
||||
base.OnMessage(e);
|
||||
if (e.IsBinary)
|
||||
{
|
||||
Server?.OnMessageReceived(this, e.RawData);
|
||||
}
|
||||
else
|
||||
{
|
||||
Server?.OnMessageReceived(this, e.Data);
|
||||
}
|
||||
}
|
||||
|
||||
internal void Broadcast(string type, object data)
|
||||
{
|
||||
var message = new { Type = type, Data = data };
|
||||
var json = Newtonsoft.Json.JsonConvert.SerializeObject(message);
|
||||
Sessions.Broadcast(json);
|
||||
}
|
||||
|
||||
internal void Broadcast(byte[] data)
|
||||
{
|
||||
Sessions.Broadcast(data);
|
||||
}
|
||||
|
||||
internal void SendToClient(string clientId, string type, object data)
|
||||
{
|
||||
// 直接发送,不检查会话是否存在
|
||||
var message = new { Type = type, Data = data };
|
||||
var json = Newtonsoft.Json.JsonConvert.SerializeObject(message);
|
||||
Sessions.SendTo(json, clientId);
|
||||
}
|
||||
|
||||
internal void SendToClient(string clientId, byte[] data)
|
||||
{
|
||||
// 直接发送,不检查会话是否存在
|
||||
Sessions.SendTo(data, clientId);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
|
||||
namespace JoyD.Windows.CS.WebSocket
|
||||
{
|
||||
/// <summary>
|
||||
/// WebSocket状态改变事件参数
|
||||
/// </summary>
|
||||
public class WebSocketStatusChangedEventArgs : EventArgs
|
||||
{
|
||||
/// <summary>
|
||||
/// 旧状态
|
||||
/// </summary>
|
||||
public string OldStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 新状态
|
||||
/// </summary>
|
||||
public string NewStatus { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace JoyD.Windows.CS.WebSocket
|
||||
{
|
||||
/// <summary>
|
||||
/// WebSocket测试类
|
||||
/// </summary>
|
||||
public class WebSocketTest
|
||||
{
|
||||
/// <summary>
|
||||
/// 测试WebSocket客户端
|
||||
/// </summary>
|
||||
public static void TestClient()
|
||||
{
|
||||
Console.WriteLine("开始测试WebSocket客户端...");
|
||||
|
||||
var config = new WebSocketConfig
|
||||
{
|
||||
WsUrl = "ws://localhost:8086/ws",
|
||||
DebugMode = true
|
||||
};
|
||||
|
||||
var client = new WebSocketClient(config);
|
||||
|
||||
// 订阅事件
|
||||
client.Connected += (sender, e) => Console.WriteLine("连接成功");
|
||||
client.Disconnected += (sender, e) => Console.WriteLine("连接断开");
|
||||
client.Error += (sender, e) => Console.WriteLine("错误: " + e.Message);
|
||||
client.Message += (sender, e) => Console.WriteLine("收到消息: " + e.Data);
|
||||
|
||||
// 连接服务器
|
||||
client.Connect();
|
||||
|
||||
// 发送测试消息
|
||||
Thread.Sleep(1000);
|
||||
client.Send("test", new { Message = "Hello, WebSocket!" });
|
||||
|
||||
Console.WriteLine("按任意键退出...");
|
||||
Console.ReadKey();
|
||||
|
||||
// 断开连接
|
||||
client.Disconnect();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 测试WebSocket服务器
|
||||
/// </summary>
|
||||
public static void TestServer()
|
||||
{
|
||||
Console.WriteLine("开始测试WebSocket服务器...");
|
||||
|
||||
var server = new WebSocketServer();
|
||||
|
||||
// 订阅事件
|
||||
server.ClientConnected += (sender, e) => Console.WriteLine("客户端连接: " + e.ClientId);
|
||||
server.ClientDisconnected += (sender, e) => Console.WriteLine("客户端断开: " + e.ClientId);
|
||||
server.MessageReceived += (sender, e) => Console.WriteLine("收到消息: " + e.Data);
|
||||
|
||||
// 启动服务器
|
||||
server.Start(8086);
|
||||
Console.WriteLine("服务器已启动,端口: 8086");
|
||||
|
||||
Console.WriteLine("按任意键停止服务器...");
|
||||
Console.ReadKey();
|
||||
|
||||
// 停止服务器
|
||||
server.Stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
4
Windows/CS/Framework4.0/CubeLib/CubeLib/packages.config
Normal file
4
Windows/CS/Framework4.0/CubeLib/CubeLib/packages.config
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<packages>
|
||||
<package id="Newtonsoft.Json" version="13.0.4" targetFramework="net40" />
|
||||
</packages>
|
||||
Reference in New Issue
Block a user