news 2026/8/24 4:45:46

Python 九阳神功之肆:网络编程 · Socket 从入门到实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Python 九阳神功之肆:网络编程 · Socket 从入门到实战

第 4 篇 · 兵器谱:网络编程 · Socket 从入门到实战

行走江湖,得有趁手的兵器。

IP 是江湖坐标,端口是暗号,TCP/UDP 是传音入密的内功心法。

本篇为你打造网络编程的「屠龙刀」,客户端与服务端通信,一剑封喉!


1. 网络设备的三要素

  • IP地址:网络环境中电脑的唯一标识,通过 IP 可以找到某台计算机
  • 端口:一台服务器上会有 N 个应用,通过唯一的端口映射服务器中某个应用(服务)
  • 协议:网络编程协议 — TCP / UDP

1.1 域名解析

输入www.baidu.com的时候,会进入 DNS 解析:

  • 本地 host 文件 → 本地 DNS 解析 → 远程 DNS 解析 → 获取 IP 地址 → 找到服务器

8.8.8.8 是比较靠谱的 DNS 解析器
绝大部分网站的端口是 80 端口,默认不需要写


1.2 IP 版本

  • IPv4:IP 地址量约 2 的 32 次方 ≈ 43 亿,已接近耗尽
  • IPv6:IP 地址量约 2 的 128 次方,几乎无穷无尽

1.3 端口可用范围

端口号的可用范围是0 ~ 65535,作用是帮你找到在计算机上独一无二的服务。

不要去抢占知名端口号:0 ~ 1023。但也有一些不在这个范围内,比如 MySQL 的 3306。

IP 地址帮你锁定网络中的计算机设备,端口号帮你在这个设备上找到对应的服务。


2. TCP 与 UDP 的区别

TCP(传输控制协议)面向连接、可靠;UDP(用户数据报协议)无连接、不可靠,都属于传输层协议。

2.1 核心对比

对比项TCPUDP
连接特性面向连接,三次握手建立连接,四次挥手断开连接无连接,发数据前不需要建立连接,直接发包
可靠性可靠传输:确认应答、超时重传、丢包重传;不丢包、不乱序不可靠:不保证到达,不保证顺序,可能丢包、乱序
流量/拥塞控制有流量控制(滑动窗口)、拥塞控制无,只管发送,不感知网络拥堵
报文面向字节流,数据是流,没有报文边界面向数据包,保留报文边界,一个 UDP 包就是一条消息
头部开销头部 20~60 字节,开销大头部固定 8 字节,开销小
速度较慢,握手、重传、确认带来额外开销速度快,延迟低,开销小
适用场景文件传输、HTTP/HTTPS、SSH、数据库访问,要求数据不能丢直播、视频通话、游戏、DNS,允许少量丢包,追求低延迟

2.2 形象比喻

  • TCP = 打电话:拨号(三次握手)→ 通话(可靠传输)→ 挂断(四次挥手);说话对方一定能收到,顺序不变,但是流程多
  • UDP = 寄明信片:直接扔出去,不用先联系对方;明信片可能丢失、顺序乱掉,但是发得快,成本低

3. TCP 的三次握手

客户端主动发起连接,服务端被动接收,一共 3 次报文交互。

3.1 三次握手流程

  1. 第一次握手:客户端 → 服务端【SYN】
    客户端发送SYN=1,随机初始序列号seq=x,客户端进入SYN_SENT状态。
    含义:客户端请求建立连接。
  2. 第二次握手:服务端 → 客户端【SYN+ACK】
    服务端收到 SYN,回复SYN=1,ACK=1ack=x+1(确认客户端),自己随机初始序列号seq=y;服务端进入SYN_RCVD状态。
    含义:同意连接,同时也向客户端发起连接请求。
  3. 第三次握手:客户端 → 服务端【ACK】
    客户端收到 SYN+ACK,回复ACK=1ack=y+1;客户端进入ESTABLISHED(已连接);服务端收到这个 ACK 后,也进入ESTABLISHED,连接正式建立,可以传数据。

3.2 为什么是三次,不是两次?

  • 两次握手:服务端收到 SYN 就直接建立连接。如果客户端旧的失效 SYN 延迟到达服务器,服务器会建立无效连接,白白占用资源。
  • 三次握手:双方收发能力都得到验证:客户端能发能收、服务端能发能收。第三次 ACK 可以拒绝过期的历史连接。

SYN=1 表示握手连接

客户端发 SYN=1,发 seq=x
服务端回 SYN=1,发 ack=x+1,seq=y
客户端回 ack=y+1


4. TCP 的四次挥手

任何一方都可以主动关闭,下面以客户端主动关闭举例。

4.1 四次挥手流程

  1. 第一次挥手:客户端 → 服务端【FIN】
    客户端发送FIN=1seq=u,客户端进入FIN_WAIT_1
    含义:客户端不再发送数据,但还可以接收数据。
  2. 第二次挥手:服务端 → 客户端【ACK】
    服务端回复ACK=1ack=u+1seq=v;服务端进入CLOSE_WAIT;客户端收到 ACK 进入FIN_WAIT_2
    含义:收到关闭请求;此时服务端还可以继续向客户端发送剩余数据
  3. 第三次挥手:服务端 → 客户端【FIN】
    服务端数据全部发送完毕,发送FIN=1ACK=1seq=wack=u+1;服务端进入LAST_ACK
    含义:服务端也没有数据要发了,请求关闭。
  4. 第四次挥手:客户端 → 服务端【ACK】
    客户端回复ACK=1ack=w+1seq=u+1;客户端进入TIME_WAIT(等待 2MSL);服务端收到 ACK,立刻进入CLOSED,连接关闭。客户端等待2MSL之后,也进入 CLOSED。

FIN=1 表示挥手连接

客户端发 FIN=1,seq=u
服务端回 ack=u+1 — 把剩余的数据传输完成
服务端发 FIN=1,ack=u+1,seq=w
客户端回 ACK=1,ack=w+1

4.2 为什么挥手要 4 次,握手只要 3 次?

握手时 SYN 和 ACK 可以合并成一个报文;挥手时,服务端收到 FIN,可能还有业务数据没发完,不能立刻发 FIN,只能先回复 ACK,等数据发完再发 FIN,所以拆成两次,总共 4 次。


5. Socket 套接字

5.1 Socket 套接字对象介绍

使用 socket 来进行网络通信编程工具。

# 引入socket模块importsocket# 创建一个socket对象server_socket=socket.socket(socket.AF_INET,socket.SOCK_STREAM)# 地址族选用 IPv4,传输类型选用字节流print(server_socket)
  • 客户端(学名 Client):前端 / HTML / CSS / JS / Android / iOS / 小程序 / Flutter / RN
  • 服务端(学名 Server):Python / Java / Go / Rust / Node.js / C#

5.2 Socket 客户端和服务端通信流程分析

服务端流程

  1. 创建一个 socket 对象 — 大管家(不负责和客户端直接对话)
  2. bind()绑定 IP 和端口号 — 表示通过这个地址和端口能找到该服务
  3. listen(5)— 设置最大挂起的 socket 数量,默认值为 1,最大为 128,一般设置为 5
  4. accept()— 接收客户端发起连接,生成和客户端对话的 socket 对象,返回客户端信息
    accept 是阻塞的:它不走完,下面谁也别走
  5. send()— 发送数据
  6. recv()— 接收数据(发送和接收没有强制顺序要求,但发送的数据一定是字节流)
  7. close()— 释放当前和客户端对话的 socket

5.3 二进制编解码操作

str="rucy,今天是个好日子"print(str.encode("gbk").decode("gbk"))print(str.encode("utf-8").decode("utf-8"))
  • GBK 编码:一个中文占 2 个字节,英文字符占 1 个字节,支持简体 + 繁体,部分日文
  • UTF-8 编码:一个中文占 3 个字节,英文字符占 1 个字节,支持全球所有字符

如果内容中不包含中文,直接在字符串前加b即表示该字符为 bytes


5.4 Socket 套接字对象创建(服务端)

注意:socket 对象默认是阻塞模式,阻塞的方法有:

  • accept()(服务端专用)
  • recv(bufsize)(客户端/服务端)
  • connect()(客户端专用)

阻塞:当前代码未执行完毕,绝不可能执行下一行代码

importsocketprint("1.创建socket服务器对象")server_socket=socket.socket(socket.AF_INET,socket.SOCK_STREAM)print("2.设置端口号在进程结束立刻释放")server_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,True)print("3.绑定端口号和ip地址")server_socket.bind(("127.0.0.1",9999))print("4.设置监听并设置挂起最大监听量")server_socket.listen(5)print("5.等待客户端连接")client_socket,client_info=server_socket.accept()print("6.客户端连接成功",client_info)print("7.给客户端发送数据")client_socket.send("我是你的服务器对象".encode("utf-8"))print("8.接收客户端消息")data=client_socket.recv(1024)print(f"收到客户端发的消息:{data.decode('utf-8')}")print("9.关闭和客户端通讯的连接")client_socket.close()# server_socket 一般不关

5.5 Socket 套接字对象创建(客户端)

importsocketprint("1.创建客户端socket对象")client_socket=socket.socket(socket.AF_INET,socket.SOCK_STREAM)print("2.通过ip端口连接服务器")client_socket.connect(("127.0.0.1",9999))print("3.连接成功")content=client_socket.recv(1024)print(f"服务器给你说:{content.decode('utf-8')}")send_content=input("跟服务说:")client_socket.send(send_content.encode("utf-8"))print("关闭客户端连接")client_socket.close()

5.6 实现无限聊天版本 — 服务端

importsocket server_socket=socket.socket(socket.AF_INET,socket.SOCK_STREAM)server_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,True)server_socket.bind(("127.0.0.1",9999))server_socket.listen(5)# 阻塞方法client_socket,client_info=server_socket.accept()print(f"和客户端{client_info}连接成功")client_socket.send("你好,我是rucy,欢迎你来到我的聊天房间".encode("utf-8"))# accept recvwhileTrue:data=client_socket.recv(1024)ifnotdata:breakprint(f"收到客户端:{client_info}的消息:{data.decode('utf-8')}")client_socket.close()

5.7 实现无限聊天 — 客户端

importsocket client_socket=socket.socket(socket.AF_INET,socket.SOCK_STREAM)client_socket.connect(("127.0.0.1",9999))data=client_socket.recv(1024)print(f"服务器对你说:{data.decode('utf-8')}")whileTrue:send_content=input("对服务器说(quit退出)")ifsend_content=='quit':breakclient_socket.send(send_content.encode('utf-8'))client_socket.close()

5.8 模拟多任务实现多人版本聊天(服务端)

支持多人聊天的服务端。

importsocket server_socket=socket.socket(socket.AF_INET,socket.SOCK_STREAM)server_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,True)server_socket.bind(("192.168.25.170",9999))server_socket.listen(5)print("完成服务端初始化所有功能")# 模拟多任务 — 多进程 > 多线程 > 多协程whileTrue:client_socket,client_info=server_socket.accept()client_socket.send("欢迎你兄弟来连接rucy的电脑".encode("utf-8"))data=client_socket.recv(1024)print(f"收到客户端{client_info}消息:{data.decode('utf-8')}")client_socket.close()

5.9 模拟多任务多人版本聊天(客户端)

importsocket client_socket=socket.socket(socket.AF_INET,socket.SOCK_STREAM)client_socket.connect(("127.0.0.1",8888))data=client_socket.recv(1024)print(f"收到服务端消息:{data.decode('utf-8')}")content=input("跟rucy说:")client_socket.send(content.encode("utf-8"))client_socket.close()

6. 扩展功能

6.1 文件选择器对象

需求:当输入 img 的时候,自动弹出文件选择器,自动过滤文件,只选择图片。

importtkinterfromtkinterimportfiledialog content=input("对服务器说(img选择图片):")ifcontent=='img':print("选择图片")root=tkinter.Tk()root.withdraw()# 防止弹出多余小窗口file_path=filedialog.askopenfilename(title="请选择图片",filetypes=[("图片","*.png;*.jpg;*.jpeg;*.gif")])print(file_path)root.destroy()

6.2 解析图片获取图片的字节流及详细信息

importosimporttkinterfromtkinterimportfiledialog content=input("输入img选择图片")ifcontent=='img':root=tkinter.Tk()root.withdraw()file_path=filedialog.askopenfilename(title="请选择图片",filetypes=[("图片","*.jpg *.jpeg *.png")])ext=os.path.splitext(file_path)[1][1:]# 拿到图片的扩展名withopen(file_path,"rb")asf:img_data=f.read()# 拿到图片的二进制数据print(f"拿到了图片的扩展名{ext}, 长度大小:{len(img_data)}, 二进制流数据:{img_data}")# TCP协议 二进制字节流 jpeg|42196|image_data

6.3 选择一张图片存储到 data 目录下

6.3.1 快速创建目录

importos# 在当前目录下创建一个abc目录,如果存在则不创建os.makedirs("./abc",exist_ok=True)

6.3.2 查询某个文件夹目录下的文件个数

importos os.makedirs("./abc",exist_ok=True)print(len(os.listdir("./")))# 返回当前目录下所有的文件列表

6.3.3 完整需求实现

  1. 当用户输入 img 的时候,弹出选择图片窗口
  2. 选择完毕图片解析图片,得到图片的后缀、长度、二进制
  3. 在当前目录下创建一个 data 目录,如果存在则不创建
  4. 在 data 目录下写入刚刚读取到的图片,保留原来的后缀,图片的命名为 图片_1、图片_2……
importosimporttkinterfromtkinterimportfiledialog content=input("请输入您的内容(img)")ifcontent=='img':root=tkinter.Tk()root.withdraw()file_path=filedialog.askopenfilename(title="选择图片",filetypes=[("图片","*.png *.jpeg *.jpg")])ext=os.path.splitext(file_path)[1][1:]# 得到后缀withopen(file_path,"rb")asf:img_data=f.read()size=len(img_data)os.makedirs("./data",exist_ok=True)flag=len(os.listdir("./data"))+1withopen(f"./data/图片_{flag}.{ext}","wb")asf:f.write(img_data)print(f"图片已经存储到对应的位置:路径:./data/图片_{flag}.{ext}")

6.4 选择图片通过 Socket(客户端)

importos.pathimportsocketimporttkinterfromtkinterimportfiledialogprint("1.创建客户端对象")client_socket=socket.socket(socket.AF_INET,socket.SOCK_STREAM)client_socket.connect(("127.0.0.1",9999))print("2.客户端连接成功")content=input("请输入您要的内容(img传图片)")ifcontent=='img':root=tkinter.Tk()root.withdraw()file_path=filedialog.askopenfilename(title="请选择图片",filetypes=[("图片","*.jpg *.jpeg *.png")])ext=os.path.splitext(file_path)[1][1:]withopen(file_path,"rb")asf:img_data=f.read()size=len(img_data)# 不但要传字节流,还要传后缀、长度client_socket.sendall(f"IMG|{ext}|{size}".encode("utf-8")+img_data)# 等待服务端图片接收完成,给一个图片上传成功的消息print(client_socket.recv(1024).decode("utf-8"))client_socket.close()

6.5 选择图片通过 Socket(服务端)

importosimportsocketprint("1.创建服务端socket对象")server_socket=socket.socket(socket.AF_INET,socket.SOCK_STREAM)print("2.设置端口释放")server_socket.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,True)print("3.绑定端口")server_socket.bind(("127.0.0.1",9999))print("4.监听")server_socket.listen(5)print("5.接受客户端连接")client_socket,client_info=server_socket.accept()print("6.客户端连接成功",client_info)# 阻塞方法: accept recv connect# 第一次接收,得到一定是图片的元信息: IMG|jpeg|10086|x01first_data=client_socket.recv(1024)iffirst_data.startswith(b"IMG|"):parts=first_data.split(b"|",maxsplit=3)ext=parts[1].decode("utf-8")size=int(parts[2])img_data=parts[3]# 循环接收直到收完所有图片数据whilelen(img_data)<size:img_data+=client_socket.recv(1024)os.makedirs("./data",exist_ok=True)flag=len(os.listdir("./data"))+1withopen(f"./data/图片_{flag}.{ext}","wb")asf:f.write(img_data)client_socket.send(f"图片上传成功,图片路径:./data/图片_{flag}.{ext}".encode("utf-8"))client_socket.close()

7. Socket 编程基础模板(必背)


7.1 服务端基础模板

python

import socket # 1. 创建 socket 对象(IPv4,TCP 字节流) server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 2. 设置端口复用(防止程序崩溃后端口被占用) server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, True) # 3. 绑定 IP 和端口 server_socket.bind(("127.0.0.1", 9999)) # 4. 开启监听,设置最大挂起连接数 server_socket.listen(5) # 5. 等待客户端连接(阻塞) client_socket, client_info = server_socket.accept() # 6. 接收数据(阻塞) data = client_socket.recv(1024) print(data.decode("utf-8")) # 7. 发送数据 client_socket.send("消息内容".encode("utf-8")) # 8. 关闭连接 client_socket.close() # server_socket.close() # 服务端一般不关,如需关闭则取消注释

7.2 客户端基础模板

python

import socket # 1. 创建 socket 对象(IPv4,TCP 字节流) client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 2. 连接服务器 client_socket.connect(("127.0.0.1", 9999)) # 3. 发送数据 client_socket.send("消息内容".encode("utf-8")) # 4. 接收数据(阻塞) data = client_socket.recv(1024) print(data.decode("utf-8")) # 5. 关闭连接 client_socket.close()
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/24 4:45:08

Gazelle 部署排障指南:4 步跑通网卡绑定与 gazelle-pdump 抓包

Gazelle 部署排障指南&#xff1a;4 步跑通网卡绑定与 gazelle-pdump 抓包 【免费下载链接】gazelle A high performance user-mode stack, which powered by dpdk and lwip 项目地址: https://gitcode.com/openeuler/gazelle 部署 Gazelle 时遇到 No NIC is matched、C…

作者头像 李华
网站建设 2026/8/24 4:45:01

typ.ing打字训练工具:外接键盘助力,让打字又快又准!

typ.ing&#xff1a;外接键盘打字的提速神器typ.ing作为一款超棒的打字训练工具&#xff0c;其核心功能是帮助用户在使用外接键盘打字时&#xff0c;实现速度更快、准确率更高的效果。它鼓励用户连接实体键盘来使用&#xff0c;以达到更好的训练效果。直击打字慢、准确率低痛点…

作者头像 李华
网站建设 2026/8/24 4:44:30

AI智能体技能安全:测绘静态分析检测边界与构建纵深防御体系

1. 项目缘起&#xff1a;当AI智能体技能成为攻击面最近几个月&#xff0c;AI智能体&#xff08;Agent&#xff09;的开发热度持续攀升&#xff0c;从简单的自动化脚本到能够自主规划、调用工具完成复杂任务的智能体&#xff0c;其能力边界正在快速扩展。随之而来的&#xff0c;…

作者头像 李华
网站建设 2026/8/24 4:43:12

最小孔径0.15mm PCB能打吗?猎板工程师揭秘工艺

最小孔径0.15mm PCB怎么打&#xff1f; 于我而言, 身为一名猎板工程师, 时常会被问询这般一个问题, 即最小孔径为0.15mm, 真的可以进行制作吗? 可进行操作, 并且务必要达成良好的效果, 这就要求我们于材料层面、对于设备层面、在工艺层面这三个方面同时施展力量。 先来讲材料…

作者头像 李华
网站建设 2026/8/24 4:42:42

智能家居自动化设计:从状态管理到容错实现

你有没有遇到过这样的场景&#xff1a;深夜想沉浸式看一部电影&#xff0c;刚打开播放器&#xff0c;却发现屏幕亮得刺眼&#xff0c;音响还是外放模式&#xff0c;手机通知还在不断弹窗&#xff0c;客厅的智能灯也亮如白昼。于是你不得不手动完成一连串操作&#xff1a;调暗屏…

作者头像 李华
网站建设 2026/8/24 4:42:39

OpenCut与RustDesk实战:开源视频剪辑与远程桌面工具选型与部署指南

这类工具推荐文章最怕的就是只列名字、不给落地细节。我一般会先看工具能不能在普通环境里稳定跑起来&#xff0c;再看它到底解决了什么具体问题&#xff0c;最后才是批量使用和长期维护的考虑。今天要聊的这四个开源工具&#xff0c;加起来在 GitHub 上有超过 20 万颗星&#…

作者头像 李华