C# 网络编程:.NET 开发者的核心技能
<h2 data-tool="mdnice编辑器"><span class="prefix"><span class="content">前言</span></span></h2><p data-tool="mdnice编辑器">数字化时代,网络编程已成为软件开发中不可或缺的一环,尤其对于 .NET 开发者而言,掌握 C# 中的网络编程技巧是迈向更高层次的必经之路。无论是构建高性能的 Web 应用,还是实现复杂的分布式系统,网络编程都是支撑这一切的基石。</p>
<p data-tool="mdnice编辑器">本篇主要为 .NET 开发者提供一份全面而精炼的 C# 网络编程入门,从基础知识到高级话题,逐一剖析,帮助你建立起扎实的网络编程功底,让你在网络世界的编码之旅中游刃有余。</p>
<h2 data-tool="mdnice编辑器"><span class="prefix"><span class="content">一、HTTP 请求</span></span></h2>
<p data-tool="mdnice编辑器">HTTP(Hypertext Transfer Protocol)是互联网上应用最为广泛的一种网络协议,主要用于从万维网服务器传输超文本到本地浏览器的传输协议。</p>
<p data-tool="mdnice编辑器">在C#中,处理HTTP请求有多种方式,从传统的<code>System.Net</code>命名空间到现代的<code>HttpClient</code>类,每种方法都有其适用场景。</p>
<h3 data-tool="mdnice编辑器"><span class="prefix"><span class="content">1、使用 <code>HttpClient</code> 发送HTTP请求</span></span></h3>
<p data-tool="mdnice编辑器"><code>HttpClient</code>是C#中推荐用于发送HTTP请求的类,它提供了异步的API,可以更好地处理长时间运行的操作,避免阻塞UI线程。</p>
<p data-tool="mdnice编辑器">以下是一个简单的GET请求示例:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.Net.Http;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.Threading.Tasks;
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> HttpClientExample
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">async</span><span style="color: rgba(0, 0, 0, 1)"> Task Main()
{
</span><span style="color: rgba(0, 0, 255, 1)">using</span> <span style="color: rgba(0, 0, 255, 1)">var</span> client = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> HttpClient();
</span><span style="color: rgba(0, 0, 255, 1)">var</span> response = <span style="color: rgba(0, 0, 255, 1)">await</span> client.GetAsync(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">https://api.example.com/data</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (response.IsSuccessStatusCode)
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> content = <span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> response.Content.ReadAsStringAsync();
Console.WriteLine(content);
}
</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">
{
Console.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Failed to retrieve data: {response.StatusCode}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
}
}
}</span></pre>
</div>
<h3> <span class="prefix"><span class="content">2、使用 <code>WebClient</code> 发送HTTP请求</span></span></h3>
<p data-tool="mdnice编辑器">尽管<code>WebClient</code>类仍然存在于.NET Framework中,但在.NET Core和后续版本中,它已被标记为过时,推荐使用<code>HttpClient</code>。</p>
<p data-tool="mdnice编辑器">不过,对于简单的同步请求,<code>WebClient</code>仍然可以使用:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.IO;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.Net;
</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> WebClientExample
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> Main()
{
</span><span style="color: rgba(0, 0, 255, 1)">using</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> client = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> WebClient())
{
</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">string</span> result = client.DownloadString(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">https://api.example.com/info</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
Console.WriteLine(result);
}
</span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception ex)
{
Console.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Error: {ex.Message}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
}
}
}
}</span></pre>
</div>
<h3 data-tool="mdnice编辑器"><span class="prefix"><span class="content">3、使用 <code>HttpRequestMessage</code> 和 <code>HttpMessageHandler</code></span></span></h3>
<p data-tool="mdnice编辑器">对于更复杂的HTTP请求,如需要自定义请求头或处理认证,可以使用<code>HttpRequestMessage</code>和<code>HttpMessageHandler</code>。</p>
<p data-tool="mdnice编辑器">这种方式提供了更多的灵活性和控制:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.Net.Http;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.Threading.Tasks;
</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> HttpRequestMessageExample
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">async</span><span style="color: rgba(0, 0, 0, 1)"> Task Main()
{
</span><span style="color: rgba(0, 0, 255, 1)">using</span> <span style="color: rgba(0, 0, 255, 1)">var</span> client = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> HttpClient();
</span><span style="color: rgba(0, 0, 255, 1)">var</span> request = <span style="color: rgba(0, 0, 255, 1)">new</span> HttpRequestMessage(HttpMethod.Get, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">https://api.example.com/info</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
request.Headers.Add(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Authorization</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Bearer your-access-token</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">var</span> response = <span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> client.SendAsync(request);
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (response.IsSuccessStatusCode)
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> content = <span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> response.Content.ReadAsStringAsync();
Console.WriteLine(content);
}
</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">
{
Console.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Failed to retrieve data: {response.StatusCode}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
}
}
}</span></pre>
</div>
<h3 data-tool="mdnice编辑器"><span class="prefix"><span class="content">4、注意事项</span></span></h3>
<ul>
<li data-tool="mdnice编辑器"><strong>安全性和性能:</strong> 使用<code>HttpClient</code>时,确保在一个应用程序的生命周期内重用同一个实例,而不是每次请求都创建新的实例。</li>
<li data-tool="mdnice编辑器"><strong>错误处理:</strong> 总是对HTTP请求的结果进行检查,处理可能发生的异常和非成功的HTTP状态码。</li>
<li data-tool="mdnice编辑器"><strong>超时和取消:</strong> 使用<code>HttpClient</code>时,可以通过<code>CancellationToken</code>来控制请求的超时和取消。</li>
</ul>
<p data-tool="mdnice编辑器">通过掌握这些知识点,能够在C#中有效地处理各种HTTP请求,从简单的GET请求到复杂的POST请求,包括身份验证和错误处理。</p>
<h2 data-tool="mdnice编辑器"><span class="prefix"><span class="content">二、WebSocket 通信</span></span></h2>
<p data-tool="mdnice编辑器">WebSocket是一种在单个TCP连接上进行全双工通信的协议,它提供了比传统HTTP请求/响应模型更低的延迟和更高的效率,非常适合实时数据流、聊天应用、在线游戏等场景。在C#中,无论是服务器端还是客户端,都可以使用WebSocket进行通信。</p>
<h3 data-tool="mdnice编辑器"><span class="prefix"><span class="content">1、客户端使用 WebSocket</span></span></h3>
<p data-tool="mdnice编辑器">在C#中,你可以使用<code>System.Net.WebSockets</code>命名空间下的<code>ClientWebSocket</code>类来创建WebSocket客户端。下面是一个简单的示例,展示了如何连接到WebSocket服务器并发送和接收消息:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.IO.Pipelines;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.Net.WebSockets;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.Text;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.Threading;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.Threading.Tasks;
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> WebSocket客户端类,用于与WebSocket服务器建立连接和通信。
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> WebSocketClient
{
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 客户端WebSocket实例。
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">readonly</span> ClientWebSocket _webSocket = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> ClientWebSocket();
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 用于取消操作的CancellationTokenSource。
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">readonly</span> CancellationTokenSource _cancellationTokenSource = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> CancellationTokenSource();
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 连接到指定的WebSocket服务器。
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><param name="uri"></span><span style="color: rgba(0, 128, 0, 1)">WebSocket服务器的URI。</span><span style="color: rgba(128, 128, 128, 1)"></param></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">async</span> Task Connect(<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> uri)
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 使用提供的URI连接到WebSocket服务器</span>
<span style="color: rgba(0, 0, 255, 1)">await</span> _webSocket.ConnectAsync(<span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Uri(uri), _cancellationTokenSource.Token);
}
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 向WebSocket服务器发送消息。
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><param name="message"></span><span style="color: rgba(0, 128, 0, 1)">要发送的消息字符串。</span><span style="color: rgba(128, 128, 128, 1)"></param></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">async</span> Task SendMessage(<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> message)
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 将消息转换为UTF8编码的字节</span>
<span style="color: rgba(0, 0, 255, 1)">byte</span>[] buffer =<span style="color: rgba(0, 0, 0, 1)"> Encoding.UTF8.GetBytes(message);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 创建ArraySegment,封装要发送的字节缓冲区</span>
ArraySegment<<span style="color: rgba(0, 0, 255, 1)">byte</span>> segment = <span style="color: rgba(0, 0, 255, 1)">new</span> ArraySegment<<span style="color: rgba(0, 0, 255, 1)">byte</span>><span style="color: rgba(0, 0, 0, 1)">(buffer);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 发送消息到WebSocket服务器</span>
<span style="color: rgba(0, 0, 255, 1)">await</span> _webSocket.SendAsync(segment, WebSocketMessageType.Text, <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">, _cancellationTokenSource.Token);
}
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 接收WebSocket服务器发送的消息。
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><param name="onMessageReceived"></span><span style="color: rgba(0, 128, 0, 1)">接收到消息时调用的回调函数。</span><span style="color: rgba(128, 128, 128, 1)"></param></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">async</span> Task ReceiveMessage(Action<<span style="color: rgba(0, 0, 255, 1)">string</span>><span style="color: rgba(0, 0, 0, 1)"> onMessageReceived)
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 当WebSocket连接处于打开状态时,持续接收消息</span>
<span style="color: rgba(0, 0, 255, 1)">while</span> (_webSocket.State ==<span style="color: rgba(0, 0, 0, 1)"> WebSocketState.Open)
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> buffer = <span style="color: rgba(0, 0, 255, 1)">new</span> <span style="color: rgba(0, 0, 255, 1)">byte</span>[<span style="color: rgba(128, 0, 128, 1)">1024</span><span style="color: rgba(0, 0, 0, 1)">];
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 接收来自WebSocket服务器的数据</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> result = <span style="color: rgba(0, 0, 255, 1)">await</span> _webSocket.ReceiveAsync(<span style="color: rgba(0, 0, 255, 1)">new</span> ArraySegment<<span style="color: rgba(0, 0, 255, 1)">byte</span>><span style="color: rgba(0, 0, 0, 1)">(buffer), _cancellationTokenSource.Token);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 如果接收到的类型为关闭,则关闭连接</span>
<span style="color: rgba(0, 0, 255, 1)">if</span> (result.MessageType ==<span style="color: rgba(0, 0, 0, 1)"> WebSocketMessageType.Close)
{
</span><span style="color: rgba(0, 0, 255, 1)">await</span> _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Closing</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, CancellationToken.None);
</span><span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 将接收到的字节转换为字符串,并通过回调函数处理</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> receivedMessage = Encoding.UTF8.GetString(buffer, <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">, result.Count);
onMessageReceived(receivedMessage);
}
}
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 断开与WebSocket服务器的连接。
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">async</span><span style="color: rgba(0, 0, 0, 1)"> Task Disconnect()
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 取消接收和发送操作</span>
<span style="color: rgba(0, 0, 0, 1)"> _cancellationTokenSource.Cancel();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 关闭WebSocket连接</span>
<span style="color: rgba(0, 0, 255, 1)">await</span> _webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Closing</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, CancellationToken.None);
}
}</span></pre>
</div>
<h3><span class="prefix"><span class="content">2、服务器端使用 WebSocket</span></span></h3>
<p data-tool="mdnice编辑器">在服务器端,可以使用ASP.NET Core中的<code>Microsoft.AspNetCore.WebSockets</code>来支持WebSocket。</p>
<p data-tool="mdnice编辑器">下面是一个简单的WebSocket服务端点配置示例:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> Microsoft.AspNetCore.Builder;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> Microsoft.AspNetCore.Hosting;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> Microsoft.AspNetCore.Http;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> Microsoft.Extensions.DependencyInjection;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.Threading.Tasks;
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Startup
{
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 配置服务容器。
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><param name="services"></span><span style="color: rgba(0, 128, 0, 1)">服务集合。</span><span style="color: rgba(128, 128, 128, 1)"></param></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> ConfigureServices(IServiceCollection services)
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 添加控制器服务</span>
<span style="color: rgba(0, 0, 0, 1)"> services.AddControllers();
}
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span><span style="color: rgba(0, 128, 0, 1)"> 配置应用管道。
</span><span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"></summary></span>
<span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><param name="app"></span><span style="color: rgba(0, 128, 0, 1)">应用构建器。</span><span style="color: rgba(128, 128, 128, 1)"></param></span>
<span style="color: rgba(128, 128, 128, 1)">///</span> <span style="color: rgba(128, 128, 128, 1)"><param name="env"></span><span style="color: rgba(0, 128, 0, 1)">主机环境。</span><span style="color: rgba(128, 128, 128, 1)"></param></span>
<span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 在开发环境中启用异常页面</span>
<span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 启用路由</span>
<span style="color: rgba(0, 0, 0, 1)"> app.UseRouting();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 启用WebSocket中间件</span>
<span style="color: rgba(0, 0, 0, 1)"> app.UseWebSockets();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 配置端点处理器</span>
app.UseEndpoints(endpoints =><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 映射默认的GET请求处理器</span>
endpoints.MapGet(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(0, 0, 255, 1)">async</span> context =><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 0, 255, 1)">await</span> context.Response.WriteAsync(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Hello World!</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
});
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 映射WebSocket请求处理器</span>
endpoints.Map(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">/ws</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(0, 0, 255, 1)">async</span> context =><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 检查当前请求是否为WebSocket请求</span>
<span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (context.WebSockets.IsWebSocketRequest)
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 接受WebSocket连接</span>
<span style="color: rgba(0, 0, 255, 1)">using</span> <span style="color: rgba(0, 0, 255, 1)">var</span> webSocket = <span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> context.WebSockets.AcceptWebSocketAsync();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 持续监听WebSocket消息</span>
<span style="color: rgba(0, 0, 255, 1)">while</span> (<span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">)
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 准备接收缓冲区</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> buffer = <span style="color: rgba(0, 0, 255, 1)">new</span> <span style="color: rgba(0, 0, 255, 1)">byte</span>[<span style="color: rgba(128, 0, 128, 1)">1024</span> * <span style="color: rgba(128, 0, 128, 1)">4</span><span style="color: rgba(0, 0, 0, 1)">];
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 接收WebSocket消息</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> result = <span style="color: rgba(0, 0, 255, 1)">await</span> webSocket.ReceiveAsync(<span style="color: rgba(0, 0, 255, 1)">new</span> ArraySegment<<span style="color: rgba(0, 0, 255, 1)">byte</span>><span style="color: rgba(0, 0, 0, 1)">(buffer), CancellationToken.None);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 如果收到的类型为关闭消息,则关闭连接</span>
<span style="color: rgba(0, 0, 255, 1)">if</span> (result.MessageType ==<span style="color: rgba(0, 0, 0, 1)"> WebSocketMessageType.Close)
{
</span><span style="color: rgba(0, 0, 255, 1)">await</span> webSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Closing</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, CancellationToken.None);
</span><span style="color: rgba(0, 0, 255, 1)">break</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 解码接收到的消息</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> message = Encoding.UTF8.GetString(buffer, <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">, result.Count);
Console.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Received: {message}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 回复消息给客户端</span>
<span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> webSocket.SendAsync(
</span><span style="color: rgba(0, 0, 255, 1)">new</span> ArraySegment<<span style="color: rgba(0, 0, 255, 1)">byte</span>>(Encoding.UTF8.GetBytes($<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Echo: {message}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">)),
result.MessageType,
result.EndOfMessage,
CancellationToken.None);
}
}
</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 如果不是WebSocket请求,则返回400错误</span>
context.Response.StatusCode = <span style="color: rgba(128, 0, 128, 1)">400</span><span style="color: rgba(0, 0, 0, 1)">;
}
});
});
}
}</span></pre>
</div>
<p data-tool="mdnice编辑器">在上面的服务器端代码中,首先启用了WebSocket中间件,然后映射了一个<code>/ws</code>端点来处理WebSocket连接。</p>
<p data-tool="mdnice编辑器">当收到连接请求时,我们接受连接并进入循环,监听客户端发送的消息,然后简单地回传一个回显消息。</p>
<h3 data-tool="mdnice编辑器"><span class="prefix"><span class="content">3、说明</span></span></h3>
<p data-tool="mdnice编辑器">WebSocket为C#开发者提供了强大的实时通信能力,无论是构建复杂的实时数据流应用还是简单的聊天室,WebSocket都是一个值得考虑的选择。通过掌握客户端和服务器端的实现细节,可以充分利用WebSocket的优势,创建高性能和低延迟的实时应用。</p>
<h2><span class="prefix"><span class="content">三、 Socket 编程</span></span></h2>
<p data-tool="mdnice编辑器">Socket编程是计算机网络通信中的基础概念,它提供了在不同计算机之间发送和接收数据的能力。</p>
<p data-tool="mdnice编辑器">在C#中,Socket编程主要通过<code>System.Net.Sockets</code>命名空间下的<code>Socket</code>类来实现。Socket可以用于创建TCP/IP和UDP两种主要类型的网络连接,分别对应于流式套接字(Stream Sockets)和数据报套接字(Datagram Sockets)。</p>
<h3 data-tool="mdnice编辑器"><span class="prefix"><span class="content">1、Socket 基础</span></span></h3>
<p data-tool="mdnice编辑器">Socket地址族:指定网络协议的类型,如<code>AddressFamily.InterNetwork</code>用于IPv4。</p>
<p data-tool="mdnice编辑器">Socket类型:<code>SocketType.Stream</code>用于TCP,<code>SocketType.Dgram</code>用于UDP。</p>
<p data-tool="mdnice编辑器">Socket协议:<code>ProtocolType.Tcp</code>或<code>ProtocolType.Udp</code>,分别用于TCP和UDP。</p>
<h3 data-tool="mdnice编辑器"><span class="prefix"><span class="content">2、TCP Socket 客户端</span></span></h3>
<p data-tool="mdnice编辑器">TCP Socket客户端通常用于建立持久的连接,并通过流的方式发送和接收数据。</p>
<p data-tool="mdnice编辑器">以下是一个简单的TCP客户端示例:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.Net;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.Net.Sockets;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.Text;
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> TcpClientExample
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> Main()
{
</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 创建一个新的Socket实例</span>
<span style="color: rgba(0, 0, 255, 1)">using</span> (Socket socket = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 连接到服务器</span>
IPAddress ipAddress = IPAddress.Parse(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">127.0.0.1</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
IPEndPoint remoteEP </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> IPEndPoint(ipAddress, <span style="color: rgba(128, 0, 128, 1)">11000</span><span style="color: rgba(0, 0, 0, 1)">);
socket.Connect(remoteEP);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 发送数据</span>
<span style="color: rgba(0, 0, 255, 1)">string</span> message = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Hello Server!</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">byte</span>[] data =<span style="color: rgba(0, 0, 0, 1)"> Encoding.ASCII.GetBytes(message);
socket.Send(data);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 接收服务器响应</span>
data = <span style="color: rgba(0, 0, 255, 1)">new</span> <span style="color: rgba(0, 0, 255, 1)">byte</span>[<span style="color: rgba(128, 0, 128, 1)">1024</span><span style="color: rgba(0, 0, 0, 1)">];
</span><span style="color: rgba(0, 0, 255, 1)">int</span> bytes =<span style="color: rgba(0, 0, 0, 1)"> socket.Receive(data);
Console.WriteLine(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Received: {0}</span><span style="color: rgba(128, 0, 0, 1)">"</span>, Encoding.ASCII.GetString(data, <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">, bytes));
}
}
</span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception e)
{
Console.WriteLine(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Error: {0}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, e.ToString());
}
}
}</span></pre>
</div>
<h3 data-tool="mdnice编辑器"><span class="prefix"><span class="content">3、TCP Socket 服务器</span></span></h3>
<p data-tool="mdnice编辑器">TCP Socket服务器负责监听客户端的连接请求,并处理来自客户端的数据。</p>
<p data-tool="mdnice编辑器">以下是一个简单的TCP服务器示例:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.Net;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.Net.Sockets;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.Text;
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> TcpServerExample
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> Main()
{
</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 创建一个新的Socket实例</span>
<span style="color: rgba(0, 0, 255, 1)">using</span> (Socket listener = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp))
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 绑定到本地端口</span>
IPAddress ipAddress =<span style="color: rgba(0, 0, 0, 1)"> IPAddress.Any;
IPEndPoint localEP </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> IPEndPoint(ipAddress, <span style="color: rgba(128, 0, 128, 1)">11000</span><span style="color: rgba(0, 0, 0, 1)">);
listener.Bind(localEP);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 监听连接</span>
listener.Listen(<span style="color: rgba(128, 0, 128, 1)">10</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 接受客户端连接</span>
Console.WriteLine(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Waiting for a connection...</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
Socket handler </span>=<span style="color: rgba(0, 0, 0, 1)"> listener.Accept();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 接收数据</span>
<span style="color: rgba(0, 0, 255, 1)">byte</span>[] data = <span style="color: rgba(0, 0, 255, 1)">new</span> <span style="color: rgba(0, 0, 255, 1)">byte</span>[<span style="color: rgba(128, 0, 128, 1)">1024</span><span style="color: rgba(0, 0, 0, 1)">];
</span><span style="color: rgba(0, 0, 255, 1)">int</span> bytes =<span style="color: rgba(0, 0, 0, 1)"> handler.Receive(data);
Console.WriteLine(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Text received: {0}</span><span style="color: rgba(128, 0, 0, 1)">"</span>, Encoding.ASCII.GetString(data, <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">, bytes));
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 发送响应</span>
<span style="color: rgba(0, 0, 255, 1)">string</span> response = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Hello Client!</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">byte</span>[] responseData =<span style="color: rgba(0, 0, 0, 1)"> Encoding.ASCII.GetBytes(response);
handler.Send(responseData);
}
}
</span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception e)
{
Console.WriteLine(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Error: {0}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, e.ToString());
}
}
}</span></pre>
</div>
<h3 data-tool="mdnice编辑器"><span class="prefix"><span class="content">4、UDP Socket</span></span></h3>
<p data-tool="mdnice编辑器">UDP Socket用于无连接的、不可靠的网络通信,通常用于实时数据传输,如视频流或游戏。</p>
<p data-tool="mdnice编辑器">以下是一个简单的UDP客户端和服务器示例:</p>
<p data-tool="mdnice编辑器"><strong>UDP客户端</strong></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.Net;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.Net.Sockets;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.Text;
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> UdpClientExample
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> Main()
{
</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 创建一个新的Socket实例</span>
<span style="color: rgba(0, 0, 255, 1)">using</span> (UdpClient client = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> UdpClient())
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 发送数据</span>
<span style="color: rgba(0, 0, 255, 1)">string</span> message = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Hello UDP Server!</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">byte</span>[] data =<span style="color: rgba(0, 0, 0, 1)"> Encoding.ASCII.GetBytes(message);
IPEndPoint server </span>= <span style="color: rgba(0, 0, 255, 1)">new</span> IPEndPoint(IPAddress.Parse(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">127.0.0.1</span><span style="color: rgba(128, 0, 0, 1)">"</span>), <span style="color: rgba(128, 0, 128, 1)">11000</span><span style="color: rgba(0, 0, 0, 1)">);
client.Send(data, data.Length, server);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 接收服务器响应</span>
data = client.Receive(<span style="color: rgba(0, 0, 255, 1)">ref</span><span style="color: rgba(0, 0, 0, 1)"> server);
Console.WriteLine(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Received: {0}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, Encoding.ASCII.GetString(data));
}
}
</span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception e)
{
Console.WriteLine(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Error: {0}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, e.ToString());
}
}
}</span></pre>
</div>
<p data-tool="mdnice编辑器"><strong>UDP服务器</strong></p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.Net;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.Net.Sockets;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.Text;
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> UdpServerExample
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> Main()
{
</span><span style="color: rgba(0, 0, 255, 1)">try</span><span style="color: rgba(0, 0, 0, 1)">
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 创建一个新的Socket实例</span>
<span style="color: rgba(0, 0, 255, 1)">using</span> (UdpClient listener = <span style="color: rgba(0, 0, 255, 1)">new</span> UdpClient(<span style="color: rgba(128, 0, 128, 1)">11000</span><span style="color: rgba(0, 0, 0, 1)">))
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 接收数据</span>
IPEndPoint client = <span style="color: rgba(0, 0, 255, 1)">new</span> IPEndPoint(IPAddress.Any, <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 0, 255, 1)">byte</span>[] data = listener.Receive(<span style="color: rgba(0, 0, 255, 1)">ref</span><span style="color: rgba(0, 0, 0, 1)"> client);
Console.WriteLine(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Text received: {0}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, Encoding.ASCII.GetString(data));
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 发送响应</span>
<span style="color: rgba(0, 0, 255, 1)">string</span> response = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Hello UDP Client!</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">byte</span>[] responseData =<span style="color: rgba(0, 0, 0, 1)"> Encoding.ASCII.GetBytes(response);
listener.Send(responseData, responseData.Length, client);
}
}
</span><span style="color: rgba(0, 0, 255, 1)">catch</span><span style="color: rgba(0, 0, 0, 1)"> (Exception e)
{
Console.WriteLine(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Error: {0}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, e.ToString());
}
}
}</span></pre>
</div>
<p data-tool="mdnice编辑器">以上示例展示了如何使用C#中的<code>Socket</code>类来实现TCP和UDP的客户端与服务器通信。</p>
<p data-tool="mdnice编辑器">在实际应用中,可能还需要处理并发连接、错误处理和资源管理等问题。</p>
<p data-tool="mdnice编辑器">此外,对于TCP通信,考虑到性能和资源使用,通常建议使用异步编程模型。</p>
<h2 data-tool="mdnice编辑器"><span class="prefix"><span class="content">四、C# 网络安全</span></span></h2>
<p data-tool="mdnice编辑器">C# 中进行网络编程时,网络安全是一个至关重要的方面,涉及数据传输的保密性、完整性和可用性。以下是一些关键的网络安全知识点,它们对于构建安全的网络应用程序至关重要:</p>
<h3 data-tool="mdnice编辑器"><span class="prefix"><span class="content">1、SSL/TLS 加密</span></span></h3>
<p data-tool="mdnice编辑器">在C#中使用<code>HttpClient</code>时,可以通过<code>HttpClientHandler</code>类来配置SSL/TLS相关的选项,确保HTTPS请求的安全性。</p>
<p data-tool="mdnice编辑器">下面是一个示例,演示了如何使用<code>HttpClientHandler</code>来配置SSL/TLS设置:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.Net.Http;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.Net.Http.Headers;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.Security.Cryptography.X509Certificates;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.Threading.Tasks;
</span><span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Program
{
</span><span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">async</span><span style="color: rgba(0, 0, 0, 1)"> Task Main()
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 创建 HttpClientHandler 实例</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> handler = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> HttpClientHandler();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 配置 SSL/TLS 设置
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 设置检查服务器证书的委托</span>
handler.ServerCertificateCustomValidationCallback =<span style="color: rgba(0, 0, 0, 1)"> HttpClientHandler.DangerousAcceptAnyServerCertificateValidator;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 设置是否自动重定向</span>
handler.AllowAutoRedirect = <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 设置代理
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> handler.UseProxy = true;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> handler.Proxy = new WebProxy("</span><span style="color: rgba(0, 128, 0, 1); text-decoration: underline">http://proxy.example.com</span><span style="color: rgba(0, 128, 0, 1)">:8080");
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 创建 HttpClient 实例</span>
<span style="color: rgba(0, 0, 255, 1)">using</span> <span style="color: rgba(0, 0, 255, 1)">var</span> httpClient = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> HttpClient(handler);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 设置请求头部</span>
<span style="color: rgba(0, 0, 0, 1)"> httpClient.DefaultRequestHeaders.Accept.Clear();
httpClient.DefaultRequestHeaders.Accept.Add(</span><span style="color: rgba(0, 0, 255, 1)">new</span> MediaTypeWithQualityHeaderValue(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">application/json</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">));
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 发送 HTTPS 请求</span>
<span style="color: rgba(0, 0, 255, 1)">var</span> response = <span style="color: rgba(0, 0, 255, 1)">await</span> httpClient.GetAsync(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">https://api.example.com/data</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 检查响应状态</span>
<span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (response.IsSuccessStatusCode)
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> content = <span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> response.Content.ReadAsStringAsync();
Console.WriteLine(content);
}
</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">
{
Console.WriteLine($</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">Failed to retrieve data: {response.StatusCode}</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
}
}
}</span></pre>
</div>
<p data-tool="mdnice编辑器"><strong>解释</strong></p>
<ul>
<li data-tool="mdnice编辑器"><code>ServerCertificateCustomValidationCallback</code>:此属性允许你指定一个委托,用来验证服务器的SSL证书。在这个示例中,我们使用了<code>HttpClientHandler.DangerousAcceptAnyServerCertificateValidator</code>,它会接受任何证书,这在测试环境中可能有用,但强烈建议在生产环境中使用更严格的证书验证逻辑。</li>
<li data-tool="mdnice编辑器"><code>AllowAutoRedirect</code>:此属性控制是否允许<code>HttpClient</code>自动处理重定向。默认情况下,它是开启的。</li>
<li data-tool="mdnice编辑器"><code>UseProxy</code> 和 <code>Proxy</code>:如果需要通过代理服务器发送请求,可以配置这两个属性。</li>
<li data-tool="mdnice编辑器"><code>DefaultRequestHeaders</code>:用于设置请求的默认头部,如<code>Accept</code>,以指定期望的响应格式。</li>
</ul>
<p data-tool="mdnice编辑器"><strong>注意事项:</strong></p>
<ul>
<li data-tool="mdnice编辑器">实际应用中,不建议使用<code>DangerousAcceptAnyServerCertificateValidator</code>,因为它绕过了正常的证书验证,可能使应用程序暴露于中间人攻击。在生产环境中,应该实现自己的证书验证逻辑,确保只接受有效和可信的证书。</li>
<li data-tool="mdnice编辑器">此外,如果应用程序需要处理特定的SSL/TLS协议版本或加密套件,也可以通过<code>SslProtocols</code>属性进一步定制<code>HttpClientHandler</code>的SSL/TLS设置。</li>
<li data-tool="mdnice编辑器">例如,可以将其设置为<code>SslProtocols.Tls12</code>或<code>SslProtocols.Tls13</code>,以限制使用的协议版本。</li>
</ul>
<h3 data-tool="mdnice编辑器"><span class="prefix"><span class="content">2、密码安全存储</span></span></h3>
<p data-tool="mdnice编辑器">在C#中安全地存储密码是一个至关重要的实践,尤其是当涉及到用户账户和敏感信息时。为了保护密码不被泄露或破解,应避免以明文形式存储密码,而是采用加密或哈希的方式。</p>
<p data-tool="mdnice编辑器">以下是一些推荐的实践:</p>
<ul>
<li data-tool="mdnice编辑器"><strong>使用哈希函数</strong></li>
</ul>
<p data-tool="mdnice编辑器">使用安全的哈希函数,如SHA-256或SHA-512,可以将密码转换为一个固定长度的摘要。但是,简单的哈希容易受到彩虹表攻击,因此需要加入盐值(salt)。</p>
<p data-tool="mdnice编辑器">示例代码:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.Security.Cryptography;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.Text;
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> PasswordHasher
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">string</span> HashPassword(<span style="color: rgba(0, 0, 255, 1)">string</span> password, <span style="color: rgba(0, 0, 255, 1)">byte</span><span style="color: rgba(0, 0, 0, 1)">[] salt)
{
</span><span style="color: rgba(0, 0, 255, 1)">using</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> sha256 =<span style="color: rgba(0, 0, 0, 1)"> SHA256.Create())
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> passwordSalted = Encoding.UTF8.GetBytes(password +<span style="color: rgba(0, 0, 0, 1)"> Encoding.UTF8.GetString(salt));
</span><span style="color: rgba(0, 0, 255, 1)">var</span> hash =<span style="color: rgba(0, 0, 0, 1)"> sha256.ComputeHash(passwordSalted);
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> Convert.ToBase64String(hash);
}
}
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">byte</span><span style="color: rgba(0, 0, 0, 1)">[] GenerateSalt()
{
</span><span style="color: rgba(0, 0, 255, 1)">using</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> rng = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> RNGCryptoServiceProvider())
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> salt = <span style="color: rgba(0, 0, 255, 1)">new</span> <span style="color: rgba(0, 0, 255, 1)">byte</span>[<span style="color: rgba(128, 0, 128, 1)">32</span><span style="color: rgba(0, 0, 0, 1)">];
rng.GetBytes(salt);
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> salt;
}
}
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 使用示例</span>
<span style="color: rgba(0, 0, 255, 1)">byte</span>[] salt =<span style="color: rgba(0, 0, 0, 1)"> PasswordHasher.GenerateSalt();
</span><span style="color: rgba(0, 0, 255, 1)">string</span> hashedPassword = PasswordHasher.HashPassword(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">password123</span><span style="color: rgba(128, 0, 0, 1)">"</span>, salt);</pre>
</div>
<ul>
<li data-tool="mdnice编辑器"><strong>使用加盐哈希</strong></li>
</ul>
<p data-tool="mdnice编辑器">在哈希密码之前,先将随机生成的盐值与密码结合。这可以防止彩虹表攻击和暴力破解。</p>
<ul>
<li data-tool="mdnice编辑器"><strong>使用慢速哈希函数</strong></li>
</ul>
<p data-tool="mdnice编辑器">使用像PBKDF2、bcrypt、scrypt或Argon2这样的慢速哈希函数,可以显著增加破解难度,因为它们设计时考虑了防止暴力破解。</p>
<p data-tool="mdnice编辑器">示例代码:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.Linq;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.Security.Cryptography;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.Text;
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> PasswordHasher
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">string</span> HashPasswordUsingBcrypt(<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> password)
{
</span><span style="color: rgba(0, 0, 255, 1)">using</span> (<span style="color: rgba(0, 0, 255, 1)">var</span> bcrypt = <span style="color: rgba(0, 0, 255, 1)">new</span> Rfc2898DeriveBytes(password, <span style="color: rgba(128, 0, 128, 1)">16</span>, <span style="color: rgba(128, 0, 128, 1)">10000</span>)) <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 16 bytes of salt, 10000 iterations</span>
<span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> Convert.ToBase64String(bcrypt.GetBytes(<span style="color: rgba(128, 0, 128, 1)">24</span>)); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 24 bytes of hash</span>
<span style="color: rgba(0, 0, 0, 1)"> }
}
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 使用示例</span>
<span style="color: rgba(0, 0, 255, 1)">string</span> hashedPassword = PasswordHasher.HashPasswordUsingBcrypt(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">password123</span><span style="color: rgba(128, 0, 0, 1)">"</span>);</pre>
</div>
<ul>
<li data-tool="mdnice编辑器"><strong>存储哈希和盐值</strong></li>
</ul>
<p data-tool="mdnice编辑器">在数据库中,除了存储哈希后的密码,还应存储用于该密码的盐值,以便在验证时使用相同的盐值重新计算哈希。</p>
<ul>
<li data-tool="mdnice编辑器"><strong>验证密码</strong></li>
</ul>
<p data-tool="mdnice编辑器">在用户登录时,从数据库中检索哈希和盐值,使用相同的哈希函数和盐值对输入的密码进行哈希,然后与存储的哈希值进行比较。</p>
<p data-tool="mdnice编辑器">示例代码:</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">bool</span> VerifyPassword(<span style="color: rgba(0, 0, 255, 1)">string</span> inputPassword, <span style="color: rgba(0, 0, 255, 1)">string</span> storedHash, <span style="color: rgba(0, 0, 255, 1)">byte</span><span style="color: rgba(0, 0, 0, 1)">[] storedSalt)
{
</span><span style="color: rgba(0, 0, 255, 1)">string</span> hashOfInput =<span style="color: rgba(0, 0, 0, 1)"> PasswordHasher.HashPassword(inputPassword, storedSalt);
</span><span style="color: rgba(0, 0, 255, 1)">return</span> hashOfInput ==<span style="color: rgba(0, 0, 0, 1)"> storedHash;
}</span></pre>
</div>
<ul>
<li data-tool="mdnice编辑器"><strong>不要存储密码重置问题的答案</strong></li>
</ul>
<p data-tool="mdnice编辑器">密码重置问题的答案应该像密码一样被安全地处理,避免以明文形式存储。</p>
<p data-tool="mdnice编辑器">ASP.NET Core提供了内置的密码哈希和验证方法,使用这些框架通常比手动实现更安全。总之,安全地存储密码涉及到使用强哈希算法、加盐、适当的迭代次数和存储机制。同时,保持对最新安全实践的关注,并定期更新代码以应对新的威胁。</p>
<h3 data-tool="mdnice编辑器"><span class="prefix"><span class="content">3、防止SQL注入</span></span></h3>
<p data-tool="mdnice编辑器">使用参数化查询或ORM工具等,防止SQL注入攻击。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">string</span> query = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">SELECT * FROM SystemUser WHERE Username = @username</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
SqlCommand command </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> SqlCommand(query, connection);
command.Parameters.AddWithValue(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">@username</span><span style="color: rgba(128, 0, 0, 1)">"</span>, inputUsername);</pre>
</div>
<h3 data-tool="mdnice编辑器"><span class="prefix"><span class="content">4、防止跨站脚本攻击(XSS)</span></span></h3>
<p data-tool="mdnice编辑器">对用户输入进行合适的编码和验证,防止恶意脚本注入。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">string</span> userContent = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)"><script>alert('XSS');</script></span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">string</span> encodedContent = HttpUtility.HtmlEncode(userContent);</pre>
</div>
<h3 data-tool="mdnice编辑器"><span class="prefix"><span class="content">5、防止跨站请求伪造(CSRF)</span></span></h3>
<p data-tool="mdnice编辑器">ASP.NET MVC可以使用Anti-Forgery Token等机制来防止CSRF攻击。</p>
<div class="cnblogs_code">
<pre>@Html.AntiForgeryToken()</pre>
</div>
<h3 data-tool="mdnice编辑器"><span class="prefix"><span class="content">6、身份验证和授权</span></span></h3>
<p data-tool="mdnice编辑器">使用更高级的身份验证机制,如JWT(JSON Web Token),并在应用中实施合适的授权策略。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 0, 1)">
</span><span style="color: rgba(0, 0, 255, 1)">public</span><span style="color: rgba(0, 0, 0, 1)"> ActionResult SecureAction()
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 安全操作</span>
}</pre>
</div>
<h3 data-tool="mdnice编辑器"><span class="prefix"><span class="content">7、判断文件安全</span></span></h3>
<p data-tool="mdnice编辑器">在C#中,判断一个文件是否"安全"可以从多个角度考量,这通常涉及到文件的来源、内容、权限以及是否包含潜在的恶意代码等。</p>
<p data-tool="mdnice编辑器">下面我会介绍几种可能的方法来检查文件的安全性:</p>
<ul>
<li data-tool="mdnice编辑器"><strong>检查文件的来源</strong></li>
</ul>
<p data-tool="mdnice编辑器">确保文件是从可信的源下载或获取的。在Web应用程序中,可以使用<code>Content-Disposition</code>响应头来检查文件是否作为附件提供,以及文件名是否符合预期。</p>
<ul>
<li data-tool="mdnice编辑器"><strong>验证文件的类型和扩展名</strong></li>
</ul>
<p data-tool="mdnice编辑器">通过检查文件的扩展名或MIME类型来确定文件类型是否符合预期,例如,如果期望图片文件,那么只接受<code>.jpg</code>, <code>.png</code>等扩展名。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">bool</span> IsFileSafeByExtension(<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> filePath)
{
</span><span style="color: rgba(0, 0, 255, 1)">string</span>[] allowedExtensions = { <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">.jpg</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">.png</span><span style="color: rgba(128, 0, 0, 1)">"</span>, <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">.gif</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)"> };
</span><span style="color: rgba(0, 0, 255, 1)">string</span> extension =<span style="color: rgba(0, 0, 0, 1)"> Path.GetExtension(filePath).ToLower();
</span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> allowedExtensions.Contains(extension);
}</span></pre>
</div>
<ul>
<li data-tool="mdnice编辑器"><strong>检查文件的内容</strong></li>
</ul>
<p data-tool="mdnice编辑器">使用文件签名或魔法数字来验证文件的实际类型与声明的类型是否一致,防止扩展名欺骗。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">bool</span> IsFileSafeByContent(<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> filePath)
{
</span><span style="color: rgba(0, 0, 255, 1)">byte</span>[] magicNumbers =<span style="color: rgba(0, 0, 0, 1)"> File.ReadAllBytes(filePath);
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (magicNumbers.Length >= <span style="color: rgba(128, 0, 128, 1)">2</span> && magicNumbers[<span style="color: rgba(128, 0, 128, 1)">0</span>] == <span style="color: rgba(128, 0, 128, 1)">0xFF</span> && magicNumbers[<span style="color: rgba(128, 0, 128, 1)">1</span>] == <span style="color: rgba(128, 0, 128, 1)">0xD8</span>) <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> JPEG</span>
<span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Add checks for other formats...</span>
<span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
}</span></pre>
</div>
<ul>
<li data-tool="mdnice编辑器"><strong>扫描病毒和恶意软件</strong></li>
</ul>
<p data-tool="mdnice编辑器">使用反病毒软件或在线API来检查文件是否含有病毒或恶意软件,VirusTotal 提供了API来检查文件是否含有病毒,https://www.virustotal.com/ 具体示例如下</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.Net.Http;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.Threading.Tasks;
</span><span style="color: rgba(0, 0, 255, 1)">using</span> Newtonsoft.Json; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 需要安装Newtonsoft.Json NuGet包</span>
<span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> Program
{
</span><span style="color: rgba(0, 0, 255, 1)">static</span> <span style="color: rgba(0, 0, 255, 1)">async</span> Task Main(<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)">[] args)
{
</span><span style="color: rgba(0, 0, 255, 1)">string</span> apiKey = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">API密钥</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">string</span> fileUrl = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">文件ID</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 0, 255, 1)">string</span> url = $<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">https://www.virustotal.com/vtapi/v3/files/{fileUrl}/report</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">;
HttpClient client </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> HttpClient();
client.DefaultRequestHeaders.Add(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">x-apikey</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">, apiKey);
HttpResponseMessage response </span>= <span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> client.GetAsync(url);
</span><span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (response.IsSuccessStatusCode)
{
</span><span style="color: rgba(0, 0, 255, 1)">string</span> responseBody = <span style="color: rgba(0, 0, 255, 1)">await</span><span style="color: rgba(0, 0, 0, 1)"> response.Content.ReadAsStringAsync();
</span><span style="color: rgba(0, 0, 255, 1)">dynamic</span> report =<span style="color: rgba(0, 0, 0, 1)"> JsonConvert.DeserializeObject(responseBody);
</span><span style="color: rgba(0, 0, 255, 1)">if</span> (report.positives > <span style="color: rgba(128, 0, 128, 1)">0</span><span style="color: rgba(0, 0, 0, 1)">)
{
Console.WriteLine(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">文件含有病毒或恶意软件。</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
}
</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">
{
Console.WriteLine(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">文件安全。</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
}
}
</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)">
{
Console.WriteLine(</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">API请求失败。</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
}
}
}</span></pre>
</div>
<ul>
<li data-tool="mdnice编辑器"><strong>检查文件权限</strong></li>
</ul>
<p data-tool="mdnice编辑器">确保文件具有正确的权限,以防止未经授权的访问。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">bool</span> IsFileSafeByPermissions(<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> filePath)
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> fileInfo = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> FileInfo(filePath);
</span><span style="color: rgba(0, 0, 255, 1)">var</span> security =<span style="color: rgba(0, 0, 0, 1)"> fileInfo.GetAccessControl();
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Check permissions here...</span>
<span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">true</span>; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Placeholder logic</span>
}</pre>
</div>
<ul>
<li data-tool="mdnice编辑器"><strong>文件大小检查</strong></li>
</ul>
<p data-tool="mdnice编辑器">限制文件的大小,避免消耗过多的磁盘空间或内存。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">bool</span> IsFileSafeBySize(<span style="color: rgba(0, 0, 255, 1)">string</span> filePath, <span style="color: rgba(0, 0, 255, 1)">long</span><span style="color: rgba(0, 0, 0, 1)"> maxSizeInBytes)
{
</span><span style="color: rgba(0, 0, 255, 1)">var</span> fileInfo = <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> FileInfo(filePath);
</span><span style="color: rgba(0, 0, 255, 1)">return</span> fileInfo.Length <=<span style="color: rgba(0, 0, 0, 1)"> maxSizeInBytes;
}</span></pre>
</div>
<ul>
<li data-tool="mdnice编辑器"><strong>内容安全策略(CSP)</strong></li>
</ul>
<p data-tool="mdnice编辑器">在Web应用中,使用CSP来限制加载的资源类型和来源,防止XSS等攻击。</p>
<ul>
<li data-tool="mdnice编辑器"><strong>综合检查函数示例</strong></li>
</ul>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">private</span> <span style="color: rgba(0, 0, 255, 1)">bool</span> IsFileSafe(<span style="color: rgba(0, 0, 255, 1)">string</span><span style="color: rgba(0, 0, 0, 1)"> filePath)
{
</span><span style="color: rgba(0, 0, 255, 1)">return</span> IsFileSafeByExtension(filePath) &&<span style="color: rgba(0, 0, 0, 1)">
IsFileSafeByContent(filePath) </span>&&<span style="color: rgba(0, 0, 0, 1)">
IsFileSafeFromVirus(filePath) </span>&&<span style="color: rgba(0, 0, 0, 1)">
IsFileSafeByPermissions(filePath) </span>&&<span style="color: rgba(0, 0, 0, 1)">
IsFileSafeBySize(filePath, </span><span style="color: rgba(128, 0, 128, 1)">1024</span> * <span style="color: rgba(128, 0, 128, 1)">1024</span>); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> Limit to 1MB</span>
}</pre>
</div>
<p data-tool="mdnice编辑器">请注意,上述代码片段仅作为示例,实际应用中可能需要调整和补充具体的实现细节,例如引入实际的病毒扫描库或API,以及更复杂的权限和内容检查逻辑。</p>
<p data-tool="mdnice编辑器">安全检查是多层面的,需要结合具体的应用场景和需求进行综合考量。</p>
<h3 data-tool="mdnice编辑器"><span class="prefix"><span class="content">8、安全的Cookie处理</span></span></h3>
<p data-tool="mdnice编辑器">Cookies是Web开发中用于存储用户信息的一种常用机制,它们可以在客户端浏览器中保存小量的数据,以便服务器可以跟踪用户的偏好设置、登录状态等信息。然而,如果Cookie处理不当,可能会引发严重的安全问题,如数据泄露、会话劫持(Session Hijacking)和跨站脚本攻击(XSS)。因此,确保Cookie的安全处理至关重要。</p>
<p data-tool="mdnice编辑器">以下是处理Cookie时应当遵循的一些最佳实践:</p>
<ul>
<li data-tool="mdnice编辑器"><strong>使用HTTPS:</strong>传输Cookie时,务必使用HTTPS加密连接。HTTPS可以防止中间人攻击(Man-in-the-Middle Attack),保护Cookie数据免受窃听。</li>
<li data-tool="mdnice编辑器"><strong>设置HttpOnly标志:</strong>将Cookie标记为HttpOnly可以阻止JavaScript脚本访问Cookie,从而降低跨站脚本攻击(XSS)的风险。</li>
<li data-tool="mdnice编辑器"><strong>设置Secure标志:</strong>当Cookie被标记为Secure时,它们只会在HTTPS连接下发送,确保数据在传输过程中的安全性。</li>
<li data-tool="mdnice编辑器"><strong>限制Cookie的有效路径和域:</strong>通过设置Cookie的Path和Domain属性,可以控制哪些页面可以访问特定的Cookie,减少攻击面。</li>
<li data-tool="mdnice编辑器"><strong>使用SameSite属性:</strong>SameSite属性可以控制Cookie是否随跨站点请求发送,减少跨站请求伪造(CSRF)攻击的可能性。可以选择Strict、Lax或None三种模式之一。</li>
<li data-tool="mdnice编辑器"><strong>设置合理的过期时间:</strong>为Cookie设定一个适当的过期时间,可以避免永久性Cookie带来的安全风险,同时也便于清理不再需要的用户信息。</li>
<li data-tool="mdnice编辑器"><strong>定期审查和更新Cookie策略:</strong>定期检查Cookie的使用情况,确保所有Cookie设置符合最新的安全标准和隐私法规。</li>
</ul>
<p data-tool="mdnice编辑器">通过遵循这些最佳实践,可以大大增强应用程序的安全性,保护用户数据免受恶意攻击。在Web开发中,安全的Cookie处理不仅是技术要求,也是对用户隐私和数据安全的责任体现。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System;
</span><span style="color: rgba(0, 0, 255, 1)">using</span><span style="color: rgba(0, 0, 0, 1)"> System.Web;
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">class</span><span style="color: rgba(0, 0, 0, 1)"> CookieHandler : IHttpHandler
{
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">void</span><span style="color: rgba(0, 0, 0, 1)"> ProcessRequest(HttpContext context)
{
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 创建一个新的Cookie对象</span>
HttpCookie cookie = <span style="color: rgba(0, 0, 255, 1)">new</span> HttpCookie(<span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">UserSession</span><span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(0, 0, 0, 1)">);
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 设置Cookie值</span>
cookie.Value = <span style="color: rgba(128, 0, 0, 1)">"</span><span style="color: rgba(128, 0, 0, 1)">123456</span><span style="color: rgba(128, 0, 0, 1)">"</span>; <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 假设这是用户的唯一标识符
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 设置Cookie的过期时间</span>
cookie.Expires = DateTime.Now.AddDays(<span style="color: rgba(128, 0, 128, 1)">1</span>); <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 设置Cookie在一天后过期
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 设置HttpOnly属性以增加安全性</span>
cookie.HttpOnly = <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 如果你的网站支持HTTPS,设置Secure属性</span>
<span style="color: rgba(0, 0, 255, 1)">if</span><span style="color: rgba(0, 0, 0, 1)"> (context.Request.IsSecureConnection)
cookie.Secure </span>= <span style="color: rgba(0, 0, 255, 1)">true</span><span style="color: rgba(0, 0, 0, 1)">;
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 添加Cookie到响应中</span>
<span style="color: rgba(0, 0, 0, 1)"> context.Response.AppendCookie(cookie);
}
</span><span style="color: rgba(0, 0, 255, 1)">public</span> <span style="color: rgba(0, 0, 255, 1)">bool</span><span style="color: rgba(0, 0, 0, 1)"> IsReusable
{
</span><span style="color: rgba(0, 0, 255, 1)">get</span> { <span style="color: rgba(0, 0, 255, 1)">return</span> <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">; }
}
}</span></pre>
</div>
<p data-tool="mdnice编辑器">在.NET Core或.NET 6+中,使用不同的API来处理Cookie,例如<code>Microsoft.AspNetCore.Http</code>命名空间下的<code>IResponseCookies</code>接口。</p>
<h2 data-tool="mdnice编辑器"><span class="prefix"><span class="content">五、总结</span></span></h2>
<p data-tool="mdnice编辑器">通过文章的全面介绍 C# 网络编程,相信对这一块内容有了了解和理解。从简单的 HTTP 请求到复杂的套接字通信,从异步编程模型到安全协议的应用,每一步都为我们构建现代网络应用奠定了坚实的基。在实际项目中,根据需求深入学习和实践这些知识点,将有助于提升.NET开发者在网络编程领域的能力。持续学习和实践是成为优秀 .NET 开发者的不二法门。</p>
<p data-tool="mdnice编辑器">如果觉得这篇文章对你有用,欢迎加入微信公众号 [<strong>DotNet技术匠</strong>] 社区,与其他热爱技术的同行交流心得,共同成长。</p>
<p data-tool="mdnice编辑器"><img src="https://img2024.cnblogs.com/blog/576536/202407/576536-20240722093332651-213039456.png" alt="" style="display: block; margin-left: auto; margin-right: auto"></p><br><br>
来源:https://www.cnblogs.com/1312mn/p/18314234
頁:
[1]