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

news2024/9/20 10:54:21

用途

在 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.coloradmin.cn/o/2148717.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Github 2024-09-20 Java开源项目日报Top10

根据Github Trendings的统计,今日(2024-09-20统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Java项目10Kotlin项目1Python项目1JavaGuide - Java 程序员学习和面试指南 创建周期:2118 天开发语言:Java协议类型:Apache License 2.0Star…

二叉树(下)

目录 判断树是否相同 判断树是不是另一棵树的子树 二叉树翻转 判断平衡二叉树 二叉树层序遍历 这篇主要提供一些关于二叉树例题的讲解,如果对二叉树及其基本操作有疑问的可以转至: 二叉树(上)-CSDN博客二叉树(中&…

imo云办公室 Imo_DownLoadUI.php 任意文件下载漏洞复现

0x01 漏洞描述: imo云办公室由上海易睦网络科技有限公司于2007年创立,总部位于上海,imo云办公室管理运营企业即时通讯平台imo,包括对imo的在线支持,故障处理,客户服务等,对imo进行持续研发&…

第五届“马栏山杯”国际音视频算法大赛创新应用赛投票环节正式启动啦!

本次大赛分为专业奖和大众人气奖。专业奖由评委直接选出。大众人气奖经组委会初筛后选出62组优秀作品,由网络投票选出40名获奖选手! 快快戳链接🔗:https://h5.shuziwenbo.cn/challenge/vote?cxidedcrfv看看自己的作品有没有入选吧…

【AI视频】Runway Gen-2:图文生视频与运动模式详解

博客主页: [小ᶻZ࿆] 本文专栏: AI视频 | Runway 文章目录 💯前言💯仅图片生成视频方法一:通过Midjourney生成图片方法二:通过Runway预览生成图片注意点 💯图加文生成视频方式一:Midjourney…

Web端云剪辑解决方案,BS架构私有化部署,安全可控

传统视频制作流程繁琐、耗时,且对专业设备和软件的高度依赖,常常让企业望而却步,美摄科技凭借其强大的技术实力和创新能力,推出了面向企业用户的Web端云剪辑解决方案,为企业提供一站式、高效、便捷的视频生产平台。 B…

计算机考研408-计算机网络

【题33】下列选项中,不属于网络体系结构所描述的内容是() A.网络的层次 B.每一层使用的协议 C.协议的内部实现细节 D.每一层必须完成的功能 解析: 本题考查的是网络体系结构相关的概念。 图1描述了网络的7层架构以及每一层所要完成…

无人机 PX4 飞控 | EKF 使用传感器汇总与添加传感器方法

无人机 PX4 飞控 | EKF 使用传感器汇总与添加传感器方法 前言飞控自带基本传感器IMU磁力计气压计静态气压位置误差校正气压计偏压补偿 高度传感器测距仪使能测距仪融合有条件 可额外配置的传感器光流外部视觉系统 新传感器确定传感器类型和接口更新硬件定义添加驱动更新EKF 前言…

MySQL:事务的ACID特性隔离级别脏读、不可重复读、幻读、Next-Key锁——场景复现

目录 1、什么是事务 2、 事务的ACID特性 2.1 事务的隔离性 3、为什么要使用事务? 4、查看支持事务的存储引擎 5、使用事务 5.1 控制事务 5.1.1 开启事务 5.1.2 关闭事务 5.2 开始一个事务,执行修改后回滚 5.3 开始一个事务,执行修…

观后感:《中国数据库前世今生》——时代变迁中的数据库崛起

最近观看了《中国数据库前世今生》纪录片,这部影片详细梳理了从1980年代至今,中国数据库技术发展的跌宕历程。作为一名程序员,这部纪录片让我不禁感慨数据库技术的飞速进步,也让我更深入地理解了数据库技术在我们日常生活中的重要…

如何借助项目管理系统实现审批流程的自动化与标准化?

在快节奏的项目申报领域中,繁琐的审批流程往往成为制约项目推进速度的瓶颈。传统的人工审批方式不仅耗时耗力,还容易因人为因素导致审批效率低下、结果不一致等问题。为此,一款能够支持在线审批流程、实现审批自动化与标准化的项目管理系统显…

Canal+RabbitMQ数据同步环境配置

Canal 是阿里巴巴开发的开源工具,主要用于解析 MySQL 的 binlog 日志,从而实现数据同步。Canal 会模拟 MySQL 从库的协议,订阅主库的 binlog,从而获取数据库的变更信息。 将 Canal 解析到的 MySQL 数据库变更消息通过 RabbitMQ 分…

算法打卡 Day34(贪心算法)-分发饼干 + 摆动序列 + 最大子序和

文章目录 理论基础Leetcode 455-分发饼干题目描述解题思路类似题目2410-运动员和训练师的最大匹配数 Leetcode 376-摆动序列题目描述解题思路 Leetcode 53-最大子序和题目描述解题思路 理论基础 贪心算法的本质是选择每一阶段的局部最优,从而达到全局最优。 贪心算…

力扣718-最长重复子数组(Java详细题解)

题目链接:718. 最长重复子数组 - 力扣(LeetCode) 前情提要: 因为本人最近都来刷dp类的题目所以该题就默认用dp方法来做。 dp五部曲。 1.确定dp数组和i下标的含义。 2.确定递推公式。 3.dp初始化。 4.确定dp的遍历顺序。 5…

【CMake】使用CMake在Visual Studio内构建多文件夹工程

一、配置准备 打开VIsual Studio,载入写好的 C M a k e l i s t s . t x t CMakelists.txt CMakelists.txt,在项目中添加以下文件: 创建一个文件夹 f u n c s funcs funcs,里面放入 f u n c . h func.h func.h、 f u n c . c p …

fmql之驱动程序编写(首次)

看了正点原子的zynq系列的Linux开发指南(pdf和视频均有),因此从最简单的程序开始。 驱动程序开发:(第四期视频) 第3.1讲 我的第一个Linux驱动-字符设备驱动框架_哔哩哔哩_bilibili 学习驱动程序编写之前&am…

【论文串烧】多媒体推荐中的模态平衡学习 | 音视频语音识别中丢失导致的模态偏差对丢失视频帧鲁棒性的影响

文章目录 一、多媒体推荐中的模态平衡学习1.1 研究背景1.2 解决问题1.3 实施方案1.4 文章摘要1.5 文章重点1.6 文章图示图 1:不同模型变体在 AmazonClothing 数据集上的初步研究图 2:CKD模型架构的说明图 3:在 Amazon-Clothing 数据集上训练过…

【Linux:共享内存】

共享内存的概念: 操作系统通过页表将共享内存的起始虚拟地址映射到当前进程的地址空间中共享内存是由需要通信的双方进程之一来创建但该资源并不属于创建它的进程,而属于操作系统 共享内存可以在系统中存在多份,供不同个数,不同进…

Qt窗口——QStatusBar

文章目录 状态栏状态栏创建状态栏显示临时消息状态栏添加子控件 状态栏 QStatusBar状态栏是应用程序中输出简要信息的区域,例如画图板下面的区域 我们也可以给程序设置状态栏,表示一些状态。 状态栏创建 使用Qt Creator创建项目的时候,如果…

实现一种可插拔的参数校验

1、概述 仿照mybatis的二级缓存的实现方式,使用“策略模式配置” 的方式实现一个可动态插拔的 参数校验,便于后期扩展。 实现方式也很简单,首先定义一个校验接口,并提供一个校验方法;每种参数校验都是实现 了该校验接口…