You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# Socket多线程数据交互:学生信息查询功能实现问询

实现C# Socket多线程服务器与客户端(限制2个连接)的完整方案

刚好做过类似的Socket多线程项目,给你整理一个能直接落地的实现方案,从服务器到客户端再到XML数据处理都拆解清楚,完全贴合你的需求:

一、先理清楚核心流程

怕走偏,先把需求再明确一遍:

  • 服务器最多同时接受2个客户端连接,多的会排队等待名额释放(你也可以按需改成直接拒绝)
  • 客户端全程自动交互:发学生ID → 收专业 → 立刻回发专业 → 收该专业的平均GPA
  • 服务器要处理两次查询:ID查专业,专业查平均GPA,数据都存在XML里

二、先搞定XML数据结构

先定义好XML的格式,后续查询才方便,创建一个StudentData.xml文件,内容如下:

<?xml version="1.0" encoding="utf-8" ?>
<Students>
  <Student>
    <ID>S001</ID>
    <Major>PHYSICS</Major>
    <GPA>3.75</GPA>
  </Student>
  <Student>
    <ID>S002</ID>
    <Major>PHYSICS</Major>
    <GPA>3.9</GPA>
  </Student>
  <Student>
    <ID>S003</ID>
    <Major>COMPUTER SCIENCE</Major>
    <GPA>3.8</GPA>
  </Student>
</Students>

三、服务器端实现

服务器要做这几件事:监听端口、限制最多2个连接、为每个客户端创建独立会话处理交互逻辑、查询XML数据。我用SemaphoreSlim控制并发连接数,用async/await处理会话(比手动创建线程更简洁高效):

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

namespace SocketServer
{
    class Program
    {
        // 限制最多2个客户端同时连接
        private static readonly SemaphoreSlim _connectionSemaphore = new SemaphoreSlim(2, 2);
        private static readonly XDocument _studentDoc = XDocument.Load("StudentData.xml");
        private const int Port = 8888;

        static async Task Main(string[] args)
        {
            var listener = new TcpListener(IPAddress.Any, Port);
            listener.Start();
            Console.WriteLine($"服务器已启动,监听端口 {Port},最多接受2个客户端连接");

            try
            {
                while (true)
                {
                    // 等待可用的连接名额
                    await _connectionSemaphore.WaitAsync();
                    Console.WriteLine("等待客户端连接...");

                    // 接受客户端连接
                    var client = await listener.AcceptTcpClientAsync();
                    Console.WriteLine($"新客户端连接:{client.Client.RemoteEndPoint}");

                    // 用Task处理客户端会话,不阻塞主线程
                    _ = HandleClientSession(client);
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"服务器异常:{ex.Message}");
            }
            finally
            {
                listener.Stop();
            }
        }

        private static async Task HandleClientSession(TcpClient client)
        {
            try
            {
                using (client)
                using (var stream = client.GetStream())
                {
                    byte[] buffer = new byte[1024];
                    int bytesRead;

                    // 第一步:接收学生ID,返回对应专业
                    Console.WriteLine("等待客户端发送学生ID...");
                    bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
                    string studentId = Encoding.UTF8.GetString(buffer, 0, bytesRead).Trim();
                    Console.WriteLine($"收到学生ID:{studentId}");

                    // 查询专业,处理不存在的ID情况
                    string major = GetMajorById(studentId) ?? "UNKNOWN";
                    Console.WriteLine($"查询到专业:{major},发送给客户端");
                    await stream.WriteAsync(Encoding.UTF8.GetBytes(major));

                    // 第二步:接收客户端回发的专业,返回平均GPA
                    Console.WriteLine("等待客户端回发专业...");
                    bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
                    string receivedMajor = Encoding.UTF8.GetString(buffer, 0, bytesRead).Trim();
                    Console.WriteLine($"收到专业:{receivedMajor}");

                    // 查询平均GPA,处理不存在的专业情况
                    double avgGpa = GetAverageGpaByMajor(receivedMajor);
                    string gpaResponse = avgGpa.ToString("F2");
                    Console.WriteLine($"查询到平均GPA:{gpaResponse},发送给客户端");
                    await stream.WriteAsync(Encoding.UTF8.GetBytes(gpaResponse));

                    Console.WriteLine($"客户端会话结束:{client.Client.RemoteEndPoint}");
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine($"客户端会话异常:{ex.Message}");
            }
            finally
            {
                // 释放连接名额,让下一个客户端可以连接
                _connectionSemaphore.Release();
                Console.WriteLine("连接名额已释放");
            }
        }

        // 根据学生ID查询专业
        private static string GetMajorById(string studentId)
        {
            var student = _studentDoc.Descendants("Student")
                .FirstOrDefault(s => s.Element("ID")?.Value == studentId);
            return student?.Element("Major")?.Value;
        }

        // 根据专业查询平均GPA
        private static double GetAverageGpaByMajor(string major)
        {
            var students = _studentDoc.Descendants("Student")
                .Where(s => s.Element("Major")?.Value.Equals(major, StringComparison.OrdinalIgnoreCase) == true)
                .Select(s => double.TryParse(s.Element("GPA")?.Value, out var gpa) ? gpa : 0)
                .ToList();

            return students.Count == 0 ? 0 : students.Average();
        }
    }
}

四、客户端实现

客户端全程自动交互,无需用户输入(这里把测试用的学生ID写死了,你也可以改成从命令行传入或者读取配置):

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

namespace SocketClient
{
    class Program
    {
        private const string ServerIp = "127.0.0.1";
        private const int Port = 8888;
        // 测试用的学生ID,可按需修改
        private const string TestStudentId = "S001";

        static async Task Main(string[] args)
        {
            using (var client = new TcpClient())
            {
                try
                {
                    // 连接服务器
                    await client.ConnectAsync(ServerIp, Port);
                    Console.WriteLine("已连接到服务器");

                    using (var stream = client.GetStream())
                    {
                        // 第一步:发送学生ID
                        Console.WriteLine($"发送学生ID:{TestStudentId}");
                        await stream.WriteAsync(Encoding.UTF8.GetBytes(TestStudentId));

                        // 接收专业
                        byte[] buffer = new byte[1024];
                        int bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
                        string major = Encoding.UTF8.GetString(buffer, 0, bytesRead);
                        Console.WriteLine($"收到专业:{major}");

                        // 第二步:回发专业
                        Console.WriteLine($"回发专业:{major}");
                        await stream.WriteAsync(Encoding.UTF8.GetBytes(major));

                        // 接收平均GPA
                        bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
                        string avgGpa = Encoding.UTF8.GetString(buffer, 0, bytesRead);
                        Console.WriteLine($"收到该专业的平均GPA:{avgGpa}");
                    }

                    Console.WriteLine("客户端会话结束");
                }
                catch (Exception ex)
                {
                    Console.WriteLine($"客户端异常:{ex.Message}");
                }
            }
        }
    }
}

五、关键要点说明

  • 连接限制:用SemaphoreSlim(2,2)严格控制同时在线的客户端数量,第三个客户端会自动排队,直到有连接释放;如果要直接拒绝第三个连接,可以在WaitAsync前判断当前计数,或者设置超时时间。
  • 异步处理:全程用async/await和Task处理会话,避免主线程阻塞,性能比手动创建线程更优。
  • XML查询:用LINQ to XML实现快速查询,同时处理了空值、格式错误等异常情况,避免程序崩溃。
  • 资源释放:用using语句自动释放TcpClient和NetworkStream资源,确保连接关闭后资源被及时回收。

测试步骤

  1. 把StudentData.xml放到服务器项目的输出目录下(或者修改代码中的文件路径)。
  2. 启动服务器,再启动最多2个客户端,观察控制台输出是否符合预期流程。
  3. 如果启动第三个客户端,它会等待直到前两个客户端中的一个断开,才会建立连接。

内容的提问来源于stack exchange,提问作者John Wick

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 07:06:56