我花了3天才搞懂:套接字、协议栈、描述符到底什么关系?

0 阅读17分钟

我写了500行代码,终于搞懂了Socket到底是怎么回事!

看完《网络是怎样连接的》,还是一头雾水?别怕,我写了可运行的Python代码,一步步带你拆解!


为什么还是看不懂?

很多文章用各种类比解释Socket:

  • 有人说像打电话

  • 有人说像寄快递

  • 有人说像管道

但看完之后,你还是不知道:

  • 描述符到底是啥?

  • bind和listen干什么用?

  • accept返回的新套接字和原来的有什么区别?

  • 客户端怎么知道服务器关闭了连接?

这次不一样,我们用真实可运行的代码,一步步拆解!


先运行看效果

把下面的代码保存为 socket_demo.py,运行起来:

python socket_demo.py

你会看到完整的通信过程,从创建套接字到关闭连接,每一步都有输出。


完整代码(可运行)

import socket

import threading

import time

  


def start_server():

    """启动一个简单的TCP服务器"""

    print("========== 服务器端 ==========")

   

    # 1. 创建服务器套接字

    print("1. 创建服务器套接字...")

    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    print(f"   服务器套接字描述符: {server_socket.fileno()}")

   

    # 设置SO_REUSEADDR选项,避免"Address already in use"错误

    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

   

    # 2. 绑定地址和端口

    print("2. 绑定地址和端口...")

    server_address = ('127.0.0.1', 9999)

    server_socket.bind(server_address)

    print(f"   绑定到: {server_address[0]}:{server_address[1]}")

   

    # 3. 开始监听连接

    print("3. 开始监听连接...")

    server_socket.listen(1)  # 最多允许1个等待连接

    print("   服务器正在监听,等待客户端连接...")

   

    def handle_client(client_socket, client_address):

        """处理客户端连接"""

        print(f"\n[服务器] 接收到来自 {client_address} 的连接")

        print(f"[服务器] 客户端套接字描述符: {client_socket.fileno()}")

       

        try:

            # 4. 接收客户端消息

            print("[服务器] 等待接收客户端消息...")

            data = client_socket.recv(1024)

            if data:

                message = data.decode('utf-8')

                print(f"[服务器] 收到客户端消息: {message}")

               

                # 5. 发送响应给客户端

                response = f"服务器已收到你的消息: '{message}'"

                print(f"[服务器] 发送响应: {response}")

                client_socket.sendall(response.encode('utf-8'))

       

        except Exception as e:

            print(f"[服务器] 处理客户端时出错: {e}")

       

        finally:

            # 6. 关闭客户端套接字

            print("[服务器] 关闭客户端连接")

            client_socket.close()

   

    return server_socket, handle_client

  


def start_client():

    """启动客户端连接服务器"""

    print("\n========== 客户端 ==========")

   

    # 1. 创建客户端套接字

    print("1. 创建客户端套接字...")

    client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    print(f"   客户端套接字描述符: {client_socket.fileno()}")

   

    # 2. 设置服务器地址

    server_address = ('127.0.0.1', 9999)

    print(f"2. 设置服务器地址: {server_address[0]}:{server_address[1]}")

   

    try:

        # 3. 连接到服务器

        print("3. 连接到服务器...")

        client_socket.connect(server_address)

        print("   连接成功!")

       

        # 获取连接后的本地和远程地址

        local_addr = client_socket.getsockname()

        remote_addr = client_socket.getpeername()

        print(f"   本地地址: {local_addr[0]}:{local_addr[1]}")

        print(f"   远程地址: {remote_addr[0]}:{remote_addr[1]}")

       

        # 4. 发送消息到服务器

        message = "Hello Server! 这是来自客户端的消息。"

        print(f"4. 发送消息到服务器: {message}")

        client_socket.sendall(message.encode('utf-8'))

       

        # 5. 接收服务器响应

        print("5. 等待服务器响应...")

        data = client_socket.recv(1024)

        response = data.decode('utf-8')

        print(f"   收到服务器响应: {response}")

       

        # 6. 关闭连接

        print("6. 关闭连接...")

       

    except ConnectionRefusedError:

        print("连接被拒绝,请确保服务器已启动")

    except Exception as e:

        print(f"客户端出错: {e}")

    finally:

        client_socket.close()

        print("   连接已关闭")

  


def demo_socket_connection():

    """演示完整的Socket连接过程"""

    print("Socket连接全过程演示")

    print("=" * 50)

   

    # 启动服务器

    server_socket, client_handler = start_server()

   

    # 在新线程中启动服务器接受连接

    def run_server():

        while True:

            client_socket, client_address = server_socket.accept()

            # 在新线程中处理客户端

            client_thread = threading.Thread(

                target=client_handler,

                args=(client_socket, client_address)

            )

            client_thread.daemon = True

            client_thread.start()

            # 只处理一个连接然后退出循环

            break

   

    server_thread = threading.Thread(target=run_server)

    server_thread.daemon = True

    server_thread.start()

   

    # 等待服务器启动

    time.sleep(0.5)

   

    # 启动客户端

    start_client()

   

    # 等待所有操作完成

    time.sleep(1)

   

    # 关闭服务器套接字

    server_socket.close()

    print("\n" + "=" * 50)

    print("演示完成!")

  


def test_socket_functions():

    """测试和演示socket相关函数"""

    print("\n========== Socket函数测试 ==========")

   

    # 创建一个UDP套接字对比

    print("\n1. 创建TCP和UDP套接字对比:")

    tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

    print(f"   TCP套接字类型: {tcp_socket.type}")

    print(f"   UDP套接字类型: {udp_socket.type}")

    print(f"   TCP套接字描述符: {tcp_socket.fileno()}")

    print(f"   UDP套接字描述符: {udp_socket.fileno()}")

   

    # 获取套接字选项

    print("\n2. 获取套接字选项:")

    print(f"   TCP_NODELAY: {tcp_socket.getsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY)}")

    print(f"   SO_REUSEADDR: {tcp_socket.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR)}")

   

    # 获取地址族和协议

    print("\n3. 套接字基本信息:")

    print(f"   地址族: {tcp_socket.family}")

    print(f"   协议: {tcp_socket.proto}")

   

    tcp_socket.close()

    udp_socket.close()

  


if __name__ == "__main__":

    # 运行完整的Socket连接演示

    demo_socket_connection()

   

    # 运行Socket函数测试

    test_socket_functions()

   

    print("\n" + "=" * 50)

    print("关键概念总结:")

    print("1. 描述符: 操作系统返回的整数标识,用于标识套接字")

    print("2. bind(): 将套接字绑定到特定IP和端口")

    print("3. listen(): 开始监听传入连接")

    print("4. accept(): 接受客户端连接,返回新的套接字")

    print("5. connect(): 客户端连接到服务器")

    print("6. send()/recv(): 发送和接收数据")

    print("7. close(): 关闭套接字释放资源")

运行这个代码,你会看到:

Socket连接全过程演示

* 服务器端 *

  1. 创建服务器套接字...

   服务器套接字描述符: 3

  1. 绑定地址和端口...

   绑定到: 127.0.0.1:9999

  1. 开始监听连接...

   服务器正在监听,等待客户端连接...

* 客户端 *

  1. 创建客户端套接字...

   客户端套接字描述符: 4

  1. 设置服务器地址: 127.0.0.1:9999

  2. 连接到服务器...

   连接成功!

   本地地址: 127.0.0.1:12345

   远程地址: 127.0.0.1:9999

[服务器] 接收到来自 ('127.0.0.1', 12345) 的连接

[服务器] 客户端套接字描述符: 5

[服务器] 等待接收客户端消息...

  1. 发送消息到服务器: Hello Server! 这是来自客户端的消息。

[服务器] 收到客户端消息: Hello Server! 这是来自客户端的消息。

[服务器] 发送响应: 服务器已收到你的消息: 'Hello Server! 这是来自客户端的消息。'

  1. 等待服务器响应...

   收到服务器响应: 服务器已收到你的消息: 'Hello Server! 这是来自客户端的消息。'

  1. 关闭连接...

[服务器] 关闭客户端连接

   连接已关闭


现在一步步拆解!

🤔 第一个问题:客户端和服务器都需要套接字吗?

这是一个非常常见的问题!很多人认为:

  • "服务器才需要套接字,客户端直接连接就行"

  • 或者 "客户端不需要创建套接字,因为套接字是用来监听的"

答案是:客户端和服务器都需要套接字!

为什么都需要?

套接字的本质:套接字是通信的接口,就像电话的听筒。

打电话的场景:

┌─────────────────┐                ┌─────────────────┐

│   客户端(你)   │                │   服务器(朋友) │

│                 │                │                 │

│  📱 电话机      │◀────电话线─────▶│  📱 电话机      │

│  (套接字)       │                │  (套接字)       │

└─────────────────┘                └─────────────────┘

类比理解:

  • 客户端套接字 = 你的电话听筒

  • 服务器套接字 = 朋友的电话听筒

  • 双方都需要电话听筒才能通话!

代码验证

服务器创建套接字

server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

print(f"服务器套接字描述符: {server_socket.fileno()}")

输出: 服务器套接字描述符: 3

客户端也创建套接字

client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

print(f"客户端套接字描述符: {client_socket.fileno()}")

输出: 客户端套接字描述符: 4

看到没有?双方都调用了 *socket() *

两者的区别

虽然都需要套接字,但它们的用途不同

方面服务器套接字客户端套接字
创建目的被动等待连接主动发起连接
使用流程socket → bind → listen → acceptsocket → connect
数量可以服务多个客户端通常连接一个服务器
类比餐厅(接待很多客人)食客(去一个餐厅吃饭)

完整的双向通信模型

客户端                              服务器

   │                                    │

   ├─ 创建套接字                         │

   │  client_socket = socket()          │

   │  (握住电话听筒)                     │

   │                                    │

   │                                    ├─ 创建套接字

   │                                    │  server_socket = socket()

   │                                    │  (握住电话听筒)

   │                                    │

   ├─ 连接到服务器                       │

   │  client_socket.connect()           │

   │  (拨号)                            │

   │                                    │

   │                                    ├─ 接受连接

   │                                    │  accept()

   │                                    │  (接电话)

   │                                    │

   │  【双方都通过套接字通信】            │

   │                                    │

   ├─ send() ──────────────────────→  recv()

   │   (说话)                          │   (听)

   │                                    │

   ├─ recv() ←──────────────────────  send()

   │   (听)                            │   (说话)

   │                                    │

   └─ close()                        close()

    (挂电话)                           (挂电话)

记忆技巧

套接字就像电话听筒:

  • 双方都需要握着听筒才能通话
  • 服务器 = 电话亭(可以接多个电话)
  • 客户端 = 普通电话(打一个电话)

第一步:创建套接字和描述符

服务器端

server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

print(f"服务器套接字描述符: {server_socket.fileno()}")

输出: 服务器套接字描述符: 3

客户端

client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

print(f"客户端套接字描述符: {client_socket.fileno()}")

输出: 客户端套接字描述符: 4

🎯 关键概念:套接字(Socket)

是什么?

  • 套接字是通信的抽象接口

  • 就像电话的听筒,握住它才能开始通信

  • 客户端和服务器都需要创建套接字

参数说明:

  • AF_INET:IPv4地址族

  • SOCK_STREAM:TCP流式套接字(可靠连接)

关键理解:

客户端套接字 ≠ 服务器套接字

两者是独立的对象,分别运行在:

  • 客户端进程

  • 服务器进程

类比:

套接字 = 电话听筒

客户端套接字 = 你的听筒

服务器套接字 = 朋友的听筒

双方都需要听筒才能通话!

🎯 关键概念:描述符(Descriptor)

是什么?

  • 描述符是操作系统给你的整数门牌号

  • 就像快递公司给你的"运单号:SF123456789"

  • 操作系统用这个号来管理你的套接字

为什么是3、4、5?

0 → 标准输入(stdin)

1 → 标准输出(stdout)

2 → 标准错误(stderr)

3 → 第一个套接字

4 → 第二个套接字

...

类比:

你创建套接字 = 填寄货单

描述符 = 运单号 SF123456789

操作系统 = 快递公司内部系统


第二步:绑定地址和端口

只有服务器需要绑定

server_address = ('127.0.0.1', 9999)

server_socket.bind(server_address)

输出: 绑定到: 127.0.0.1:9999

🎯 关键概念:bind()

为什么服务器需要bind?

  • 服务器需要告诉操作系统:"我要监听这个IP和端口"

  • 客户端就知道往哪里发连接请求

类比:

bind() = 在门口挂个牌子

        "营业时间:9:00-18:00"

        "服务地址:127.0.0.1:9999"

IP地址和端口的作用:

  • 127.0.0.1:本地回环地址(只在本机通信)

  • 9999:端口号(可以理解为一栋楼的房间号)

为什么客户端不需要bind?

  • 客户端是主动连接的一方
  • 操作系统会自动分配一个随机端口
  • 就像:你是去餐厅吃饭,餐厅需要门牌号,你不需要

第三步:开始监听

server_socket.listen(1)

print("服务器正在监听,等待客户端连接...")

🎯 关键概念:listen()

做什么?

  • 将套接字从"普通模式"切换到"监听模式"

  • 开始等待客户端的连接请求

参数1是什么?

  • 最大等待队列长度

  • 如果有2个客户端同时连接,第2个会排队,第3个会被拒绝

类比:

listen() = 服务员站在门口

          "欢迎光临!请取号排队"

          (最多允许1个人等待)


第四步:客户端连接

客户端

client_socket.connect(server_address)

print("连接成功!")

获取连接后的地址

local_addr = client_socket.getsockname()  # 本地地址

remote_addr = client_socket.getpeername() # 远程地址

print(f"本地地址: {local_addr[0]}:{local_addr[1]}")

print(f"远程地址: {remote_addr[0]}:{remote_addr[1]}")

输出:

连接成功!

本地地址: 127.0.0.1:54321

远程地址: 127.0.0.1:9999

🎯 关键概念:connect()

发生了什么?(三次握手)

客户端                                  服务器

   │                                      │

   │ ① SYN: "我想建立连接" ──────────────→ │

   │                                      │

   │ ←──────────────────── ② SYN-ACK      │

   │   "好的,我也想建立连接"             │

   │                                      │

   │ ③ ACK: "确认收到" ─────────────────→ │

   │                                      │

   │           连接建立!                 │

类比:

你:服务员,我要点餐!            (SYN)

服务员:好的,请问点什么?        (SYN-ACK)

你:我要一份宫保鸡丁              (ACK)

→ 订单建立

getsockname() vs getpeername():

  • getsockname():自己的地址(我:127.0.0.1:54321)
  • getpeername():对方的地址(对方:127.0.0.1:9999)

第五步:服务器接受连接

服务器端

client_socket, client_address = server_socket.accept()

print(f"接收到来自 {client_address} 的连接")

print(f"客户端套接字描述符: {client_socket.fileno()}")

输出:

[服务器] 接收到来自 ('127.0.0.1', 54321) 的连接

[服务器] 客户端套接字描述符: 5

🎯 关键概念:accept()

返回了什么?

client_socket, client_address = server_socket.accept()

#                    ↓

#            新的套接字(描述符5)

#                    ↓

客户端的地址信息

为什么要返回新的套接字?

server_socket(描述符3)= 主管,负责门口迎接客人

client_socket(描述符5)= 服务员,负责服务具体客人

类比:

餐厅老板(server_socket):站在门口,等客人来

→ 客人来了

老板叫来服务员(client_socket)

服务员专门服务这个客人

老板继续在门口等下一个客人

关键理解:

  • server_socket(描述符3):只负责accept,收新的客户端
  • client_socket(描述符5):负责和具体客户端通信

第六步:发送和接收数据

客户端发送

message = "Hello Server!"

client_socket.sendall(message.encode('utf-8'))

服务器接收

data = client_socket.recv(1024)

message = data.decode('utf-8')

print(f"收到客户端消息: {message}")

🎯 **关键概念:send() 和 recv()

数据流向:

客户端send() → 网络 → 服务器recv()

                    ↓

              数据在这里

recv(1024)是什么意思?

  • 最多接收1024字节

  • 如果对方发了2000字节,需要调用2次recv

  • 如果对方发了500字节,只会收到500字节

类比:

send() = 把信投进邮筒

recv() = 去信箱取信(一次最多拿1024封信)


第七步:关闭连接

客户端


client_socket.close()

服务器

client_socket.close()

server_socket.close()

🎯 关键概念:close()

发生了什么?(四次挥手)

客户端(client_socket)                    服务器(client_socket)

   │                                      │

   │ ① FIN: "我要关闭连接" ──────────────→ │

   │   (挂电话)                           │

   │                                      │

   │ ←──────────────────── ② ACK          │

   │   "收到,你说完了吗"                 │

   │                                      │

   │ ←──────────────────── ③ FIN          │

   │   "我也说完了"                       │

   │   (挂电话)                           │

   │                                      │

   │ ④ ACK: "好的,再见" ────────────────→ │

   │                                      │

   │           连接关闭!                 │

类比:

你(客户端):我说完了,挂电话              (FIN)

朋友(服务器):收到,你说完了吗            (ACK)

朋友(服务器):我也说完了,挂电话          (FIN)

你(客户端):好的,再见                    (ACK)

→ 通话结束

注意:双方都有自己的套接字,都需要关闭!


关键问题:客户端怎么知道服务器关闭了连接?

问题场景

客户端代码

while True:

    data = client_socket.recv(1024)

    if data:

        print(f"收到: {data.decode('utf-8')}")

    else:

        print("服务器关闭了连接")

        break

关键点: *recv() ***返回空字符串时,说明连接已关闭!

演示代码


def demo_close_detection():

    """演示客户端如何检测服务器关闭连接"""

    print("\n* 检测连接关闭 *")

    

    # 服务器

    server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    server_socket.bind(('127.0.0.1', 9998))

    server_socket.listen(1)

    

    def server_task():

        client_sock, _ = server_socket.accept()

        # 发送一条消息

        client_sock.sendall("第一条消息".encode('utf-8'))

        time.sleep(1)

        # 发送第二条消息

        client_sock.sendall("第二条消息".encode('utf-8'))

        time.sleep(1)

        # 关闭连接

        print("[服务器] 关闭连接")

        client_sock.close()

    

    server_thread = threading.Thread(target=server_task)

    server_thread.daemon = True

    server_thread.start()

    time.sleep(0.5)

    

    # 客户端

    client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    client_socket.connect(('127.0.0.1', 9998))

    

    print("[客户端] 开始接收消息...")

    count = 0

    while True:

        try:

            data = client_socket.recv(1024)

            if not data:

                # recv返回空字符串,说明连接已关闭

                print(f"[客户端] 收到空数据,连接已关闭!")

                break

            count += 1

            print(f"[客户端] 收到第{count}条消息: {data.decode('utf-8')}")

        except Exception as e:

            print(f"[客户端] 异常: {e}")

            break

    

    client_socket.close()

    server_socket.close()

    print("[客户端] 客户端关闭")

  


if __name__ * "__main__":*

*    demo_close_detection()*

运行结果:

 检测连接关闭 ==

[客户端] 开始接收消息...

[客户端] 收到第1条消息: 第一条消息

[客户端] 收到第2条消息: 第二条消息

[服务器] 关闭连接

[客户端] 收到空数据,连接已关闭!

[客户端] 客户端关闭

总结:检测连接关闭的3种方法

重要:recv()返回空字符串说明对方(无论是客户端还是服务器)关闭了它的套接字!

方法1:recv()返回空字符串(最常用)

data = client_socket.recv(1024)

if not data:

    print("对方关闭了连接")

    # 对方调用了 close()

理解:

  • 服务器调用 client_socket.close() → 客户端recv()返回空

  • 客户端调用 client_socket.close() → 服务器recv()返回空

  • 双方都需要关闭自己的套接字 

  • break

方法2:捕获异常


try:

    data = client_socket.recv(1024)

except ConnectionResetError:

    print("连接被重置")

方法3:使用select/poll(高级)

import select

ready = select.select([client_socket], [], [], timeout)

if ready[0]:

    data = client_socket.recv(1024)

完整流程图总结

【服务器】                          【客户端】

   │                                   │

   ├─1. socket() → 描述符3              │

   │                                   ├─1. socket() → 描述符4

   ├─2. bind(127.0.0.1:9999)           │

   │                                   ├─2. connect()

   ├─3. listen(1)                      │   (三次握手)

   │                                   │

   ├─4. accept() → 描述符5              │

   │   (主管叫来服务员)                 │

   │                                   ├─3. send()

   ├─5. recv() ───────────────────────→ │   "Hello"

   │                                   │

   │                                   ├─4. recv()

   ├─6. send() ←─────────────────────── │   (收到响应)

   │   "Thanks"                        │

   │                                   │

   └─7. close()                        └─5. close()

       (四次挥手)


常见误区解答

❌ **误区1:只有服务器需要套接字

错误理解: 只有服务器需要套接字,客户端不需要

正确理解:

  • 客户端和服务器**都需要套接字

  • 套接字 = 通信接口(电话听筒)

  • 双方都需要握住听筒才能通话

类比:

打电话:

你需要电话听筒 ←→ 朋友也需要电话听筒

(客户端套接字)   (服务器套接字)

❌ **误区2:描述符就是套接字

错误理解: 描述符 = 套接字

正确理解:

  • 套接字 = 通信对象(就像一个邮箱)

  • 描述符 = 访问套接字的索引(就像邮箱号码)

操作系统内部:

描述符3 → 客户端套接字对象(包含IP、端口、缓冲区等)

描述符4 → 服务器监听套接字

描述符5 → 服务器与客户端通信的套接字

...

❌ **误区3:accept()返回的套接字和原来的套接字一样

错误理解: accept返回的client_socket和server_socket是同一个

正确理解:

server_socket (描述符4) = 主管(只负责accept新连接)

client_socket (描述符5) = 服务员(负责具体通信)

每个accept都返回新的套接字!

❌ **误区3:connect()后就建立了物理连接

错误理解: connect()后,两个电脑之间拉了一根物理线

正确理解:

  • TCP连接是逻辑连接,不是物理连接

  • 通过IP地址、端口号、序列号等来标识连接

  • 就像:电话连接是逻辑的,不是真的拉了一根电话线

❌ **误区4:recv(1024)保证收到1024字节

错误理解: recv(1024)一定会收到1024字节

正确理解:

  • recv(1024) = 最多接收1024字节

  • 可能收到0字节(对方关闭连接)

  • 可能收到1-1024字节(取决于网络情况)

正确的做法

data = b''

while len(data) < 1024:

    chunk = client_socket.recv(1024 - len(data))

    if not chunk:

        break

    data += chunk

记忆技巧:一张表格搞定

概念类比一句话记住
socket电话听筒通信的接口,双方都需要
描述符运单号操作系统给的索引
bind()挂营业牌子告诉别人我在哪
listen()站门口等客人开始监听连接
connect()拨电话主动发起连接
accept()接电话返回新的套接字
send()说话发送数据
recv()接收数据
close()挂电话关闭连接
recv返回空对方挂了对方关闭了套接字

关键公式

客户端和服务器都需要套接字 = 双方都需要电话听筒

套接字 + 描述符 = 通信的访问方式

bind + listen = 服务器准备就绪

connect = 客户端主动连接

accept = 服务器接受连接,返回新套接字

send + recv = 双向通信

close = 四次挥手,关闭连接

recv返回空字符串 = 对方关闭连接


总结

看完这篇文章和代码,你应该理解了:

客户端和服务器都需要套接字:双方都需要电话听筒才能通信 

套接字:通信的抽象接口 

描述符:操作系统给的整数索引 

bind() :服务器绑定IP和端口 

listen() :开始监听连接 

connect() :客户端发起连接(三次握手) 

accept() :服务器接受连接,返回新套接字 

send() / recv() :发送和接收数据 

close() :关闭连接(四次挥手) 

检测连接关闭:recv()返回空字符串(对方关闭了它的套接字)

记住最关键的一点:

客户端和服务器都需要套接字,就像双方都需要电话听筒才能通话! 

描述符是门牌号,套接字是房间。描述符帮你找到套接字,套接字负责实际的通信。


如果这篇文章对你有帮助,请点个赞👍 ,让更多人看到!