C#之WebSocket服务端
CZerocheng 2/3/2024 CSharp
# 效果演示

# 什么是 WebSocket?
在现代的Web开发中,实时数据传输已成为提升用户体验的重要组成部分。传统的HTTP协议是一个基于请求-响应模式的协议,这意味着客户端每次需要获取新数据时都必须发起一个请求。而对于一些需要实时更新的应用(例如在线聊天、股票行情、实时游戏等),这种方式效率低下且带来了不必要的延迟。
这时,WebSocket应运而生,它为双向通信提供了一个高效、持久的通道。本文将介绍WebSocket的工作原理、应用场景以及如何在Web开发中实现WebSocket。 WebSocket 是一个网络协议,它使得客户端和服务器之间能够建立持久的双向通信通道。与传统的HTTP请求-响应模型不同,WebSocket 连接一旦建立,客户端和服务器之间可以随时相互发送数据,而不需要频繁地建立连接。
# WebSocket的特点
1、全双工通信:WebSocket 允许客户端和服务器进行实时的双向通信,不像传统的HTTP协议只能由客户端发起请求。
2、持久连接:WebSocket连接在客户端和服务器之间保持开放,直到显式地关闭连接,这比HTTP的每次请求-响应方式更高效。
3、低延迟:由于是持久连接,WebSocket 可以减少每次请求和响应时的延迟,适合实时更新场景。
4、节省资源:WebSocket 只需要一次握手建立连接,之后的数据交换不需要重新发送HTTP头,减少了网络负担。
# 使用TouchSocket编写服务端
public delegate void GetReceivedEvent(IWebSocket client, WSDataFrameEventArgs e);
public delegate void TcpClosedEvent(ClosedEventArgs e);
public delegate void TcpClosedEvent2(IWebSocket client,ClosedEventArgs e);
public delegate void TcpConnectedEvent();
public delegate void TcpConnectedEvent2(IWebSocket client, HttpContextEventArgs e);
public class Server
{
public event GetReceivedEvent OnGetReceivedEvent;
public event TcpClosedEvent2 OnTcpClosedEvent;
public event TcpConnectedEvent2 OnTcpConnectedEvent;
//private Thread scanThread = null;
public HttpService service = null;
private IPHost[] port = null;
public string wsURL = string.Empty;
public Server(params IPHost[] port)
{
this.port = port;
}
public async Task StartService()
{
try
{
service = new HttpService();
await service.SetupAsync(new TouchSocketConfig()//加载配置
.SetListenIPHosts(port)
.ConfigureContainer(a =>
{
a.AddConsoleLogger();
a.RegisterSingleton<IHttpService>(service);
a.RegisterSingleton<Server>(this);
})
.ConfigurePlugins(a =>
{
if (!string.IsNullOrEmpty(wsURL))
{
a.UseWebSocket()//添加WebSocket功能
.SetVerifyConnection(VerifyConnection)
.SetWSUrl(wsURL)
.UseAutoPong(); ;
a.Add<MyWebSocketPlugin>();//自定义插件。
}
else
{
a.UseWebSocket()//添加WebSocket功能
.SetVerifyConnection(VerifyConnection)
.SetWSUrl("/ws");
a.Add<MyWebSocketPlugin>();//自定义插件。
}
}));
await service.StartAsync();
}
catch (Exception ex)
{
throw ex;
}
}
private void ScanThread()
{
int count = 0;
while (true)
{
IClientCollection<HttpSessionClient> clients = service.Clients;
foreach (var item in clients)
{
Console.WriteLine($"Id:{item.Id},IP:{item.IP},port:{item.Port}");
}
}
}
/// <summary>
/// 验证websocket的连接
/// </summary>
/// <param name="client"></param>
/// <param name="context"></param>
/// <returns></returns>
private static bool VerifyConnection(IHttpSessionClient client, HttpContext context)
{
if (!context.Request.IsUpgrade())//如果不包含升级协议的header,就直接返回false。
{
return false;
}
if (context.Request.UrlEquals("/ws"))//以此连接,则直接可以连接
{
return true;
}
else if (context.Request.UrlEquals("/wsquery"))//以此连接,则需要传入token才可以连接
{
if (context.Request.Query.Get("token") == "123456")
{
return true;
}
else
{
context.Response
.SetStatus(403, "token不正确")
.AnswerAsync();
}
}
else if (context.Request.UrlEquals("/wsheader"))//以此连接,则需要从header传入token才可以连接
{
if (context.Request.Headers.Get("token") == "123456")
{
return true;
}
else
{
context.Response
.SetStatus(403, "token不正确")
.AnswerAsync();
}
}
return false;
}
public void GetReceive(IWebSocket client, WSDataFrameEventArgs e)
{
OnGetReceivedEvent?.Invoke(client, e);
}
public void TcpClosed(IWebSocket webSocket,ClosedEventArgs e)
{
OnTcpClosedEvent?.Invoke(webSocket,e);
}
public void TcpConnection(IWebSocket webSocket, HttpContextEventArgs e)
{
OnTcpConnectedEvent?.Invoke(webSocket,e);
}
public async Task SendAsync(string id, string text, bool endOfMessage = true)
{
try
{
HttpSessionClient client = service?.Clients.Where(it => it.Id == id).FirstOrDefault();
await client.WebSocket.SendAsync(text, endOfMessage);
}
catch (Exception ex)
{
Console.WriteLine($"{ex.Message}");
}
}
public async Task SendAsync(string id, WSDataFrame dataFrame, bool endOfMessage = true)
{
try
{
HttpSessionClient client = service?.Clients.Where(it => it.Id == id).FirstOrDefault();
await client.WebSocket.SendAsync(dataFrame, endOfMessage);
}
catch (Exception ex)
{
Console.WriteLine($"{ex.Message}");
}
}
public async Task SendAsync(string id, ReadOnlyMemory<byte> memory, bool endOfMessage = true)
{
try
{
HttpSessionClient client = service?.Clients.Where(it => it.Id == id).FirstOrDefault();
await client.WebSocket.SendAsync(memory, endOfMessage);
}
catch (Exception ex)
{
Console.WriteLine($"{ex.Message}");
}
}
public async Task SendAsync(string text, bool endOfMessage = true)
{
try
{
if(null !=service)
{
var clients = service.Clients;
foreach (var item in clients)
if (item.Protocol == Protocol.WebSocket)//先判断是不是websocket协议
await item.WebSocket.SendAsync(text, endOfMessage);
}
}
catch (Exception ex)
{
Console.WriteLine($"{ex.Message}");
}
}
public async Task SendAsync(WSDataFrame dataFrame, bool endOfMessage = true)
{
try
{
if (null != service)
{
var clients = service.Clients;
foreach (var item in clients)
if (item.Protocol == Protocol.WebSocket)//先判断是不是websocket协议
await item.WebSocket.SendAsync(dataFrame, endOfMessage);
}
}
catch (Exception ex)
{
Console.WriteLine($"{ex.Message}");
}
}
public async Task SendAsync(ReadOnlyMemory<byte> memory, bool endOfMessage = true)
{
try
{
if (null != service)
{
var clients = service.Clients;
foreach (var item in clients)
if (item.Protocol == Protocol.WebSocket)//先判断是不是websocket协议
await item.WebSocket.SendAsync(memory, endOfMessage);
}
}
catch (Exception ex)
{
Console.WriteLine($"{ex.Message}");
}
}
public async void Close(string id)
{
try
{
HttpSessionClient client = service?.Clients.Where(it => it.Id == id).FirstOrDefault();
await client.WebSocket.CloseAsync($"Id:{client.Id},IP:{client.IP},port:{client.Port}客户端已被关闭");
}
catch (Exception ex)
{
Console.WriteLine($"{ex.Message}");
}
}
public async Task Close()
{
try
{
var clients = service?.Clients;
foreach (var item in clients)
if (item.Protocol == Protocol.WebSocket)//先判断是不是websocket协议
await item.WebSocket.CloseAsync($"Id:{item.Id},IP:{item.IP},port:{item.Port}客户端已被关闭");
service.Dispose();
service = null;
}
catch (Exception ex)
{
Console.WriteLine($"{ex.Message}");
}
}
}
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
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
public class MyWebSocketPlugin : PluginBase, IWebSocketReceivedPlugin, IWebSocketClosedPlugin, IWebSocketHandshakedPlugin
{
private readonly ILog m_logger;
private readonly IHttpService m_httpService;
private readonly Server m_server;
public MyWebSocketPlugin(ILog logger, IHttpService httpServic, Server server)
{
this.m_logger = logger;
this.m_httpService = httpServic;
this.m_server = server;
}
public async Task OnWebSocketClosed(IWebSocket webSocket, ClosedEventArgs e)
{
m_server.TcpClosed(webSocket,e);
await e.InvokeNext();
}
public async Task OnWebSocketHandshaked(IWebSocket webSocket, HttpContextEventArgs e)
{
m_server.TcpConnection(webSocket,e);
await e.InvokeNext();
}
public async Task OnWebSocketReceived(IWebSocket client, WSDataFrameEventArgs e)
{
if (e.DataFrame.Opcode == WSDataType.Ping)
Console.WriteLine($"{client.Client.GetIPPort()}|心跳包:连接中");
else
m_server.GetReceive(client, e);
await e.InvokeNext();
}
}
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
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
TouchSocket是一组非常优秀的类库,我是在它的基础上面封装我常用的调用方法!