当前位置:首页 > 技术 > 正文内容

基于TCP的异步Socket通信实现解析

访客 技术 2026年6月28日 1

在C#网络编程中,Socket通信遵循一套标准的流程,涉及套接字创建、绑定、监听、连接、数据收发和关闭等阶段。以下是对这一过程的详细阐述,包括异步模式的实现原理和代码示例。

Socket通信的基本流程

  1. 启动连接前,必须先创建Socket对象并调用Bind方法。Socket用于初始化套接字实例,Bind用于将套接字绑定到特定的IP地址和端口号(对于客户端,Connect时会由系统自动分配端口,因此可省略Bind)。
  2. 服务端通过Listen方法进入监听状态,等待客户端连接请求。
  3. 客户端调用Connect建立连接,服务端通过Accept接受连接。在Connect-Accept交互中,操作系统底层会完成TCP三次握手。
  4. 连接建立后,双方通过Write和Read(或Send/Receive)交换数据,操作系统负责TCP数据的确认与重发等可靠性机制。
  5. 通信结束后,调用Close关闭连接,操作系统执行四次挥手以终止连接。

异步通信原理

Socket编程继承了文件操作的核心思想:打开、读写、关闭。在C/S模式下,服务端执行:打开通道并绑定,进入监听状态,接收客户端请求并创建专用连接进行读写,处理完成后关闭专用连接(可重复循环)。客户端则简化为:打开通道并连接,数据交互,关闭通道。

Socket通信分为两种模式:

  • 同步模式:客户端发送请求后必须等待服务端响应才能继续,串行执行。
  • 异步模式:客户端发送请求后无需等待响应即可继续其他操作,并行执行。

套接字的工作模式:

  • 阻塞模式:调用Socket方法时,当前线程会被挂起,直到操作完成才返回结果。
  • 非阻塞模式:调用Socket方法立即返回,无论操作是否完成。

套接字工作步骤:

  • 服务器监听:服务端套接字不绑定特定客户端,而是处于等待连接的状态,持续监控网络。
  • 客户端请求连接:客户端发送连接请求,目标为服务端套接字的地址和端口。
  • 连接确认:服务端监听到请求后,响应并创建新线程,将服务端套接字描述发送给客户端,确认后连接建立;原服务端套接字继续监听其他请求。

异步通信示例代码

以下示例展示了基于TCP的异步Socket服务器实现,包括连接池管理、异步接受连接和接收数据。

// 初始化连接池,maxConn为最大客户端数量
Conn[] connections = new Conn[maxConn];
for (int i = 0; i < maxConn; i++)
{
    connections[i] = new Conn();
}

// 创建TCP套接字并绑定本地端点
Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPEndPoint localEP = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 11000);
serverSocket.Bind(localEP);
serverSocket.Listen(maxConn);
Console.WriteLine("[服务器]启动成功,等待连接...");

// 开始异步接受连接
serverSocket.BeginAccept(AcceptCallback, null);

// 保持服务器运行,输入"quit"退出
while (true)
{
    if (Console.ReadLine() == "quit")
        return;
}
/// <summary>
/// Accept回调,处理新客户端连接
/// </summary>
private static void AcceptCallback(IAsyncResult asyncResult)
{
    try
    {
        Socket clientSocket = serverSocket.EndAccept(asyncResult);

        int slotIndex = GetAvailableIndex();
        if (slotIndex < 0)
        {
            clientSocket.Close();
            Console.WriteLine("[警告] 连接池已满,拒绝连接");
        }
        else
        {
            Conn conn = connections[slotIndex];
            conn.Initialize(clientSocket);
            string clientAddress = conn.GetRemoteAddress();
            Console.WriteLine($"客户端连接 [{clientAddress}],连接池索引:{slotIndex}");

            // 开始异步接收数据
            conn.socket.BeginReceive(conn.receiveBuffer, conn.bufferOffset, conn.GetRemainingBufferSize(),
                SocketFlags.None, ReceiveCallback, conn);
        }

        // 继续接受下一个连接
        serverSocket.BeginAccept(AcceptCallback, null);
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Accept回调异常:{ex.Message}");
    }
}
/// <summary>
/// 接收回调,处理客户端发送的数据
/// </summary>
private static void ReceiveCallback(IAsyncResult asyncResult)
{
    Conn conn = (Conn)asyncResult.AsyncState;
    try
    {
        int bytesReceived = conn.socket.EndReceive(asyncResult);

        // 客户端断开连接
        if (bytesReceived <= 0)
        {
            Console.WriteLine($"客户端 [{conn.GetRemoteAddress()}] 断开连接");
            conn.Close();
            return;
        }

        // 解析接收到的数据
        string receivedMessage = Encoding.UTF8.GetString(conn.receiveBuffer, 0, bytesReceived);
        Console.WriteLine($"收到 [{conn.GetRemoteAddress()}] 数据:{receivedMessage}");

        // 构建响应消息
        string response = $"{conn.GetRemoteAddress()} 发送的消息:{receivedMessage}";
        byte[] responseBytes = Encoding.UTF8.GetBytes($"服务端已收到:{response}");

        // 广播给当前连接池中的第一个活跃连接(示例为点播)
        for (int i = 0; i <= 0; i++)
        {
            if (connections[i] != null && connections[i].isActive)
            {
                Console.WriteLine($"转发消息给 {connections[i].GetRemoteAddress()}");
                connections[i].socket.Send(responseBytes);
            }
        }

        // 继续异步接收数据
        conn.socket.BeginReceive(conn.receiveBuffer, conn.bufferOffset, conn.GetRemainingBufferSize(),
            SocketFlags.None, ReceiveCallback, conn);
    }
    catch (Exception ex)
    {
        Console.WriteLine($"接收数据时客户端 [{conn.GetRemoteAddress()}] 断开,异常:{ex.Message}");
        conn.Close();
    }
}

相关文章

Linux crontab 详解

1) crontab 是什么cron 是 Linux 的定时任务守护进程;crontab 是用来编辑/查看“按时间周期执行命令”的表(cron table)。常见两类:用户 crontab:每个用户一份(crontab -e 编辑)系统级 crontab / cron.d:可指定执行用户(/etc/crontab、/etc/cron.d/*)2) crontab 时间...

富文本里可以允许的 HTML 属性

一、所有标签默认允许的安全属性(极少)class        (可选)id           (通常建议禁用)title️ 注意:id 容易被滥用做锚点注入,很多系统直接禁用class 允许的话最好只允许固定前缀(如 editor-*)二、a 标签允许属性<a href="" t...

Mac 安装 Node.js 指南

方法一:通过官网安装包(最简单,适合初学者)如果你只是想快速安装并开始使用,这是最直接的方法。访问 Node.js 官网。页面会显示两个版本:LTS (Recommended For Most Users):长期支持版,最稳定。建议选这个。Current:最新特性版,包含最新功能但可能不够稳定。下载 .pkg 安装包并运行。按照安装向导点击“下一步”即可完成。方法二:使用 Homebrew 安装(...

Dom\HTML_NO_DEFAULT_NS 的副作用:自动加闭合标签

在使用Dom\HTMLDocument时,Dom\HTML_NO_DEFAULT_NS 将禁止在解析过程中设置元素的命名空间, 此设置是为了与DOMDocument向后兼容而存在的。当使用它时,已知的一个副作用就是:自动加闭合标签例如 </img> 为什么会这样?当你使用:Dom\HTML_NO_DEFAULT_NS文档会变成 无命名空间模式,此时内部更接近 XML...

Laravel 事件和监听器创建

在 Laravel 中,使用 Artisan 命令创建 Events(事件) 和 Listeners(监听器) 是非常高效的。你可以通过以下几种方式来实现:1. 手动创建单个 Event如果你只想创建一个事件类,可以使用 make:event 命令:Bashphp artisan make:event UserRegistered执行后,文件将生成在 app/Even...

自定义域名解析神器 dnsmasq

什么是 dnsmasq?dnsmasq 是一个轻量级、功能强大的网络服务工具,专为小型和中等规模网络设计。它是一个综合的网络基础设施解决方案[1]。dnsmasq 能做什么?功能说明应用场景DNS 转发与缓存将 DNS 查询转发到上游服务器(ISP、Google DNS 等),并在本地缓存结果加快 DNS 查询速度,减少外部 DNS 流量本地 DNS解析本地网络设备的主机名,无需编辑&n...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。