用「快递收发系统」讲透Socket技术

34 阅读4分钟

想象你要开一个快递站

传统方式(像邮局寄信)

csharp

// 每次都要:填单子→去邮局→排队→寄信→回家
// 对方也要:去邮局→排队→取信→回家
// 效率低,不能实时沟通

Socket方式(像开一个快递收发站)

csharp

// 在你的地址(IP)上开个门店(端口)
// 客户随时可以来寄件,你也可以随时派件
// 实时、双向、持续的通信通道

Socket的核心概念(快递站运营)

1. 服务器Socket(开快递总站)

csharp

// 第一步:选址开店(绑定IP和端口)
IPAddress ip = IPAddress.Parse("192.168.1.100");  // 街道地址
int port = 8888;                                  // 门牌号

Socket 快递总站 = new Socket(
    AddressFamily.InterNetwork,     // 使用IPv4地址(小区规划)
    SocketType.Stream,              // 像快递流水线,有序传输
    ProtocolType.Tcp);              // 可靠协议(保证送到)

快递总站.Bind(new IPEndPoint(ip, port));  // 挂牌营业
快递总站.Listen(10);                       // 允许10个客户同时排队

Console.WriteLine($"快递站开张!地址:{ip}:{port}");

2. 等待客户上门(监听连接)

csharp

// 像快递员在店里等客户
while (true)
{
    Console.WriteLine("等待客户上门...");
    
    // 有客户来了,安排专属快递员接待
    Socket 专属快递员 = 快递总站.Accept();  // 创建专用通道
    
    // 启动新线程处理这个客户(不影响接待其他客户)
    ThreadPool.QueueUserWorkItem(处理客户寄件, 专属快递员);
}

完整的快递站例子

csharp

public class 快递收发站
{
    // 快递站开张
    public void 开始营业()
    {
        // 1. 选址开店
        IPEndPoint 店铺地址 = new IPEndPoint(IPAddress.Any, 8888);
        Socket 快递站 = new Socket(AddressFamily.InterNetwork, 
                                    SocketType.Stream, 
                                    ProtocolType.Tcp);
        
        快递站.Bind(店铺地址);  // 挂牌
        快递站.Listen(10);      // 10个等待位
        
        Console.WriteLine("快递站营业中...");
        Console.WriteLine($"地址:{快递站.LocalEndPoint}");
        
        // 2. 无限循环接待客户
        while (true)
        {
            Console.WriteLine("\n等待下一个客户...");
            
            // 客户上门,分配专属快递员
            Socket 专属快递员 = 快递站.Accept();
            Console.WriteLine($"新客户连接:{专属快递员.RemoteEndPoint}");
            
            // 新开窗口服务这个客户(线程池)
            Task.Run(() => 处理客户业务(专属快递员));
        }
    }
    
    // 处理单个客户的业务
    private void 处理客户业务(Socket 快递员)
    {
        try
        {
            // 循环处理这个客户的所有需求
            while (true)
            {
                // 3. 接收客户发来的包裹(数据)
                byte[] 缓冲区 = new byte[1024];
                int 收到字节数 = 快递员.Receive(缓冲区);
                
                if (收到字节数 == 0)  // 客户走了
                {
                    Console.WriteLine("客户断开连接");
                    break;
                }
                
                // 解析包裹内容
                string 客户需求 = Encoding.UTF8.GetString(缓冲区, 0, 收到字节数);
                Console.WriteLine($"收到包裹:{客户需求}");
                
                // 4. 处理业务并回复
                string 回复内容 = 处理需求(客户需求);
                byte[] 回复数据 = Encoding.UTF8.GetBytes(回复内容);
                快递员.Send(回复数据);
                Console.WriteLine($"已回复:{回复内容}");
            }
        }
        catch (Exception ex)
        {
            Console.WriteLine($"处理出错:{ex.Message}");
        }
        finally
        {
            // 5. 客户离开,关闭连接
            快递员.Close();
            Console.WriteLine("快递员下班");
        }
    }
    
    private string 处理需求(string 需求)
    {
        // 根据客户需求提供不同服务
        if (需求.Contains("查询物流"))
            return "您的包裹正在派送中,预计明天到达";
        else if (需求.Contains("寄快递"))
            return "已收到您的包裹,运单号:SF123456789";
        else if (需求.Contains("投诉"))
            return "抱歉给您带来不便,我们会尽快处理";
        else
            return "收到您的消息:" + 需求;
    }
}

客户端(寄快递的人)

csharp

public class 寄件人
{
    public void 寄快递(string 服务器IP = "127.0.0.1", int 端口 = 8888)
    {
        try
        {
            // 1. 找到快递站地址
            IPAddress 地址 = IPAddress.Parse(服务器IP);
            IPEndPoint 快递站地址 = new IPEndPoint(地址, 端口);
            
            // 2. 创建自己的寄件通道
            Socket 寄件人 = new Socket(AddressFamily.InterNetwork,
                                         SocketType.Stream,
                                         ProtocolType.Tcp);
            
            // 3. 连接到快递站
            Console.WriteLine($"前往快递站:{快递站地址}");
            寄件人.Connect(快递站地址);
            Console.WriteLine("连接成功!可以寄件了");
            
            // 4. 发送包裹(数据)
            while (true)
            {
                Console.Write("\n请输入要寄送的内容(输入exit退出):");
                string 包裹内容 = Console.ReadLine();
                
                if (包裹内容.ToLower() == "exit")
                    break;
                
                // 打包数据
                byte[] 数据包 = Encoding.UTF8.GetBytes(包裹内容);
                
                // 寄出包裹
                寄件人.Send(数据包);
                Console.WriteLine($"已寄出:{包裹内容}");
                
                // 5. 等待回执
                byte[] 回执缓冲区 = new byte[1024];
                int 收到字节 = 寄件人.Receive(回执缓冲区);
                string 回执 = Encoding.UTF8.GetString(回执缓冲区, 0, 收到字节);
                Console.WriteLine($"收到回执:{回执}");
            }
            
            // 6. 离开快递站
            寄件人.Shutdown(SocketShutdown.Both);
            寄件人.Close();
            Console.WriteLine("已断开连接");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"出错了:{ex.Message}");
        }
    }
}

实际场景:聊天室(多个客户同时沟通)

csharp

public class 聊天室服务器
{
    // 管理所有在线的客户(快递员列表)
    private static List<Socket> 所有客户 = new List<Socket>();
    
    public void 开启聊天室()
    {
        Socket 聊天室 = new Socket(AddressFamily.InterNetwork, 
                                    SocketType.Stream, 
                                    ProtocolType.Tcp);
        聊天室.Bind(new IPEndPoint(IPAddress.Any, 9999));
        聊天室.Listen(100);
        
        Console.WriteLine("聊天室已开启,等待用户加入...");
        
        while (true)
        {
            Socket 新客户 = 聊天室.Accept();
            所有客户.Add(新客户);  // 记录新客户
            
            // 新客户单独开一个窗口
            Task.Run(() => 处理聊天消息(新客户));
        }
    }
    
    private void 处理聊天消息(Socket 客户)
    {
        try
        {
            byte[] 缓冲区 = new byte[1024];
            
            while (true)
            {
                int 收到字节 = 客户.Receive(缓冲区);
                if (收到字节 == 0) break;
                
                string 消息 = Encoding.UTF8.GetString(缓冲区, 0, 收到字节);
                Console.WriteLine($"收到消息:{消息}");
                
                // 广播给所有其他客户
                广播消息(消息, 客户);
            }
        }
        finally
        {
            所有客户.Remove(客户);
            客户.Close();
        }
    }
    
    private void 广播消息(string 消息, Socket 发送者)
    {
        byte[] 数据 = Encoding.UTF8.GetBytes(消息);
        
        foreach (var 其他客户 in 所有客户)
        {
            if (其他客户 != 发送者 && 其他客户.Connected)
            {
                其他客户.Send(数据);
            }
        }
    }
}

Socket的不同模式

1. 同步模式(像柜台服务)

csharp

// 一个快递员一次只服务一个客户,其他人要排队
public void 同步服务()
{
    Socket 客户 = 快递站.Accept();  // 阻塞,直到有客户
    byte[] 数据 = new byte[1024];
    int 长度 = 客户.Receive(数据);  // 阻塞,直到收到数据
    // 处理...
    客户.Send(回复数据);          // 发送回复
}

2. 异步模式(像智能仓储系统)

csharp

// 自动分拣系统,来了包裹自动处理
public async Task 异步服务()
{
    Socket 客户 = await 快递站.AcceptAsync();  // 不阻塞,有客户再来
    
    byte[] 缓冲区 = new byte[1024];
    // 开始监听,有数据自动回调
    var 接收结果 = await 客户.ReceiveAsync(new ArraySegment<byte>(缓冲区), 
                                          SocketFlags.None);
    
    // 异步发送回复
    await 客户.SendAsync(new ArraySegment<byte>(Encoding.UTF8.GetBytes("收到")), 
                        SocketFlags.None);
}

3. UDP模式(像发传单)

csharp

// 不需要建立固定连接,像在街上发传单
public void UDP快递站()
{
    // 创建UDP Socket(不像快递员,像发传单的人)
    Socket 传单员 = new Socket(AddressFamily.InterNetwork,
                               SocketType.Dgram,        // 数据报模式
                               ProtocolType.Udp);       // 不可靠但快速
    
    // 绑定地址,但不监听连接
    传单员.Bind(new IPEndPoint(IPAddress.Any, 7777));
    
    // 接收任何人的传单
    EndPoint 客户地址 = new IPEndPoint(IPAddress.Any, 0);
    byte[] 缓冲区 = new byte[1024];
    
    // 收到传单(不保证顺序,可能丢失)
    int 字节数 = 传单员.ReceiveFrom(缓冲区, ref 客户地址);
    string 传单内容 = Encoding.UTF8.GetString(缓冲区, 0, 字节数);
    
    // 也可以随时向任何人发传单
    string 我的传单 = "大促销!";
    byte[] 传单数据 = Encoding.UTF8.GetBytes(我的传单);
    传单员.SendTo(传单数据, 客户地址);
}

实际应用:远程控制智能家居

csharp

public class 智能家居服务器
{
    private Dictionary<string, string> 设备状态 = new Dictionary<string, string>
    {
        {"客厅灯", "关"},
        {"空调", "关"},
        {"窗帘", "关"}
    };
    
    public void 启动控制中心()
    {
        Socket 控制中心 = new Socket(AddressFamily.InterNetwork, 
                                     SocketType.Stream, 
                                     ProtocolType.Tcp);
        控制中心.Bind(new IPEndPoint(IPAddress.Any, 6666));
        控制中心.Listen(5);
        
        Console.WriteLine("智能家居控制中心已启动");
        
        while (true)
        {
            Socket 手机 = 控制中心.Accept();
            Task.Run(() => 处理手机指令(手机));
        }
    }
    
    private void 处理手机指令(Socket 手机)
    {
        try
        {
            NetworkStream 流 = new NetworkStream(手机);
            StreamReader 读 = new StreamReader(流, Encoding.UTF8);
            StreamWriter 写 = new StreamWriter(流, Encoding.UTF8) { AutoFlush = true };
            
            while (true)
            {
                // 读取手机指令
                string 指令 = 读.ReadLine();
                if (指令 == null) break;
                
                Console.WriteLine($"收到指令:{指令}");
                
                // 解析指令
                string[] 部分 = 指令.Split(' ');
                string 响应;
                
                if (部分[0] == "状态")
                {
                    // 查询状态
                    响应 = $"客厅灯:{设备状态["客厅灯"]}, 空调:{设备状态["空调"]}";
                }
                else if (部分[0] == "打开" && 部分.Length == 2)
                {
                    // 控制设备
                    if (设备状态.ContainsKey(部分[1]))
                    {
                        设备状态[部分[1]] = "开";
                        响应 = $"已打开{部分[1]}";
                    }
                    else
                    {
                        响应 = $"找不到设备:{部分[1]}";
                    }
                }
                else if (部分[0] == "关闭" && 部分.Length == 2)
                {
                    // 关闭设备
                    if (设备状态.ContainsKey(部分[1]))
                    {
                        设备状态[部分[1]] = "关";
                        响应 = $"已关闭{部分[1]}";
                    }
                    else
                    {
                        响应 = $"找不到设备:{部分[1]}";
                    }
                }
                else
                {
                    响应 = $"未知指令:{指令}";
                }
                
                // 发送响应
                写.WriteLine(响应);
            }
        }
        finally
        {
            手机.Close();
        }
    }
}

Socket vs HttpClient(快递站 vs 打电话)

特性Socket(快递站)HttpClient(打电话)
连接方式长期保持连接用完即断
实时性实时双向通信请求-响应模式
复杂度需要自己管理连接框架管理连接
适用场景聊天、游戏、实时监控网页API、文件下载

总结比喻

Socket就像开一个快递收发站:

  1. 先开店:绑定IP和端口(选地址挂牌)
  2. 等客户:监听连接(快递员等客户上门)
  3. 收包裹:Receive数据(接收客户寄件)
  4. 处理业务:处理数据(登记、分拣包裹)
  5. 发回执:Send数据(给客户回执)
  6. 保持连接:持续服务(客户可以连续寄件)

关键理解:

  • TCP Socket:像可靠的快递服务,保证包裹顺序、不丢失
  • UDP Socket:像发传单,快速但不保证每个人都能收到
  • 服务器Socket:像快递总站,接收所有客户的连接
  • 客户端Socket:像寄件人,主动连接服务器

核心价值:
提供实时、双向、持续的通信能力,就像在两家之间建立了专属的快递通道,随时可以收发包裹,而不需要每次都重新建立联系!