MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

C#网络编程:Socket通信与HttpClient进阶

2021-02-166.3k 阅读

C# 网络编程:Socket 通信

Socket 通信基础概念

在 C# 网络编程中,Socket(套接字)是一种跨网络进程间通信的机制。它起源于 Unix 系统,后被广泛应用于各类操作系统平台。Socket 为应用程序提供了一种与网络交互的抽象层,使得不同主机上的进程能够进行数据传输。

从本质上讲,Socket 是 IP 地址与端口号的组合。IP 地址用于标识网络中的主机,而端口号则用于标识主机上的特定进程。这种组合允许数据在网络中准确地传输到目标应用程序。Socket 通信基于传输层协议,常见的有 TCP(传输控制协议)和 UDP(用户数据报协议)。

TCP 是一种面向连接的协议,它提供可靠的数据传输。在进行数据传输之前,TCP 会在客户端和服务器之间建立一条虚拟的连接,通过三次握手来确保连接的可靠性。数据在传输过程中会进行编号和确认,若有数据丢失或错误,TCP 会自动重传,保证数据的完整性和顺序性。

UDP 则是一种无连接的协议,它不保证数据的可靠传输。UDP 直接将数据报发送到目标地址,不进行连接的建立和确认。虽然 UDP 不具备 TCP 的可靠性,但它的优点是传输速度快,开销小,适用于对实时性要求高但对数据准确性要求相对较低的场景,如实时视频流、音频流传输等。

使用 TCP Socket 进行通信

  1. 服务器端实现 首先创建一个简单的 TCP 服务器端示例。在 C# 中,我们使用 System.Net.Sockets 命名空间来操作 Socket。
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

class TcpServer
{
    private const int BufferSize = 1024;
    private TcpListener _tcpListener;

    public TcpServer(int port)
    {
        _tcpListener = new TcpListener(IPAddress.Any, port);
    }

    public void Start()
    {
        _tcpListener.Start();
        Console.WriteLine($"服务器已启动,正在监听端口 {_tcpListener.LocalEndpoint}");

        while (true)
        {
            TcpClient client = _tcpListener.AcceptTcpClient();
            Console.WriteLine($"客户端 {client.Client.RemoteEndPoint} 已连接");

            NetworkStream stream = client.GetStream();
            byte[] buffer = new byte[BufferSize];
            int bytesRead = stream.Read(buffer, 0, BufferSize);
            string message = Encoding.UTF8.GetString(buffer, 0, bytesRead);
            Console.WriteLine($"收到客户端消息: {message}");

            string response = "消息已收到";
            byte[] responseBytes = Encoding.UTF8.GetBytes(response);
            stream.Write(responseBytes, 0, responseBytes.Length);

            stream.Close();
            client.Close();
        }
    }

    public void Stop()
    {
        _tcpListener.Stop();
        Console.WriteLine("服务器已停止");
    }
}

在上述代码中,我们创建了一个 TcpServer 类。构造函数中初始化 TcpListener 并绑定到指定端口。Start 方法启动服务器并进入一个无限循环,等待客户端连接。当有客户端连接时,接受连接并获取 NetworkStream,通过 Stream 读取客户端发送的消息,然后向客户端发送响应消息。最后关闭 StreamTcpClient

  1. 客户端实现 接下来实现对应的 TCP 客户端。
using System;
using System.Net.Sockets;
using System.Text;

class TcpClientApp
{
    private const string ServerIp = "127.0.0.1";
    private const int ServerPort = 12345;

    public void SendMessage()
    {
        using (TcpClient client = new TcpClient(ServerIp, ServerPort))
        {
            NetworkStream stream = client.GetStream();
            string message = "Hello, Server!";
            byte[] messageBytes = Encoding.UTF8.GetBytes(message);
            stream.Write(messageBytes, 0, messageBytes.Length);

            byte[] buffer = new byte[1024];
            int bytesRead = stream.Read(buffer, 0, buffer.Length);
            string response = Encoding.UTF8.GetString(buffer, 0, bytesRead);
            Console.WriteLine($"收到服务器响应: {response}");
        }
    }
}

在客户端代码中,我们使用 TcpClient 连接到指定的服务器 IP 和端口。创建 NetworkStream 后,向服务器发送消息,并读取服务器的响应消息。

使用 UDP Socket 进行通信

  1. 服务器端实现 UDP 服务器端的实现与 TCP 有所不同,因为 UDP 不需要建立连接。
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

class UdpServer
{
    private const int BufferSize = 1024;
    private UdpClient _udpClient;

    public UdpServer(int port)
    {
        _udpClient = new UdpClient(port);
    }

    public void Start()
    {
        IPEndPoint clientEndPoint = new IPEndPoint(IPAddress.Any, 0);
        while (true)
        {
            byte[] receiveBytes = _udpClient.Receive(ref clientEndPoint);
            string message = Encoding.UTF8.GetString(receiveBytes);
            Console.WriteLine($"收到客户端消息: {message} 来自 {clientEndPoint}");

            string response = "消息已收到";
            byte[] responseBytes = Encoding.UTF8.GetBytes(response);
            _udpClient.Send(responseBytes, responseBytes.Length, clientEndPoint);
        }
    }

    public void Stop()
    {
        _udpClient.Close();
        Console.WriteLine("服务器已停止");
    }
}

在 UDP 服务器端代码中,我们创建 UdpClient 并绑定到指定端口。通过 Receive 方法接收客户端发送的数据,同时获取客户端的 IPEndPoint。接收到消息后,向客户端发送响应消息。

  1. 客户端实现
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

class UdpClientApp
{
    private const string ServerIp = "127.0.0.1";
    private const int ServerPort = 12345;

    public void SendMessage()
    {
        using (UdpClient client = new UdpClient())
        {
            IPEndPoint serverEndPoint = new IPEndPoint(IPAddress.Parse(ServerIp), ServerPort);
            string message = "Hello, Server!";
            byte[] messageBytes = Encoding.UTF8.GetBytes(message);
            client.Send(messageBytes, messageBytes.Length, serverEndPoint);

            IPEndPoint receiveEndPoint = new IPEndPoint(IPAddress.Any, 0);
            byte[] receiveBytes = client.Receive(ref receiveEndPoint);
            string response = Encoding.UTF8.GetString(receiveBytes);
            Console.WriteLine($"收到服务器响应: {response}");
        }
    }
}

在 UDP 客户端代码中,创建 UdpClient 并向指定的服务器 IPEndPoint 发送消息。然后通过 Receive 方法接收服务器的响应消息。

C# 网络编程:HttpClient 进阶

HttpClient 基础使用

HttpClient 是 .NET 中用于发送 HTTP 请求和接收 HTTP 响应的强大工具。它位于 System.Net.Http 命名空间下。在使用 HttpClient 之前,需要先创建一个 HttpClient 实例。

using System;
using System.Net.Http;
using System.Threading.Tasks;

class HttpClientExample
{
    private readonly HttpClient _httpClient;

    public HttpClientExample()
    {
        _httpClient = new HttpClient();
    }

    public async Task<string> GetStringAsync(string url)
    {
        HttpResponseMessage response = await _httpClient.GetAsync(url);
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadAsStringAsync();
    }
}

在上述代码中,我们创建了一个 HttpClientExample 类,在构造函数中初始化 HttpClientGetStringAsync 方法使用 HttpClientGetAsync 方法发送 GET 请求,获取 HttpResponseMessage。通过 EnsureSuccessStatusCode 方法检查响应状态码,如果状态码不是 2xx 系列,会抛出异常。最后通过 ReadAsStringAsync 方法读取响应内容并返回字符串。

设置请求头

在实际应用中,常常需要设置请求头来满足不同的服务器要求或携带额外信息。

public async Task<string> GetStringWithHeadersAsync(string url)
{
    _httpClient.DefaultRequestHeaders.Add("User - Agent", "MyApp/1.0");
    _httpClient.DefaultRequestHeaders.Add("Accept", "application/json");

    HttpResponseMessage response = await _httpClient.GetAsync(url);
    response.EnsureSuccessStatusCode();
    return await response.Content.ReadAsStringAsync();
}

GetStringWithHeadersAsync 方法中,我们通过 DefaultRequestHeaders 属性添加了 User - AgentAccept 请求头。User - Agent 用于标识客户端应用程序,Accept 用于告诉服务器客户端能够接受的响应内容类型。

发送 POST 请求

发送 POST 请求时,通常需要在请求体中携带数据。

public async Task<string> PostDataAsync(string url, string content)
{
    HttpContent httpContent = new StringContent(content, Encoding.UTF8, "application/json");
    HttpResponseMessage response = await _httpClient.PostAsync(url, httpContent);
    response.EnsureSuccessStatusCode();
    return await response.Content.ReadAsStringAsync();
}

PostDataAsync 方法中,我们创建了一个 StringContent 实例,将需要发送的数据以 JSON 格式包装,并设置编码为 UTF - 8。然后使用 PostAsync 方法发送 POST 请求,同时将 HttpContent 作为请求体传递。

处理响应

  1. 解析 JSON 响应 当服务器返回 JSON 格式的数据时,我们可以使用 System.Text.JsonNewtonsoft.Json 库来解析。
using System.Text.Json;

public async Task<T> GetJsonDataAsync<T>(string url)
{
    HttpResponseMessage response = await _httpClient.GetAsync(url);
    response.EnsureSuccessStatusCode();
    string json = await response.Content.ReadAsStringAsync();
    return JsonSerializer.Deserialize<T>(json);
}

GetJsonDataAsync 方法中,我们通过 JsonSerializer.Deserialize 方法将 JSON 字符串反序列化为指定类型 T 的对象。

  1. 处理错误响应 除了检查状态码,还可以根据响应内容来处理错误。
public async Task<string> HandleErrorResponseAsync(string url)
{
    try
    {
        HttpResponseMessage response = await _httpClient.GetAsync(url);
        if (response.IsSuccessStatusCode)
        {
            return await response.Content.ReadAsStringAsync();
        }
        else
        {
            string errorContent = await response.Content.ReadAsStringAsync();
            Console.WriteLine($"错误状态码: {response.StatusCode},错误内容: {errorContent}");
            return null;
        }
    }
    catch (HttpRequestException ex)
    {
        Console.WriteLine($"请求异常: {ex.Message}");
        return null;
    }
}

HandleErrorResponseAsync 方法中,我们首先检查响应状态码。如果状态码表示成功,读取响应内容。否则,读取错误内容并打印状态码和错误信息。同时捕获 HttpRequestException 异常并处理。

使用 HttpClientFactory

在 ASP.NET Core 应用程序中,推荐使用 HttpClientFactory 来创建 HttpClient 实例。这有助于管理 HttpClient 的生命周期,避免资源泄漏等问题。

  1. 注册 HttpClientFactoryStartup.cs 文件中,添加如下代码:
using Microsoft.Extensions.DependencyInjection;

public void ConfigureServices(IServiceCollection services)
{
    services.AddHttpClient();
}
  1. 使用 HttpClientFactory 在控制器或其他服务中,可以通过依赖注入获取 HttpClient
using Microsoft.AspNetCore.Mvc;
using System.Net.Http;
using System.Threading.Tasks;

public class MyController : ControllerBase
{
    private readonly HttpClient _httpClient;

    public MyController(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    [HttpGet]
    public async Task<string> GetData()
    {
        HttpResponseMessage response = await _httpClient.GetAsync("https://example.com/api/data");
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadAsStringAsync();
    }
}

在上述代码中,HttpClient 通过构造函数注入到控制器中。这样可以确保 HttpClient 的正确管理和复用。

高级特性:自定义 HttpMessageHandler

HttpMessageHandlerHttpClient 处理请求和响应的核心组件。通过自定义 HttpMessageHandler,可以实现诸如日志记录、请求重试等高级功能。

  1. 创建自定义 HttpMessageHandler
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

public class LoggingHandler : DelegatingHandler
{
    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        Console.WriteLine($"发送请求: {request.Method} {request.RequestUri}");
        HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
        Console.WriteLine($"收到响应: {response.StatusCode}");
        return response;
    }
}

LoggingHandler 类中,我们继承自 DelegatingHandler,并重写 SendAsync 方法。在方法中,打印请求的方法和 URL,然后调用基类的 SendAsync 方法发送请求,并在收到响应后打印响应状态码。

  1. 使用自定义 HttpMessageHandler
public class CustomHttpClientExample
{
    private readonly HttpClient _httpClient;

    public CustomHttpClientExample()
    {
        HttpMessageHandler handler = new LoggingHandler();
        _httpClient = new HttpClient(handler);
    }

    public async Task<string> GetStringAsync(string url)
    {
        HttpResponseMessage response = await _httpClient.GetAsync(url);
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadAsStringAsync();
    }
}

CustomHttpClientExample 类中,我们创建了 LoggingHandler 实例,并将其传递给 HttpClient 的构造函数。这样,在使用 HttpClient 发送请求时,就会执行 LoggingHandler 中的日志记录逻辑。

性能优化与注意事项

  1. 复用 HttpClient 实例 避免在频繁的操作中每次都创建新的 HttpClient 实例。HttpClient 内部维护了连接池,复用实例可以提高性能并减少资源消耗。
  2. 设置合理的超时时间 通过 HttpClient.Timeout 属性设置合理的请求超时时间,避免长时间等待无响应的请求,浪费资源。
_httpClient.Timeout = TimeSpan.FromSeconds(10);
  1. 处理大响应数据 对于大响应数据,避免一次性读取整个响应内容到内存中。可以使用流的方式逐步处理响应数据,例如:
public async Task ProcessLargeResponseAsync(string url)
{
    HttpResponseMessage response = await _httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
    response.EnsureSuccessStatusCode();
    using (Stream stream = await response.Content.ReadAsStreamAsync())
    {
        // 在这里对流进行处理,例如写入文件等
    }
}

在上述代码中,通过 HttpCompletionOption.ResponseHeadersRead 选项,在接收到响应头后就开始处理,而不是等待整个响应内容下载完成。然后通过 ReadAsStreamAsync 方法获取响应流,进行后续处理。

  1. 安全考虑 在发送敏感数据时,确保使用 HTTPS 协议。同时,验证服务器的证书,防止中间人攻击。可以通过 HttpClientHandler.ServerCertificateCustomValidationCallback 来实现自定义的证书验证逻辑。
HttpClientHandler handler = new HttpClientHandler();
handler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) =>
{
    // 自定义证书验证逻辑,返回 true 或 false
    return true;
};
HttpClient httpClient = new HttpClient(handler);

通过深入理解和掌握 C# 中的 Socket 通信和 HttpClient 的进阶用法,开发者能够更加高效地构建健壮、高性能的网络应用程序,满足不同场景下的网络通信需求。无论是开发实时交互的应用,还是与各种 Web 服务进行数据交互,这些知识和技能都将发挥重要作用。