C#之WebSocket客户端
CZerocheng 2/4/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 class Client
{
public event GetReceivedEvent OnGetReceivedEvent;
public event TcpClosedEvent OnTcpClosedEvent;
public event TcpConnectedEvent OnTcpConnectedEvent;
public event EventHandler OnLoseLineEvent;
private Thread pingThread = null;
private WebSocketClient client = null;
public bool Online => client.Online;
private string IPHost = "ws://127.0.0.1:7789/ws";
private string certificatePath = string.Empty;
private string password = string.Empty;
public Client(string IPHost = "ws://127.0.0.1:7789/ws", string certificatePath = "", string password = "")
{
this.IPHost = IPHost;
this.certificatePath = certificatePath;
this.password = password;
}
public async Task Connection(int millisecondsTimeout = 5000)
{
try
{
client = new WebSocketClient();
if (!string.IsNullOrEmpty(certificatePath))
{
string host = Regex.Match(IPHost, @"//([\d\.]+):").Groups[1].Value;
await client.SetupAsync(new TouchSocketConfig()
.SetRemoteIPHost(IPHost)
.SetClientSslOption(
new ClientSslOption()
{
ClientCertificates = new X509CertificateCollection() { new X509Certificate2(certificatePath, password) },
SslProtocols = SslProtocols.Tls12,
TargetHost = host,
CertificateValidationCallback = (sender, certificate, chain, sslPolicyErrors) => { return true; }
})
.ConfigureContainer(a =>
{
a.AddConsoleLogger();
})
.ConfigurePlugins(a =>
{
a.UseTcpReconnection();
a.UseWebSocketHeartbeat()//使用心跳插件
.SetTick(TimeSpan.FromSeconds(3));//每1秒ping一次
//a.Add<MyWebSocketPlugin>();
}));
}
else
{
await client.SetupAsync(new TouchSocketConfig()
.SetRemoteIPHost(IPHost)
.ConfigureContainer(a =>
{
a.AddConsoleLogger();
})
.ConfigurePlugins(a =>
{
a.UseTcpReconnection();
//a.UseWebSocketHeartbeat()//使用心跳插件
//.SetTick(TimeSpan.FromSeconds(5));//每5秒ping一次
// //a.Add<MyWebSocketPlugin>();
}));
}
client.Received = (c, e) =>
{
OnGetReceivedEvent?.Invoke(c, e);
return e.InvokeNext();
};
client.Closed += (c, e) =>
{
OnTcpClosedEvent?.Invoke(e);
return e.InvokeNext();
};
await client.ConnectAsync(millisecondsTimeout);
OnTcpConnectedEvent?.Invoke();
pingThread = new Thread(() => {
while (client.Online)
Thread.Sleep(1000);
OnLoseLineEvent?.Invoke("客户端与服务器断开了!",null);
});
pingThread.Start();
}
catch (Exception ex)
{
throw ex;
}
}
public Task SendAsync(WSDataFrame dataFrame, bool endOfMessage = true)
{
try
{
return client?.SendAsync(dataFrame, endOfMessage);
}
catch (Exception ex)
{
throw ex;
}
}
public Task SendAsync(string text, bool endOfMessage = true)
{
try
{
return client?.SendAsync(text, endOfMessage);
}
catch (Exception ex)
{
throw ex;
}
}
public Task SendAsync(ReadOnlyMemory<byte> memory, bool endOfMessage = true)
{
return client?.SendAsync(memory, endOfMessage);
}
public async void Close()
{
try
{
await client?.Client.CloseAsync("主动断开连接");
}
catch (Exception ex)
{
throw;
}
}
public async void Ping()
{
try
{
await client?.PingAsync();
}
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
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
TouchSocket是一组非常优秀的类库,我是在它的基础上面封装我常用的调用方法!