socket由浅入深


1) Socket 到底是什么?

**Socket(套接字)**可以理解成:

两台设备在网络里"开出来的一根通信管道的两端"。

  • 一端在 A 设备上
  • 另一端在 B 设备上
  • 数据通过这根"管道"来回流动

它不是某个具体的硬件,而是操作系统提供的网络接口

有点像你在两台电脑之间拉了一根虚拟网线,然后通过它读写字节。


2) 为什么需要 socket?

网络的底层其实只做一件事:传字节(bytes)

socket 就是让你可以:

  • 建立连接
  • 发送字节
  • 接收字节
  • 关闭连接

你在上层看到的 HTTP、WebSocket、MQTT、游戏联机等等,

本质上都在 socket 之上封装出来的。


3) Socket 通信的两种大类

A. TCP Socket(可靠连接)

特点:

  • 建立连接(像打电话)
  • 数据可靠、有序、不易丢
  • 速度稍慢,但稳定

适合:

  • 控制指令
  • 文件传输
  • 网页/接口服务(HTTP)

Python 里对应:SOCK_STREAM


B. UDP Socket(无连接)

特点:

  • 不建立连接,直接扔数据(像发广播/短信)
  • 不保证到达、顺序可能乱
  • 速度快,延迟低

适合:

  • 视频/语音实时流
  • 游戏状态同步
  • 广播发现设备

Python 里对应:SOCK_DGRAM


4) Socket 的"角色模型":服务器 & 客户端

任何 socket 通信都至少有两端,而且角色固定:

服务器(Server)

负责:

  1. 绑定 IP+端口(告诉系统自己在哪接客)
  2. 监听端口
  3. 等待别人连接
  4. 收/发数据

客户端(Client)

负责:

  1. 主动发起连接到服务器
  2. 收/发数据
  3. 断开

记忆法:
服务器等人来,客户端去找人。


5) 一次 TCP 通信的完整生命周期

用"打电话"类比:

  1. server: socket()
    买手机
  2. server: bind()
    申请号码(IP+端口)
  3. server: listen()
    开机等电话
  4. client: socket()
    买手机
  5. client: connect()
    拨号
  6. server: accept()
    接电话(建立连接)
  7. 双方 send()/recv()
    说话
  8. close()
    挂电话

6) 最常用的 socket API(Python/MicroPython 通用)

创建 socket

python 复制代码
s = socket.socket(AF_INET, SOCK_STREAM)  # TCP
s = socket.socket(AF_INET, SOCK_DGRAM)   # UDP
  • AF_INET:IPv4
  • SOCK_STREAM:TCP
  • SOCK_DGRAM:UDP

服务器端核心方法

python 复制代码
s.bind((ip, port))   # 绑定地址
s.listen(n)          # 监听
conn, addr = s.accept()  # 等连接
data = conn.recv(1024)   # 收
conn.send(b"hi")         # 发
conn.close()             # 断开当前连接

客户端端核心方法

python 复制代码
s.connect((ip, port))  # 连接服务器
s.send(b"hello")       # 发
data = s.recv(1024)    # 收
s.close()              # 关闭

7) 地址的概念:IP + Port

socket 的地址一定是:

IP 地址 + 端口号

比如:

  • IP:192.168.2.132
  • Port:8080

IP 表示设备是谁
Port 表示设备上的哪个程序在听

一台设备可以同时开很多服务:

  • 80 端口跑网页
  • 1883 跑 MQTT
  • 8080 跑你自己的控制服务

8) Blocking(阻塞) vs Non-blocking(非阻塞)

socket 默认是 阻塞模式

  • accept() 没人连 → 就一直等
  • recv() 没数据 → 就一直等

这很适合简单项目。

超时模式(更灵活)

python 复制代码
s.settimeout(5)

意思是:

  • 等最多 5 秒
  • 超过就抛异常

非阻塞模式(高级)

python 复制代码
s.setblocking(False)

意义:

  • 立刻返回,不等
  • 没数据就报错,需要你轮询/事件循环

9) TCP 是"字节流",不是"消息"

这是 socket 里最重要的一个坑:

TCP 没有消息边界

你发送两次:

  • send("A")
  • send("B")

对方 recv() 可能收到:

  • "AB"(粘包)
  • "A" 然后 "B"
  • 或者 "A" "B" 被拆成碎片

所以真实项目都要定义"协议",比如:

  • \n 作为消息结束符
  • 或固定长度头部告诉数据多长

10) Socket 能做什么(现实里的对应)

  • HTTP 服务(浏览器访问)
  • WebSocket(实时聊天/推送)
  • 游戏联机
  • IoT 设备与服务器通信
  • 远程控制设备
  • 传感器数据上传
  • 局域网设备发现

它是所有网络应用的"地基"。



TCP 最小模板

TCP Server(服务端)

作用:监听端口,收到消息就回一句 OK.

python 复制代码
# tcp_server.py
import socket

HOST = "0.0.0.0"   # 监听本机所有网卡
PORT = 8080

def main():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.bind((HOST, PORT))
    s.listen(1)
    print(f"TCP server listening on {HOST}:{PORT}")

    while True:
        conn, addr = s.accept()
        print("client connected:", addr)

        data = conn.recv(1024)
        if data:
            print("recv:", data.decode())
            conn.send(b"OK\n")

        conn.close()
        print("client disconnected")

if __name__ == "__main__":
    main()

TCP Client(客户端)

作用:连服务端,发一句话,收回执。

python 复制代码
# tcp_client.py
import socket

SERVER_IP = "127.0.0.1"  # 改成服务器IP
PORT = 8080

def main():
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((SERVER_IP, PORT))

    s.send(b"hello tcp")
    resp = s.recv(1024)
    print("resp:", resp.decode())

    s.close()

if __name__ == "__main__":
    main()
运行方式
  1. 终端1:

    bash 复制代码
    python tcp_server.py
  2. 终端2:

    bash 复制代码
    python tcp_client.py

UDP 最小模板

UDP Server(服务端)

作用:绑定端口,收一个包就回 OK.

python 复制代码
# udp_server.py
import socket

HOST = "0.0.0.0"
PORT = 8080

def main():
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.bind((HOST, PORT))
    print(f"UDP server listening on {HOST}:{PORT}")

    while True:
        data, addr = s.recvfrom(1024)
        print("recv from", addr, ":", data.decode())

        s.sendto(b"OK\n", addr)

if __name__ == "__main__":
    main()

UDP Client(客户端)

作用:给服务端发一个包,收回包。

python 复制代码
# udp_client.py
import socket

SERVER_IP = "127.0.0.1"  # 改成服务器IP
PORT = 8080

def main():
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

    s.sendto(b"hello udp", (SERVER_IP, PORT))
    resp, addr = s.recvfrom(1024)
    print("resp:", resp.decode(), "from", addr)

    s.close()

if __name__ == "__main__":
    main()
运行方式
  1. 终端1:

    bash 复制代码
    python udp_server.py
  2. 终端2:

    bash 复制代码
    python udp_client.py


1. socket 是什么?一句话版

socket 就是两台设备之间的网络"插座/管道"

一头插在 ESP32 上,一头插在电脑上,中间靠路由器/网络把数据送过去。

你现在的场景是:

  • ESP32 开个"店"(服务器)
  • 电脑去"敲门下单"(客户端)
  • 下单内容就是你发的字符串:1 / 0 / ON / OFF

2. TCP vs UDP(你用的是 TCP)

TCP(SOCK_STREAM)

  • 面向连接:像打电话,先拨号接通再说话
  • 可靠:按顺序、不丢包(一般)
  • 适合控制命令、文件传输

你的代码就是 TCP。

UDP(SOCK_DGRAM)

  • 无连接:像发短信/广播,直接发
  • 不保证到达/顺序
  • 适合实时、容忍丢包的场景(比如视频/传感器高频数据)

3. socket 的"角色"必须分清

服务器(Server)

负责:

  1. 绑定地址(bind)
  2. 监听端口(listen)
  3. 等客户端连(accept)

ESP32 当服务器。

客户端(Client)

负责:

  1. 主动连接服务器(connect)
  2. 发数据(send)
  3. 收数据(recv)

电脑当客户端。


4. 你代码里用到的 socket 方法逐个解释

下面我按出现顺序来:


4.1 socket.socket(family, type)

创建一个 socket 对象。

python 复制代码
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
  • AF_INET:IPv4(192.168.x.x 这种)
  • SOCK_STREAM:TCP

等价理解

"造一个 TCP 的 IPv4 插座"。


4.2 setsockopt(level, optname, value)

设置 socket 的"行为参数"。

python 复制代码
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
  • SO_REUSEADDR=1:允许端口快速复用
    你 ESP32 程序崩了/重启后,端口不会被系统拖住导致 bind 失败。

等价理解

"这个插座关了马上还能再开,不要卡住"。

不是必须,但非常推荐。


4.3 bind((ip, port))

把 socket 绑定到指定 IP+端口上。

python 复制代码
s.bind((ip, 8080))

ESP32 必须告诉系统:

"我就用这个 IP 的 8080 端口开服务"。

如果不 bind:

  • 系统不知道你要在哪个端口接收连接。

4.4 listen(backlog)

开始监听。

此时 socket 进入"被动等待客户端连接"状态。

python 复制代码
s.listen(1)
  • 1 是排队等待连接的客户端数(队列长度)
  • 实验用 1 足够。

等价理解

"店开好了,最多允许 1 个客人在门口排队"。


4.5 accept()

阻塞等待客户端连接。

python 复制代码
conn, client_addr = s.accept()

一旦有客户端 connect 过来,就返回:

  • conn专门和这个客户端通信的连接
  • client_addr:客户端地址(IP, 端口)

关键点
accept() 返回后,s 仍然负责"等下一个连接",

conn 负责"跟当前这位客户端说话"。

等价理解

"有人进店了 → 给他单独分配一个客服 conn"。


4.6 recv(bufsize)

接收数据。

一次最多收 bufsize 字节。

python 复制代码
data = conn.recv(1024)
  • TCP 是"字节流",不是"消息包"
  • 你发 "1",但 TCP 可能分成几段到达,也可能粘在一起
    所以一般要 .strip() 处理。

返回值

  • b'' 空字节 → 表示客户端断开连接。

4.7 send(bytes)

发送数据(必须是 bytes)。

python 复制代码
conn.send(b"LED ON\n")

注意:

  • Python socket 里发送必须是字节
  • MicroPython 也一样

所以你电脑端要:

python 复制代码
s.send(cmd.encode())

4.8 connect((ip, port))(客户端专属)

客户端主动发起连接请求。

python 复制代码
s.connect((ESP_IP, 8080))

如果连接成功,才能 send/recv。


4.9 close()

关闭 socket/连接。

python 复制代码
conn.close()
s.close()
  • conn.close():断开当前客户端
  • s.close():关掉服务器(你的代码里没关,因为要一直运行)

5. 阻塞(blocking)行为:为什么你的代码会"卡住"

socket 默认是阻塞模式

  • accept() 会一直等,直到有人连
  • recv() 会一直等,直到收到数据或断开

所以你看到 ESP32 串口会停在某行不动------这是正常的"等人连"。

想非阻塞也可以:

python 复制代码
s.settimeout(5)   # 5秒超时

或者:

python 复制代码
s.setblocking(False)

但对你这实验来说,阻塞更简单稳定。


6. 编码/解码为什么要做?

TCP 只认识 bytes(字节)

  • 电脑输入的是 str
  • ESP32 收到的是 bytes

所以要互相转换:

电脑发

python 复制代码
s.send(cmd.encode())   # str -> bytes

ESP32 收

python 复制代码
msg = data.decode()    # bytes -> str

7. 你这套 TCP 控灯的最小骨架(背下来就会改)

ESP32 服务器骨架

python 复制代码
s = socket.socket()
s.bind((ip, port))
s.listen(1)

while True:
    conn, addr = s.accept()
    while True:
        data = conn.recv(1024)
        if not data:
            break
        conn.send(b"ok")
    conn.close()

电脑客户端骨架

python 复制代码
s = socket.socket()
s.connect((ip, port))
s.send(b"hello")
print(s.recv(1024))
s.close()

8. 常见坑(你已经踩过一个)

  1. 文件命名冲突
    电脑端不要叫 socket.py
  2. ESP32 IP 变化
    重启后 IP 会变
  3. 端口没对上
    ESP listen 8080 → 电脑 connect 8080
  4. 防火墙拦截
    Windows 可能阻止 Python 访问网络
  5. TCP 字节流粘包
    复杂时要用分隔符 \n 或固定长度协议

下面我给你的代码逐行加上中文注释,然后再用"流程图式"解释一遍它怎么跑、每一步干啥。你可以直接复制这个带注释版用。


✅ 带详细注释的代码

python 复制代码
# 导入ESP32的引脚控制模块
from machine import Pin
# 导入时间模块:延时、计时
import time
# 导入网络模块:控制WiFi连接
import network
# 导入socket模块:实现TCP网络通信
import socket

# -------------------- 硬件部分 --------------------
# 定义GPIO15为输出口,用来控制LED
led1 = Pin(15, Pin.OUT)
# 初始化为低电平(灯灭)
led1.value(0)

# -------------------- WiFi信息 --------------------
# 路由器WiFi名称
ssid = "jerry"
# 路由器WiFi密码
password = "ldk123456"


# -------------------- 连接WiFi函数 --------------------
def wifi_connect():
    # 创建一个WLAN对象,STA_IF表示"客户端模式",用来连路由器
    wlan = network.WLAN(network.STA_IF)

    # 打开WiFi功能
    wlan.active(True)

    # 如果当前没有连接WiFi
    if not wlan.isconnected():
        print("connecting to network...")

        # 开始连接路由器
        wlan.connect(ssid, password)

        # 记录开始连接的时间,用来判断超时
        start_time = time.time()

        # 只要还没连上,就一直等待
        while not wlan.isconnected():

            # 连接过程中让LED闪烁,表示正在连接
            led1.value(1)
            time.sleep_ms(300)
            led1.value(0)
            time.sleep_ms(300)

            # 超过15秒仍未连接成功,则判定为超时
            if time.time() - start_time > 15:
                print("WIFI Connect Timeout!")
                return None   # 返回None表示连接失败

    # 连接成功后打印网络信息
    # ifconfig() 返回 (IP, 子网掩码, 网关, DNS)
    print("network information:", wlan.ifconfig())

    # 返回IP地址,用于后续开TCP服务器
    return wlan.ifconfig()[0]


# -------------------- TCP服务器函数 --------------------
def start_server(ip, port=8080):
    # 服务器地址 = (ESP32当前IP, 端口)
    addr = (ip, port)

    # 创建TCP socket对象
    # AF_INET: IPv4
    # SOCK_STREAM: TCP
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    # 设置端口复用,避免重启程序时端口被占用
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    # 绑定地址(告诉系统:我要用这个IP+端口)
    s.bind(addr)

    # 开始监听,允许1个客户端排队连接
    s.listen(1)

    print("TCP server listening on", addr)

    # 服务器主循环:一直等待客户端连接
    while True:
        # accept() 会阻塞等待客户端连接
        # 连接成功后得到:
        # conn:与当前客户端通信的socket
        # client_addr:客户端地址
        conn, client_addr = s.accept()
        print("client connected from", client_addr)

        try:
            # 与当前客户端通信循环
            while True:
                # 接收客户端数据(最多1024字节)
                data = conn.recv(1024)

                # 如果客户端断开连接,则data为空
                if not data:
                    break

                # 把收到的数据转成字符串,去空格,转大写
                msg = data.decode().strip().upper()
                print("recv:", msg)

                # 如果收到的命令是 1 或 ON 等 → 点亮LED
                if msg in ("1", "ON", "OPEN", "HIGH"):
                    led1.value(1)
                    conn.send(b"LED ON\n")  # 回发确认信息

                # 如果收到的命令是 0 或 OFF 等 → 熄灭LED
                elif msg in ("0", "OFF", "CLOSE", "LOW"):
                    led1.value(0)
                    conn.send(b"LED OFF\n")

                # 其他命令都认为无效
                else:
                    conn.send(b"Unknown cmd. Use ON/OFF or 1/0\n")

        # 通信过程中出现异常就打印
        except Exception as e:
            print("error:", e)

        # finally 一定执行:关闭当前客户端连接
        finally:
            conn.close()
            print("client disconnected")


# -------------------- 程序入口 --------------------
if __name__ == "__main__":
    # 先连接WiFi,得到ESP32当前IP
    ip = wifi_connect()

    # 只有WiFi连接成功(ip不是None)才启动TCP服务器
    if ip:
        start_server(ip, 8080)

✅ 程序运行流程(一步一步)

阶段 1:初始化

  1. ESP32 启动运行代码
  2. 设置 GPIO15 为输出口
  3. LED 先灭

阶段 2:连接 WiFi

  1. ESP32 以 STA(客户端)模式连接路由器

  2. 连接时 LED 闪烁

  3. 15 秒内成功:

    • 打印 IP 信息
    • 返回 IP(比如 192.168.2.132)
  4. 超过 15 秒仍失败:

    • 打印超时
    • 返回 None
    • 不再开服务器

阶段 3:启动 TCP 服务器

  1. 创建 socket(TCP)

  2. 绑定 IP + 端口(8080)

  3. 监听端口

  4. 控制台输出:

    复制代码
    TCP server listening on ('192.168.2.132',8080)

阶段 4:等待客户端连接

  • accept() 阻塞等待

  • 电脑一连上就输出:

    复制代码
    client connected from ('192.168.2.247',xxxxx)

阶段 5:接收命令控制灯

  1. 客户端发来数据

  2. ESP32 收到并解析成 msg

  3. 判断:

    • 1 / ON / HIGH → GPIO15=1 → 灯亮
    • 0 / OFF / LOW → GPIO15=0 → 灯灭
    • 其他 → 回传"Unknown cmd"

阶段 6:客户端断开

  • 电脑端关闭连接后:

    • recv() 收到空 → break
    • conn.close() 断开
    • 服务器又回去继续等下一个客户端

你现在已经掌握了

✅ WiFi 连接

✅ TCP 服务器

✅ 电脑下发命令控制 GPIO

✅ 基础通信协议设计


import socket

ESP_IP = "192.168.2.132" # 你的ESP32 IP

PORT = 8080

def send_cmd(cmd):

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

s.connect((ESP_IP, PORT))

s.send(cmd.encode())

resp = s.recv(1024).decode()

s.close()

print("resp:", resp)

if name == "main ":

while True:

cmd = input("Input cmd(ON/OFF/1/0/q): ").strip()

if cmd.lower() == "q":

break

send_cmd(cmd)

相关推荐
可爱又迷人的反派角色“yang”1 小时前
ansible剧本编写(三)
linux·网络·云计算·ansible
m0_738120721 小时前
应急响应——知攻善防Web-3靶机详细教程
服务器·前端·网络·安全·web安全·php
橘子真甜~7 小时前
C/C++ Linux网络编程15 - 网络层IP协议
linux·网络·c++·网络协议·tcp/ip·计算机网络·网络层
Allen正心正念20258 小时前
网络编程与通讯协议综合解析
网络
bing_feilong8 小时前
ubuntu中的WIFI与自身热点切换
网络
CodeByV8 小时前
【网络】UDP 协议深度解析:从五元组标识到缓冲区
网络·网络协议·udp
虹科网络安全9 小时前
艾体宝洞察 | 利用“隐形字符”的钓鱼邮件:传统防御为何失效,AI安全意识培训如何补上最后一道防线
运维·网络·安全
石像鬼₧魂石10 小时前
Kali Linux 网络端口深度扫描
linux·运维·网络
适应规律11 小时前
UNeXt-Stripe网络架构解释
网络
纸带13 小时前
USB通信的状态
网络