WPF桌面端开发13-Websocket

前言

大部分Socket相关的库都要求.Net Framework 4及以上,

为了支持3.5试了好多的库。

现在还是切换到了4.0的环境,支持3.5的库没有找到合适没问题的。

NuGet安装包重新安装

如果我们升级版本或者降低版本,引入的库也要相应的升级和降级,最简单的方式是重新安装所有的库

方式如下:

工具=>NuGet包管理器=>程序包管理器控制台

输入

1
Update-Package -reinstall

SuperWebSocket(服务端)(.Net Framework)

推荐使用

SuperWebSocket :https://github.com/kerryjiang/SuperWebSocket

  • 服务端

  • 运行环境 .Net Framework 4

安装

1
install-package SuperWebSocket

工具类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
namespace z_remote_control.Utils
{
using System;
using System.Threading.Tasks;
using SuperSocket.SocketBase;
using SuperSocket.SocketBase.Config;
using SuperSocket.SocketEngine;
using SuperWebSocket;

public class ZwsServer : IDisposable
{
public event Action<WebSocketSession, string> MessageReceived;
public event Action<WebSocketSession, byte[]> MessageByteReceived;

public event Action<WebSocketSession> NewConnected;

public event Action<WebSocketSession> Closed;

private static ZwsServer _mWsServer;
private WebSocketServer _wsserrver;

public static ZwsServer GetInstance()
{
return _mWsServer ?? (_mWsServer = new ZwsServer());
}

/// <summary>
/// 开启服务端
/// </summary>
/// <param name="port"></param>
/// <param name="isUseCertificate"></param>
/// <param name="serverStoreName"></param>
/// <param name="serverSecurity"></param>
/// <param name="serverThumbprint"></param>
/// <returns></returns>
public bool Start
(
int port,
bool isUseCertificate = false,
string serverStoreName = "",
string serverSecurity = "",
string serverThumbprint = ""
)
{
bool isSetuped = false;
try
{
_wsserrver = new WebSocketServer();
var serverConfig = new ServerConfig
{
Name = "ZWSServer",
MaxConnectionNumber = 100, //最大允许的客户端连接数目,默认为100。
MaxRequestLength = 1024 * 1024 * 3, //设置请求的数据最大长度
Mode = SocketMode.Tcp,
Port = port, //服务器监听的端口。
ClearIdleSession = false, //true或者false, 是否清除空闲会话,默认为false。
ClearIdleSessionInterval = 120, //清除空闲会话的时间间隔,默认为120,单位为秒。
ListenBacklog = 10,
ReceiveBufferSize = 64 * 1024, //用于接收数据的缓冲区大小,默认为2048。
SendBufferSize = 64 * 1024, //用户发送数据的缓冲区大小,默认为2048。
KeepAliveTime = 10, //多长时间没有收到数据开始发送心跳包 单位秒。
KeepAliveInterval = 1, //发送心跳包的时间间隔 单位秒。
SyncSend = false
};
SocketServerFactory socketServerFactory = null;
//开启wss 使用证书
if (isUseCertificate)
{
serverConfig.Security = serverSecurity;
serverConfig.Certificate = new CertificateConfig
{
StoreName = serverStoreName,
StoreLocation = System.Security.Cryptography.X509Certificates.StoreLocation.LocalMachine,
Thumbprint = serverThumbprint
};
socketServerFactory = new SocketServerFactory();
}
isSetuped = _wsserrver.Setup(
new RootConfig(),
serverConfig,
socketServerFactory
);
_wsserrver.NewSessionConnected += NewSessionConnected;
_wsserrver.NewMessageReceived += NewMessageReceived;
_wsserrver.NewDataReceived += NewDataReceived;
_wsserrver.SessionClosed += SessionClosed;
isSetuped = _wsserrver.Start();
if (isSetuped)
{
Console.WriteLine(@"Start Success...");
Console.WriteLine(@"Server Listen at " + _wsserrver.Listeners[0].EndPoint.Port.ToString());
}
else
{
Console.WriteLine(@"Failed to start!");
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
return isSetuped;
}

/// <summary>
/// 新链接触发事件
/// </summary>
/// <param name="session"></param>
private void NewSessionConnected(WebSocketSession session)
{
try
{
string message = string.Format(
"Session Open:{0}, Path:{1}, Host:{2}, IP:{3}",
session.SessionID,
session.Path,
session.Host,
session.RemoteEndPoint
);
Console.WriteLine(message);
NewConnected?.Invoke(session);
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}

/// <summary>
/// 客户端链接关闭触发事件
/// </summary>
/// <param name="session"></param>
/// <param name="value"></param>
private void SessionClosed
(
WebSocketSession session,
CloseReason value
)
{
string message = string.Format(
"Session Close:{0} Reason:{1}, Path:{2}, IP:{3}",
session.SessionID,
value.ToString(),
session.Path,
session.RemoteEndPoint
);
Console.WriteLine(message);
Closed?.Invoke(session);
}

/// <summary>
/// 消息触发事件
/// </summary>
/// <param name="session"></param>
/// <param name="value"></param>
private void NewMessageReceived
(
WebSocketSession session,
string value
)
{
try
{
Console.WriteLine(@"Receive:" + value + @" ClientIP:" + session.RemoteEndPoint);
MessageReceived?.Invoke(
session,
value
);
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}

private void NewDataReceived
(
WebSocketSession session,
byte[] value
)
{
if (value.Length == 1)
{
string message = string.Format(
"接收到心跳:{0}, Path:{1}, Host:{2}, IP:{3}",
session.SessionID,
session.Path,
session.Host,
session.RemoteEndPoint
);
Console.WriteLine(message);
}
else
{
MessageByteReceived?.Invoke(
session,
value
);
}
}

/// <summary>
/// 关闭服务端触发事件
/// </summary>
public void Dispose()
{
foreach (WebSocketSession session in _wsserrver.GetAllSessions())
{
session.Close();
}
try
{
_wsserrver.Stop();
}
catch
{
// ignored
}
}

/// <summary>
/// 发送消息
/// </summary>
/// <param name="session">
/// 客户端连接
/// </param>
/// <param name="message">
/// 消息内容
/// </param>
public void SendMessage
(
WebSocketSession session,
string message
)
{
Task.Factory.StartNew(
() =>
{
if (session != null && session.Connected)
{
try
{
session.Send(message);
}
catch (Exception)
{
// ignored
}
}
}
);
}

/// <summary>
/// 发送文件
/// </summary>
/// <param name="session">
/// 客户端连接
/// </param>
/// <param name="file">
/// 文件
/// </param>
public void SendMessage
(
WebSocketSession session,
byte[] file
)
{
Task.Factory.StartNew(
() =>
{
if (session != null && session.Connected)
{
try
{
session.Send(
file,
0,
file.Length
);
}
catch (Exception)
{
// ignored
}
}
}
);
}
}
}

调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
private void StartServer()
{
new Thread(
s =>
{
ZwsServer wsServer = ZwsServer.GetInstance();
wsServer.Start(10088);
wsServer.MessageReceived += WsServer_MessageReceived;
wsServer.MessageByteReceived += WsServer_MessageByteReceived;
}
) { IsBackground = true }.Start();
}

private void WsServer_MessageByteReceived
(
SuperWebSocket.WebSocketSession session,
byte[] msg
)
{
}

private void WsServer_MessageReceived
(
SuperWebSocket.WebSocketSession session,
string msg
)
{
}

JS发送心跳包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//心跳包
function heartCheck() {
if (window.ws_inter) {
clearInterval(window.ws_inter);
}
window.ws_inter = setInterval(function () {
if (socket && socket.readyState == socket.OPEN) {
let buffer = new ArrayBuffer(1); // 初始化1个Byte的二进制数据缓冲区
let dataView = new DataView(buffer);
dataView.setInt8(0, 127);
socket.send(dataView);
console.info("发送心跳", " 时间(s):" + parseInt(new Date().getTime() / 1000));
} else {
connect_ws();
}
}, 10000);
}

注意

MaxConnectionNumber不要设置太高,会非常占用内存,导致程序内存不足,要根据实际情况设置,我之前设置的10000,开启服务就会多占用大概600-700M,当程序将近1G时,就会报内存不足。

发送消息的时候要用线程池,否则群发信息的时候如果有用户网络中断,会导致给其它用户发消息也卡顿。

对比

ClearIdleSessionInterval和KeepAliveTime和KeepAliveInterval

  • KeepAliveTime 多长时间没有收到数据开始发送心跳包 单位秒

  • KeepAliveInterval 发送心跳包的时间间隔 单位秒

注意

如果想让断网的用户尽可能短的时间从服务端断开只需要调整KeepAliveIntervalKeepAliveTime即可,不用修改ClearIdleSessionInterval

但是ClearIdleSessionInterval的时间一定要大于客户端心跳包的时间间隔。

KeepAliveTime可以小于心跳包的时间间隔,但是建议大于等于客户端心跳包的时间。

默认值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public ServerConfig()
{
Security = "None";
MaxConnectionNumber = 100;
Mode = SocketMode.Tcp;
MaxRequestLength = 1024;
KeepAliveTime = 600;
KeepAliveInterval = 60;
ListenBacklog = 100;
ReceiveBufferSize = 4096;
SendingQueueSize = 5;
SendTimeOut = 5000;
ClearIdleSessionInterval = 120;
IdleSessionTimeOut = 300;
}

常见问题

发送数据过长客户端断开连接

配置

1
MaxRequestLength = 1024 * 1024

报错:

System.IO.FileNotFoundException:“未能加载文件或程序集“SuperSocket.SocketEngine”或它的某一个依赖项。系统找不到指定的文件。”

原因是DLL文件没有自动发布到目标目录中

我们就用命令复制

项目->属性->生成事件->生成前事件命令行

添加如下

1
2
xcopy /Y /d $(ProjectDir)\packages\SuperWebSocket.0.9.0.2\lib\net40\SuperSocket.SocketEngine.dll $(TargetDir)
xcopy /Y /d $(ProjectDir)\packages\SuperWebSocket.0.9.0.2\lib\net40\SuperSocket.SocketEngine.pdb $(TargetDir)

这样就大功告成了。

Fleck(服务端)

https://github.com/statianzo/Fleck

应该不错 Star比较多。

安装

1
Install-Package Fleck -Version 1.2.0

WebSocketSharp(客户端/服务端)

https://github.com/sta/websocket-sharp

应该也不错

安装

1
Install-Package WebSocketSharp -Version 1.0.3-rc11

SuperWebSocketNETServer(服务端)(.Net Framework)

该库也有问题,当客户端发送的数据过大(比如包含图片的Base64)时发送失败。

官网:https://archive.codeplex.com/?p=superwebsocket

  • 服务端

  • 运行环境 .Net Framework 4

项目右键=>管理Nuget程序包,搜索 SuperWebSocketNETServer,点击右侧 安装

安装

1
Install-Package SuperWebSocketNETServer -Version 0.8.0

使用

建立服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private WebSocketServer wsserver = null;
public void init()
{
WebSocketServer wsserver = new WebSocketServer();//监听所有的的地址
wsserver.NewSessionConnected += Ws_NewSessionConnected;
wsserver.SessionClosed += Ws_SessionClosed;
wsserver.NewMessageReceived += Ws_NewMessageReceived;
wsserver.NewDataReceived += Ws_NewDataReceived;
if (!wsserver.Setup(10007))
{
Console.WriteLine("WS: 设置WebSocket服务侦听地址失败");
return;
}

if (!wsserver.Start())
{
Console.WriteLine("WS: 启动WebSocket服务侦听失败");
return;
}
}

关闭服务

1
2
3
4
5
6
7
public void closeSocket()
{
if (wsserver.State == ServerState.Running)
{
wsserver.Stop();
}
}

服务器状态

1
2
3
4
5
6
7
8
9
public enum ServerState
{
NotInitialized = 0,
Initializing = 1,
NotStarted = 2,
Starting = 3,
Running = 4,
Stopping = 5
}

客户连接回调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
private static List<ZSocketsUser> socketsList = new List<ZSocketsUser>();

private void Ws_NewSessionConnected(WebSocketSession session)
{
socketsList.Add(
new ZSocketsUser()
{
Id = session.SessionID,
session = session
}
);

Console.WriteLine("WS:用户上线:" + session.SessionID);
Console.WriteLine("WS:在线用户数量:" + socketsList.Count);
}

private void Ws_SessionClosed(WebSocketSession session, CloseReason value)
{
var su = getSocketUserById(session.SessionID);
if (su != null)
{
socketsList.Remove(su);
}
Console.WriteLine("WS:断开连接:" + session.SessionID);
Console.WriteLine("WS:在线用户数量:" + socketsList.Count);
}

private void Ws_NewDataReceived(WebSocketSession session, byte[] file)
{
if (file.Length > 1)
{
foreach (var item in socketsList)
{
if (item.session.Connected) {
item.session.Send(file, 0, file.Length);
}
}
}
}

private void Ws_NewMessageReceived(WebSocketSession session, string message)
{

}

发送信息时要注意客户端的连接状态

1
2
3
4
5
6
if(session.Connected){
//发送二进制
session.Send(file, 0, file.Length);
//发送字符串
session.Send(str);
}

其中ZSocketsUser是我为了保存上线用户创建的类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ZSocketsUser
{
public string Id { get; set; }

public WebSocketSession session { get; set; }

public int userid { get; set; }

public string username { get; set; }

public string userpic { get; set; }

public int usertype { get; set; }
}

辅助方法

1
2
3
4
5
6
7
8
9
10
11
12
private ZSocketsUser getSocketUserById(string id)
{
foreach (var su in socketsList)
{
if (su.Id == id)
{
return su;
}
}

return null;
}

TouchSocket(服务端/客户端)(.Net6)

项目简介

https://gitee.com/dotnetchina/TouchSocket

支持

  • .NET 5.0

  • .NET Core 3.1

  • .NET Standard 2.0

  • .NET Framework 4.5

这个库优点

可以同时作为WS服务器和静态文件服务器。

安装

1
Install-Package TouchSocket -Version 0.5.2

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
using System;
using System.Diagnostics;

using TouchSocket.Core;
using TouchSocket.Core.Config;
using TouchSocket.Core.Dependency;
using TouchSocket.Core.Log;
using TouchSocket.Core.Plugins;
using TouchSocket.Http;
using TouchSocket.Http.Plugins;
using TouchSocket.Http.WebSockets;
using TouchSocket.Http.WebSockets.Plugins;
using TouchSocket.Rpc.WebApi;
using TouchSocket.Sockets;

namespace Net6WSServer.Utils
{
public class ZWSServer : IDisposable
{
private static HttpService? wsServer;

public static void init()
{
wsServer = new HttpService();

wsServer.Setup(
new TouchSocketConfig()
.UsePlugin()
.SetListenIPHosts(new IPHost[] { new IPHost(10077) })
.ConfigureContainer(a =>
{
a.SetSingletonLogger<ConsoleLogger>();
})
.ConfigurePlugins(a =>
{
//添加静态页面
a
.Add<HttpStaticPagePlugin>()
.AddFolder("./wwwroot/");

//添加WebSocket功能
a.Add<WebSocketServerPlugin>()
.SetWSUrl("/")
.SetCallback(WSCallback);//WSCallback回调函数是在WS收到数据时触发回调的。
a.Add<MyWebSocketPlugin>();//MyWebSocketPlugin是继承自WebSocketPluginBase的插件。
a.Add<WebApiParserPlugin>();
})
)
.Start();

Trace.WriteLine("服务器已启动,可使用下列地址连接");
Trace.WriteLine("浏览器访问:http://127.0.0.1:10077/index.html");
Trace.WriteLine("ws://127.0.0.1:10077");
}

private static void WSCallback(ITcpClientBase client, WSDataFrameEventArgs e)
{
switch (e.DataFrame.Opcode)
{
case WSDataType.Cont:
Trace.WriteLine($"收到中间数据,长度为:{e.DataFrame.PayloadLength}");
break;

case WSDataType.Text:
Trace.WriteLine(e.DataFrame.ToText());
((HttpSocketClient)client).SendWithWS(e.DataFrame.ToText());
break;

case WSDataType.Binary:
if (e.DataFrame.FIN)
{
Trace.WriteLine($"收到二进制数据,长度为:{e.DataFrame.PayloadLength}");
}
else
{
Trace.WriteLine($"收到未结束的二进制数据,长度为:{e.DataFrame.PayloadLength}");
}
break;

case WSDataType.Close:
{
Trace.WriteLine("WebSocket断开连接");
client.Close("断开");
}

break;

case WSDataType.Ping:
break;

case WSDataType.Pong:
break;

default:
break;
}
}

public void Dispose()
{
if (wsServer != null)
{
wsServer.Dispose();
}
}

public class MyWebSocketPlugin : WebSocketPluginBase
{
protected override void OnConnected(ITcpClientBase client, TouchSocketEventArgs e)
{
Trace.WriteLine("TCP连接");
base.OnConnected(client, e);
}

protected override void OnHandshaking(ITcpClientBase client, HttpContextEventArgs e)
{
Trace.WriteLine("WebSocket正在连接");
//e.IsPermitOperation = false;表示拒绝
base.OnHandshaking(client, e);
}

protected override void OnHandshaked(ITcpClientBase client, HttpContextEventArgs e)
{
Trace.WriteLine("WebSocket成功连接");
base.OnHandshaked(client, e);
}

protected override void OnDisconnected(ITcpClientBase client, ClientDisconnectedEventArgs e)
{
Trace.WriteLine("TCP断开连接");
base.OnDisconnected(client, e);
}
}
}
}

其中

判断是否能发送

1
client.CanSend

发送

1
((HttpSocketClient)client).SendWithWS("hello");

WebSocket4Net(客户端)(.Net Framework)

安装

1
2
3
install-package WebSocket4Net
install-package nlog
install-package nlog.config

客户端类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
using SuperSocket.ClientEngine;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using WebSocket4Net;

namespace WebSocketClient
{
public class WSocketClient:IDisposable
{
//日志管理
public static NLog.Logger _Logger = NLog.LogManager.GetCurrentClassLogger();

#region 向外传递数据事件
public event Action<string> MessageReceived;
#endregion

WebSocket4Net.WebSocket _webSocket;
/// <summary>
/// 检查重连线程
/// </summary>
Thread _thread;
bool _isRunning = true;
/// <summary>
/// WebSocket连接地址
/// </summary>
public string ServerPath { get; set; }

public WSocketClient(string url)
{
ServerPath = url;
this._webSocket = new WebSocket4Net.WebSocket(url);
this._webSocket.Opened += WebSocket_Opened;
this._webSocket.Error += WebSocket_Error;
this._webSocket.Closed += WebSocket_Closed;
this._webSocket.MessageReceived += WebSocket_MessageReceived;
}

#region "web socket "
/// <summary>
/// 连接方法
/// <returns></returns>
public bool Start()
{
bool result = true;
try
{
this._webSocket.Open();

this._isRunning = true;
this._thread = new Thread(new ThreadStart(CheckConnection));
this._thread.Start();
}
catch (Exception ex)
{
_Logger.Error(ex.ToString());
result = false;
}
return result;
}
/// <summary>
/// 消息收到事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void WebSocket_MessageReceived(object sender, MessageReceivedEventArgs e)
{
_Logger.Info(" Received:" +e.Message);
MessageReceived?.Invoke(e.Message);
}
/// <summary>
/// Socket关闭事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void WebSocket_Closed(object sender, EventArgs e)
{
_Logger.Info("websocket_Closed");
}
/// <summary>
/// Socket报错事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void WebSocket_Error(object sender, ErrorEventArgs e)
{
_Logger.Info("websocket_Error:" + e.Exception.ToString());
}
/// <summary>
/// Socket打开事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void WebSocket_Opened(object sender, EventArgs e)
{
_Logger.Info(" websocket_Opened");
}
/// <summary>
/// 检查重连线程
/// </summary>
private void CheckConnection()
{
do
{
try
{
if (this._webSocket.State != WebSocket4Net.WebSocketState.Open && this._webSocket.State != WebSocket4Net.WebSocketState.Connecting)
{
_Logger.Info(" Reconnect websocket WebSocketState:" + this._webSocket.State);
this._webSocket.Close();
this._webSocket.Open();
Console.WriteLine("正在重连");
}
}
catch (Exception ex)
{
_Logger.Error(ex.ToString());
}
System.Threading.Thread.Sleep(5000);
} while (this._isRunning);
}
#endregion

/// <summary>
/// 发送消息
/// </summary>
/// <param name="Message"></param>
public void SendMessage(string Message)
{
Task.Factory.StartNew(() =>
{
if (_webSocket != null && _webSocket.State == WebSocket4Net.WebSocketState.Open)
{
this._webSocket.Send(Message);
}
});
}

public void Dispose()
{
this._isRunning = false;
try
{
_thread.Abort();
}
catch
{

}
this._webSocket.Close();
this._webSocket.Dispose();
this._webSocket = null;
}
}
}

websocketsharp.core(服务端/客户端)(.Net6)

不推荐

目前测试的时候Web端能正常,但是Android上不行。

SuperWebSocket不支持.Net6,这个库支持。

官网:

https://github.com/ImoutoChan/websocket-sharp-core

https://github.com/sta/websocket-sharp

https://www.nuget.org/packages/websocketsharp.core/1.0.0?_src=template#supportedframeworks-body-tab

安装

1
Install-Package websocketsharp.core -Version 1.0.0

代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
using System.Collections.Generic;
using System.Threading.Tasks;

using WebSocketSharp;
using WebSocketSharp.Server;

namespace Net6WSServer.Utils
{
public class ZWSServer
{
private static List<WSSession> sessionList = new List<WSSession>();

public static void init()
{
var wssv = new WebSocketServer(10088, false);

wssv.AddWebSocketService<WSSession>("/ws");
wssv.Start();
}

public class WSSession : WebSocketBehavior
{
protected override void OnOpen()
{
System.Diagnostics.Trace.WriteLine("WS:用户上线:" + this.ID);
lock (sessionList)
{
sessionList.Add(this);
}
}

protected override void OnClose(CloseEventArgs e)
{
System.Diagnostics.Trace.WriteLine("用户断开" + this.ID);
lock (sessionList)
{
if (sessionList.Contains(this))
{
sessionList.Remove(this);
}
}
}

protected override void OnError(ErrorEventArgs e)
{
}

protected override void OnMessage(MessageEventArgs e)
{
if (e.IsText)
{
var msg = e.Data;
foreach (WSSession session in sessionList)
{
if (session.State == WebSocketState.Open)
{
Task.Run(() => { session.Send("[" + ID + "]: send: " + msg); });
}
}
}
}

public void SendMsg(string msg)
{
Send(msg);
}
}
}
}

网页(客户端)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>聊天客户端</title>

<style type="text/css">
h1,
h2,
h3 {
margin-top: 6px;
margin-bottom: 6px;
}
body {
margin: 0;
padding: 0;
overflow: hidden;
}

.outer {
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
}

#content {
height: 0;
flex: 1;
width: 100%;
padding: 10px;
overflow-y: auto;
}

#msg {
width: 100%;
height: 40px;
line-height: 40px;
flex: none;
border: 1px solid #eee;
outline: none;
}
</style>
</head>

<body>
<div class="outer">
<div id="content"></div>
<input type="text" id="msg" />
</div>

<script type="text/javascript" src="js/index.js"></script>
</body>
</html>

JS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
let timeunix = 0;
var socket = null;
var isopen = false;

function connect_ws() {
socket = new WebSocket("ws://127.0.0.1:10088/ws");
timeunix = parseInt(new Date().getTime() / 1000);
socket.onopen = function () {
addMsg("<h3>连接成功!</h3>");
isopen = true;

timeunix = parseInt(new Date().getTime() / 1000);
heartCheck();
};
socket.onmessage = function (evt) {
addMsg("<div>" + evt.data + "</div>");

};
socket.onclose = function (evt) {
if (isopen) {
addMsg("<h3>连接关闭!</h3>");
addMsg("<div>连接时长" + (parseInt(new Date().getTime() / 1000) - timeunix) + "秒</div>");
}


isopen = false;
}
socket.onerror = function (evt) {
addMsg("<h3>" + "连接出错!" + "</h3>");
}
}

window.onload = function () {
connect_ws();
}

//滚动到底部
function scrollBottom() {
var content_div = document.querySelector("#content");
content_div.scrollTop = content_div.scrollHeight;
}

function encodeScript(data) {
if (null == data || "" == data) {
return "";
}
return data.replace("<", "&lt;").replace(">", "&gt;");
}

function send_msg_click() {
if (socket && socket.readyState == socket.OPEN) {
var msg_div = document.querySelector("#msg");
var text = encodeScript(msg_div.value);
if (text) {
socket.send(text);
addMsg("<div style='color: #" + "CECECE" + "; font-size: " + 12 + ";'>" + text + "</div>");
msg_div.value = "";
}
} else {
connect_ws();
}
}

//心跳包
function heartCheck() {
if (window.ws_inter) {
clearInterval(window.ws_inter);
}
window.ws_inter = setInterval(function () {
if (socket && socket.readyState == socket.OPEN) {
let buffer = new ArrayBuffer(1); // 初始化1个Byte的二进制数据缓冲区
let dataView = new DataView(buffer);
dataView.setInt8(0, 127);
socket.send(dataView);
console.info("发送心跳", " 时间(s):" + parseInt(new Date().getTime() / 1000));
} else {
connect_ws();
}
}, 10000);
}

function addMsg(msg) {
var content_div = document.querySelector("#content");
content_div.innerHTML += msg;
scrollBottom();
}

document.onkeydown = function (event) {
var e = event || window.event || arguments.callee.caller.arguments[0];
if (e && e.keyCode == 13) { // enter 键
send_msg_click();
}
};