当前位置: 首页 > article >正文

C# Socket网络通信【高并发场景】

用途

在 C# 中,Socket 类是用于在网络上进行低级别通信的核心类。它提供了对 TCP、UDP 等协议的支持,可以实现服务器和客户端之间的数据传输。Socket 提供了比 TcpClientUdpClient 等更细粒度的控制,因此通常用于需要更多控制的场景。

使用

服务器

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

class Server
{
    static void Main()
    {
        // 创建 Socket
        Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

        // 绑定到本地 IP 和端口
        IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, 11000);
        listener.Bind(localEndPoint);

        // 开始监听
        //服务器开始监听客户端连接,请求队列长度为 10,这意味着可以同时接受最多 10 个等待连接的客户端。
        listener.Listen(10);
        Console.WriteLine("等待客户端连接...");

        while (true) // 无限循环持续监听
        {
            // 接受客户端连接
            //会阻塞主线程,直到有客户端发起连接。这种设计确保了服务器能够同步处理每个客户端的连接。
            Socket handler = listener.Accept();
            Console.WriteLine("客户端已连接!");

            // 接收客户端发送的数据
            byte[] buffer = new byte[1024];
            int bytesRec = handler.Receive(buffer);
            Console.WriteLine("收到消息: " + Encoding.ASCII.GetString(buffer, 0, bytesRec));

            // 发送响应给客户端
            byte[] msg = Encoding.ASCII.GetBytes("消息已收到!");
            handler.Send(msg);

            // 关闭与客户端的连接
            handler.Shutdown(SocketShutdown.Both);
            handler.Close();
        }
    }
}

客户端

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

class Client
{
    static void Main()
    {
        // 创建 Socket
        Socket sender = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

        // 连接到服务器
        IPEndPoint remoteEP = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 11000);
        sender.Connect(remoteEP);

        Console.WriteLine("已连接到服务器!");

        // 发送数据给服务器
        byte[] msg = Encoding.ASCII.GetBytes("Hello Server!");
        sender.Send(msg);

        // 接收服务器的响应
        byte[] buffer = new byte[1024];
        int bytesRec = sender.Receive(buffer);
        Console.WriteLine("服务器响应: " + Encoding.ASCII.GetString(buffer, 0, bytesRec));

        // 关闭连接
        sender.Shutdown(SocketShutdown.Both);
        sender.Close();
    }
}

服务器:Bind/Listen/Send/Close/
客户端:Connect/Send/Receive/

高并发场景

当前的服务器示例是单线程的,它只能顺序处理一个客户端的连接。一旦接受并处理了一个客户端的请求,才会继续监听下一个客户端的连接请求。

问题:
由于服务器是单线程的,它在处理某个客户端的连接时(调用 accept、receive 等),其他客户端的连接请求会被阻塞,直到当前请求完成。这意味着服务器无法并发处理多个客户端的请求。

解决方案:
为了使服务器能够并发处理多个客户端的连接,我们可以使用多线程或异步编程。

多线程实现:

通过为每个客户端连接创建一个独立的线程,服务器可以同时处理多个客户端。这样,每个客户端的请求都会在一个单独的线程中进行处理,主线程可以继续监听新的连接请求。

下面是修改后的代码,使用多线程来处理多个客户端的连接:

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;

class Server
{
    static void Main()
    {
        // 创建一个 TCP Socket
        Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

        // 绑定到本地 IP 和端口
        IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, 11000);
        listener.Bind(localEndPoint);

        // 开始监听连接请求,队列长度为10
        listener.Listen(10);
        Console.WriteLine("服务器正在监听客户端连接...");

        while (true) // 无限循环持续监听
        {
            // 接受客户端连接
            Socket handler = listener.Accept();
            Console.WriteLine("客户端已连接!");

            // 为每个客户端连接创建一个新线程
            Thread clientThread = new Thread(() => HandleClient(handler));
            clientThread.Start();
        }
    }

    // 处理客户端连接的逻辑
    static void HandleClient(Socket clientSocket)
    {
        try
        {
            // 接收客户端发送的数据
            byte[] buffer = new byte[1024];
            int bytesRec = clientSocket.Receive(buffer);
            Console.WriteLine("收到消息: " + Encoding.ASCII.GetString(buffer, 0, bytesRec));

            // 发送响应给客户端
            byte[] msg = Encoding.ASCII.GetBytes("消息已收到!");
            clientSocket.Send(msg);
        }
        finally
        {
            // 关闭与客户端的连接
            clientSocket.Shutdown(SocketShutdown.Both);
            clientSocket.Close();
        }
    }
}

在每次有新的客户端连接时,服务器会调用 listener.Accept() 接受连接,然后为这个连接创建一个新的线程。
新线程会执行 HandleClient 方法,在该方法中处理客户端的通信(接收消息、发送响应等)。
并发处理:

服务器主线程通过 while (true) 持续监听客户端的连接请求。
每次有客户端连接时,服务器会创建一个新的线程处理该连接,这样主线程不会被阻塞,可以继续接受其他客户端的连接请求。
线程处理客户端:
HandleClient 方法会在线程中执行,处理特定客户端的通信。
在该线程中,服务器接收客户端的数据并发送响应,最后关闭连接。
多线程的优缺点:

  • 优点:
    • 能够同时处理多个客户端的连接,提升服务器的并发能力。
    • 每个客户端的请求处理相互独立,不会因为某个客户端的处理时间过长而阻塞其他客户端的连接。
  • 缺点:
    • 每个客户端连接都创建一个新线程,随着客户端数量的增加,可能会消耗大量的系统资源(如 CPU 和内存)。
    • 线程的创建和销毁是有开销的,在高并发场景下,可能不够高效。

异步编程实现:

在 C# 中,还可以使用异步编程(async/await)来处理多个客户端的连接,避免创建大量线程。异步方法允许服务器在等待 I/O 操作(如 Receive、Send)完成时,不会阻塞主线程,具有更好的扩展性。

异步服务器示例:

using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

class Server
{
    static async Task Main()
    {
        // 创建一个 TCP Socket
        Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

        // 绑定到本地 IP 和端口
        IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Any, 11000);
        listener.Bind(localEndPoint);

        // 开始监听连接请求,队列长度为10
        listener.Listen(10);
        Console.WriteLine("服务器正在监听客户端连接...");

        while (true)
        {
            // 异步接受客户端连接
            Socket handler = await Task.Factory.FromAsync(listener.BeginAccept, listener.EndAccept, null);
            Console.WriteLine("客户端已连接!");

            // 异步处理客户端连接
            _ = Task.Run(() => HandleClient(handler));
        }
    }

    // 处理客户端连接的逻辑
    static async Task HandleClient(Socket clientSocket)
    {
        try
        {
            byte[] buffer = new byte[1024];
            // 异步接收客户端发送的数据
            int bytesRec = await Task.Factory.FromAsync(
                clientSocket.BeginReceive(buffer, 0, buffer.Length, SocketFlags.None, null, null),
                clientSocket.EndReceive);

            Console.WriteLine("收到消息: " + Encoding.ASCII.GetString(buffer, 0, bytesRec));

            // 异步发送响应给客户端
            byte[] msg = Encoding.ASCII.GetBytes("消息已收到!");
            await Task.Factory.FromAsync(
                clientSocket.BeginSend(msg, 0, msg.Length, SocketFlags.None, null, null),
                clientSocket.EndSend);
        }
        finally
        {
            // 关闭与客户端的连接
            clientSocket.Shutdown(SocketShutdown.Both);
            clientSocket.Close();
        }
    }
}

异步编程的优缺点:

  • 优点:
    • 异步编程模型更高效,因为它不需要为每个客户端连接创建新的线程。【而是通过申请线程池中的线程来完成任务】
    • 能够处理大量并发请求而不会消耗太多系统资源。
  • 缺点:
    • 代码复杂度较高,需要熟悉异步编程模型和 async/await 语法。

总结:

  • 单线程:示例中初始的服务器是单线程的,只能顺序处理一个客户端的连接请求。无法并发处理多个客户端。
  • 多线程:可以通过为每个客户端连接创建一个新线程来实现并发处理,能够处理多个客户端,但线程的创建和销毁有开销。
  • 异步编程:通过异步方法来处理客户端连接,在处理 I/O 操作时不会阻塞线程,能够处理大量并发请求,且资源消耗较低。

如果你的服务器需要处理大量并发请求,异步编程模型可能是更优的选择。如果并发量较小,多线程方式也是一个有效的方案。


http://www.kler.cn/news/312067.html

相关文章:

  • 【QT】重载信号Connect链接使用方式
  • cuda中使用二维矩阵
  • SpringCloud系列之一---搭建高可用的Eureka注册中心
  • 使用密钥文件登陆Linux服务器
  • 【论文阅读】Grounding Language with Visual Affordances over Unstructured Data
  • 【vue3】vue3.3新特性真香
  • 兔子检测系统源码分享
  • AtCoder Beginner Contest 371
  • 在SpringCloud中实现服务熔断与降级,保障系统稳定性
  • vue2中字符串动态拼接字段给到接口
  • 机器学习(西瓜书)第 14 章 概率图模型
  • 程序员的养生
  • NET WPF使用组件库HandyControl
  • Github 2024-09-14 Rust开源项目日报Top10
  • 傅里叶变换
  • vim 操作一列数字
  • 【天怡AI-注册安全分析报告-无验证方式导致安全隐患】
  • javascript 浏览器打印不同页面设置方向,横向纵向打印
  • CPLEX+Yalmip+MATLAB2022a配置
  • 【贪心算法】贪心算法一
  • vue前端调起电脑应用并且检测有没有调用成功
  • 人工智能将来好就业吗?
  • LINUX的PHY抽象层——PAL
  • Qt/C++ 了解NTFS文件系统,解析盘符引导扇区数据获取MFT(Master File Table)主文件表偏移地址
  • 服务发现和代理实例的自动更新
  • Linux 基本使用和 web 程序部署 ( 8000 字 Linux 入门 )
  • 【python】后台程序初始化全流程
  • electron-vue安装与打包问题解决
  • js中箭头函数与普通函数的区别
  • 删除视频最后几帧 剪切视频