从单机到互联 — 理解计算机之间如何"对话",掌握 Python 网络编程的核心技术
网络世界的"通用语言" — 计算机之间交流的基础规则
想象一下:一个只会说中文的人和一个只会说法语的人见面了,他们无法交流。计算机之间也面临同样的问题 — 不同的计算机硬件、操作系统各不相同,如果没有一套统一的"语言规则",它们根本无法沟通。
通信协议就像"交通规则"。全世界的车辆品牌不同、大小各异,但只要大家都遵守红灯停、绿灯行的规则,交通就能有序进行。TCP/IP协议就是网络世界里所有计算机都遵守的"交通规则"。
TCP/IP(Transmission Control Protocol / Internet Protocol)是互联网最基本的通信协议,它不是单一的协议,而是一个协议族(Protocol Suite),定义了数据如何在网络中打包、寻址、传输和接收。
TCP(传输控制协议):负责数据的可靠传输,确保数据完整无误地到达。
IP(网际协议):负责数据的寻址和路由,确保数据能找到正确的目的地。
TCP/IP协议将网络通信分为4个层次,每层各司其职,就像快递系统的各个环节:
应用层 = 你写了一封信(具体内容)
传输层 = 快递公司把信装进包裹,编好单号(保证可靠送达)
网络层 = 物流中心规划从北京到上海的最优路线(寻址路由)
网络接口层 = 实际的卡车、飞机在公路和航线上运输(物理传输)
每台联网的计算机都有一个唯一的 IP 地址,就像每栋房子都有门牌号。目前广泛使用的是 IPv4 地址。
由4组 0~255 的数字组成,用点分隔。例如:192.168.1.100
一些特殊地址:127.0.0.1 — 本机地址(localhost),0.0.0.0 — 监听所有网络接口
TCP在传输数据前,需要先建立连接。这个建立连接的过程叫做"三次握手"(Three-way Handshake)。为什么要"握手"三次?因为双方都需要确认:我能发消息给你,你也能发消息给我。
SYN(Synchronize,同步)= 请求建立连接的信号。可以理解为"我想跟你说话"。
ACK(Acknowledge,确认)= 确认收到的信号。可以理解为"我收到了,我知道了"。
这两个术语只是 TCP 数据包里的标志位,就像信件上盖的"挂号"或"回执"邮戳。计算机看到 SYN 就知道"对方想建立连接",看到 ACK 就知道"对方确认了"。
第一次握手 (SYN):你给对方发了一个好友请求 → "我想加你好友"
第二次握手 (SYN+ACK):对方点了"通过"并给你回了一条消息 → "通过了(ACK),我也想聊聊(SYN)"
第三次握手 (ACK):你看到对方通过了,回一条 → "好的收到(ACK),咱们开始聊!"
为什么两次不够?如果只有两次,服务器(对方)确认了客户端(你)能发消息,但客户端还不确定服务器能不能发消息给自己。第三次握手就是客户端向服务器确认:"我确实能收到你的回复"。这样双方都放心了。
三次握手的核心目的就是:确认双方的发送能力和接收能力都正常。第一次确认客户端能发,第二次确认服务器能收能发,第三次确认客户端能收。缺一不可。
传输层有两大主角:TCP 和 UDP,它们各有特长,适用于不同场景。
| 对比项 | TCP 传输控制协议 | UDP 用户数据报协议 |
|---|---|---|
| 连接方式 | 面向连接(先握手再传数据) | 无连接(直接发送) |
| 可靠性 | ✅ 可靠,保证数据完整有序到达 | ❌ 不可靠,可能丢包或乱序 |
| 速度 | 较慢(需要确认机制) | 较快(无需等待确认) |
| 传输方式 | 字节流(连续的数据流) | 数据报(独立的消息包) |
| 典型场景 | 网页浏览、文件传输、邮件 | 视频直播、语音通话、在线游戏 |
TCP 像寄挂号信:有编号、有签收、丢了能补寄,确保对方一定收到。但流程多,速度较慢。
UDP 像在广场上喊话:直接开口就说,不管对方有没有听到。速度快,但可能漏听。适合直播 — 掉几帧画面无所谓,但不能有延迟。
Socket(套接字)是网络通信的基础接口。应用程序通过 Socket 向网络发出请求或应答请求,就像电器通过插座接入电网一样。
Socket = IP 地址 + 端口号。IP 地址确定是哪台计算机,端口号确定是计算机上的哪个程序。就像快递地址 = 小区地址(IP)+ 门牌号(端口)。
Python 的 socket 模块提供了网络通信的各种方法。不用死记硬背,下面我们直接通过"局域网聊天室"的实战项目来学会它们。
搭建一个局域网聊天室!
任务:老师在电脑上运行"聊天服务器",同学们在自己的电脑上运行"聊天客户端",全班就能在局域网里实时聊天了!
服务器负责接收所有人的消息,再转发给其他所有人。就像一个群聊的"中转站"。
每个同学运行客户端,输入自己的昵称,然后就可以发消息了。
服务器要做的事情:监听端口 → 接受多个客户端连接 → 把每个人发的消息转发给其他所有人。
# ============================== # 局域网聊天室 - 服务器端 # 老师在自己电脑上运行这个文件 # ============================== import socket # 导入socket模块,提供网络通信功能 import threading # 导入线程模块,让服务器能同时处理多个客户端 # ---------- 基本配置 ---------- HOST = '0.0.0.0' # 监听所有网卡的IP(这样局域网内的同学都能连进来) PORT = 9999 # 端口号,相当于服务器的"房间号",客户端要填一样的 # ---------- 存储所有连接的客户端 ---------- clients = {} # 字典:{客户端socket对象: 昵称},记录谁连进来了 def broadcast(message, sender_socket=None): """把消息发送给所有人(除了发送者自己)""" for client_socket in clients: # 遍历所有已连接的客户端 if client_socket != sender_socket: # 跳过发送者自己(避免自己收到自己的消息) try: client_socket.send(message.encode('utf-8')) # 发送消息(要先编码成字节) except: client_socket.close() # 发送失败说明客户端已断开,关闭连接 del clients[client_socket] # 从列表里移除 def handle_client(client_socket, addr): """处理单个客户端的函数(每个客户端分配一个线程运行这个函数)""" try: # 第一条消息是客户端发来的昵称 nickname = client_socket.recv(1024).decode('utf-8') # 接收昵称 clients[client_socket] = nickname # 保存到字典里 # 通知所有人:有新同学加入了 welcome = f'📢 【{nickname}】加入了聊天室!当前在线:{len(clients)}人' print(welcome) # 服务器控制台也打印一下 broadcast(welcome) # 告诉所有已连接的人 # 持续接收这个客户端发来的消息 while True: data = client_socket.recv(1024).decode('utf-8') # 接收消息 if not data: # 如果收到空数据,说明客户端断开了 break msg = f'💬 【{nickname}】: {data}' # 拼接 "昵称: 消息内容" print(msg) # 服务器控制台打印 broadcast(msg, client_socket) # 转发给其他所有人 except: pass # 出现异常(比如客户端强制关闭) finally: # 客户端离开时的清理工作 nickname = clients.get(client_socket, '未知用户') if client_socket in clients: del clients[client_socket] # 从字典里移除 client_socket.close() # 关闭socket连接 leave_msg = f'👋 【{nickname}】离开了聊天室。在线:{len(clients)}人' print(leave_msg) broadcast(leave_msg) # 通知其他人 # ---------- 启动服务器 ---------- server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 创建TCP套接字 server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 允许端口复用(避免重启时"端口被占用") server.bind((HOST, PORT)) # 绑定地址:在所有网卡的9999端口上监听 server.listen(50) # 开始监听,最多排队50个连接 print(f'🚀 聊天室服务器已启动!端口: {PORT}') print(f'📌 请告诉同学们服务器IP地址(用 ipconfig 或 ifconfig 查看)') print('等待同学们连接...\n') while True: client_socket, addr = server.accept() # 等待新的客户端连接进来(阻塞) print(f'新连接来自: {addr}') # 打印客户端的IP和端口 # 为每个客户端创建一个新线程,这样可以同时处理多人聊天 thread = threading.Thread(target=handle_client, args=(client_socket, addr)) thread.daemon = True # 设为守护线程(主程序退出时自动结束) thread.start() # 启动线程
客户端要做的事情:连接服务器 → 输入昵称 → 一边接收别人的消息,一边发送自己的消息。
# ============================== # 局域网聊天室 - 客户端 # 同学们在自己电脑上运行这个文件 # ============================== import socket # 网络通信模块 import threading # 线程模块(需要同时收消息和发消息) # ---------- 连接配置 ---------- # ⚠️ 这里要改成老师电脑的IP地址! # 老师在终端输入 ipconfig (Windows) 或 ifconfig (Mac/Linux) 查看 SERVER_IP = '192.168.1.100' # ← 改成老师告诉你的IP地址 SERVER_PORT = 9999 # 端口号,和服务器一致 # ---------- 创建连接 ---------- client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 创建TCP套接字 try: client.connect((SERVER_IP, SERVER_PORT)) # 连接服务器(三次握手就在这里发生!) print('✅ 成功连接到聊天室服务器!') except: print('❌ 连接失败!请检查IP地址和端口号是否正确,以及老师的服务器是否已启动') exit() # 连接失败就退出程序 # ---------- 输入昵称并发送给服务器 ---------- nickname = input('请输入你的昵称: ') # 让用户输入昵称 client.send(nickname.encode('utf-8')) # 把昵称发给服务器(第一条消息) print(f'欢迎 {nickname}!输入消息后按回车发送,输入 quit 退出\n') # ---------- 接收消息的函数(在后台线程运行)---------- def receive_messages(): """持续接收服务器转发过来的消息""" while True: try: data = client.recv(1024).decode('utf-8') # 接收消息 if data: print(f'\r{data}\n你: ', end='') # 打印别人的消息,\r回到行首避免混乱 except: print('\n❌ 与服务器的连接已断开') break # 启动一个后台线程专门用来接收消息(这样不会阻塞发送消息) recv_thread = threading.Thread(target=receive_messages) recv_thread.daemon = True # 守护线程,主程序退出时自动结束 recv_thread.start() # ---------- 发送消息(主线程)---------- while True: msg = input('你: ') # 等待用户输入消息 if msg.lower() == 'quit': # 输入quit退出 print('👋 你已退出聊天室') client.close() # 关闭socket连接 break if msg: # 如果不是空消息 client.send(msg.encode('utf-8')) # 发送给服务器
1. 把代码里的 SERVER_IP = '192.168.1.100' 改成老师告诉你的真实IP地址!
2. 确保你和老师连的是同一个WiFi网络(在同一个局域网里)
3. 先等老师启动服务器,你再运行客户端
4. 输入 quit 退出聊天室
运行 chat_server.py,服务器在 9999 端口开始监听,等待同学们连接。
运行 chat_client.py,connect() 触发 TCP 三次握手,建立连接。输入昵称后加入聊天室。
同学 A 发一条消息 → 服务器收到 → 服务器转发给 B、C、D… 所有其他人。每个人都能实时看到。
输入 quit 或直接关闭窗口,服务器检测到断开,通知其他人"某某离开了"。
更轻量、更快速 — 用 UDP 做一个"匿名弹幕墙"
UDP(User Datagram Protocol)不需要建立连接,直接发数据。速度快、代码简单,但不保证对方一定能收到。适合对实时性要求高、偶尔丢数据没关系的场景。
偶尔掉一两个音节无所谓,但延迟很影响体验
掉几帧感觉不到,但卡顿半秒就很明显
角色位置更新需要极低延迟
一问一答,简单快速
定期发送网络状态信息
小文件快速传输
做一个简单的互动:老师运行"弹幕墙服务器",同学们往服务器发送匿名消息,所有消息实时显示在老师的屏幕上。可以用来提问、吐槽、发表情包文字……
弹幕场景:消息量大、实时性要求高、偶尔丢一两条无所谓。而且 UDP 代码更简单 — 不需要建立连接,发就完了!
# ============================== # 匿名弹幕墙 - 服务器端(老师运行) # 接收所有同学发来的UDP消息并显示 # ============================== import socket # 创建UDP套接字(注意:SOCK_DGRAM 表示 UDP,不是TCP的SOCK_STREAM) server = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 绑定地址:监听所有网卡的8888端口 server.bind(('0.0.0.0', 8888)) print('🎬 弹幕墙已开启!等待同学们发送弹幕...') print('=' * 50) # 持续接收弹幕 while True: # recvfrom 会同时返回数据和发送者的地址 # 注意:UDP不需要accept!因为没有"连接"的概念 data, addr = server.recvfrom(1024) message = data.decode('utf-8') # 把字节解码成字符串 ip = addr[0] # 发送者的IP地址 # 用IP最后一段作为匿名标识(保护隐私又能区分) anon_id = ip.split('.')[-1] print(f' 💬 匿名{anon_id}: {message}')
# ============================== # 匿名弹幕墙 - 客户端(同学运行) # ============================== import socket # 创建UDP套接字 client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # ⚠️ 改成老师的IP地址! SERVER_IP = '192.168.1.100' # ← 改成老师告诉你的IP SERVER_PORT = 8888 print('🎤 弹幕发送器已就绪!输入消息后按回车发送,输入 quit 退出') while True: msg = input('发送弹幕: ') if msg.lower() == 'quit': print('👋 已退出') break if msg: # UDP发送用sendto,必须指定目标地址 # 注意:不需要connect!直接就能发 client.sendto(msg.encode('utf-8'), (SERVER_IP, SERVER_PORT)) client.close()
UDP 代码明显更简单:不需要 listen()、accept()、connect(),不需要线程。
因为 UDP "不建立连接",服务器直接用 recvfrom() 收数据就行,客户端直接用 sendto() 发就行。
代价是:消息可能丢失、可能乱序。但弹幕场景完全可以接受。
理解浏览器和服务器之间的"对话" — HTTP协议
当你在浏览器地址栏输入 www.baidu.com 并按下回车,背后发生了什么?
HTTP(HyperText Transfer Protocol,超文本传输协议)是浏览器和Web服务器之间通信的协议。它基于 TCP 协议,规定了客户端如何请求、服务器如何响应。
你(浏览器/客户端)走进餐厅,对服务员说:"请给我一份宫保鸡丁"(HTTP请求)
服务员(Web服务器)把你的请求转达给后厨,后厨做好菜端给你(HTTP响应)
菜单就是 URL 地址,点菜方式就是请求方法(GET/POST),上菜结果就是状态码(200成功/404没这道菜)
浏览器通过 TCP/IP 协议与服务器建立 TCP 连接(三次握手)。
浏览器向服务器发送 HTTP 请求,说明想要什么资源(网页、图片等)。
服务器找到资源,如果是动态内容还会调用后端程序处理,然后将 HTML 等内容返回。
数据传完后断开连接。浏览器解析 HTML 并渲染出你看到的页面。
HTTP定义了多种请求方法,告诉服务器你想对资源做什么操作。用一个"管理网上商城"的场景来理解:
| 方法 | 作用 | 具体例子 |
|---|---|---|
| GET | "给我看看" 获取数据,不修改任何东西 |
你在淘宝搜索"耳机" → 浏览器发送 GET 请求获取搜索结果页面 你点开商品详情页 → 又是一个 GET 请求 |
| POST | "我要提交新东西" 向服务器提交数据,创建新资源 |
你在网站注册账号,填完信息点"提交" → POST 请求,把你填的信息发给服务器 你发一条微博 → POST 请求,服务器收到后创建这条新微博 |
| PUT | "我要修改已有的东西" 用新数据替换旧数据 |
你在个人主页点"编辑资料",改了昵称和头像,点保存 → PUT 请求,用新资料覆盖旧的 商家修改商品价格 → PUT 请求更新商品信息 |
| DELETE | "我要删掉这个" 删除指定资源 |
你删除一条朋友圈 → DELETE 请求,服务器把这条数据删掉 商家下架一个商品 → DELETE 请求 |
| HEAD | "只告诉我基本信息" 和GET一样,但只返回头信息,不返回正文 |
下载一个大文件前,先发 HEAD 请求问一下"这个文件多大?" → 服务器只回复文件大小,不传文件本身 检查一个网页是否还存在,不需要加载全部内容 |
| OPTIONS | "你支持哪些操作?" 查询服务器对某个资源支持的请求方法 |
前端App想调用后端API前,先问一下"这个接口允许 GET、POST 还是 PUT?" → 这就是 OPTIONS 预检请求 在浏览器开发中,跨域请求时浏览器会自动先发一个 OPTIONS 请求 |
GET = 看(读取) · POST = 交(提交新的) · PUT = 改(修改已有的) · DELETE = 删 · HEAD = 瞄一眼(只看标题不看内容) · OPTIONS = 先问问(探路)
每次 HTTP 响应都带一个3位数字的状态码,告诉客户端请求的处理结果:
200 OK:请求成功,最常见
201 Created:资源创建成功
301:永久搬家了
302:临时跳转
404 Not Found:页面不存在
403:没有权限
500:服务器内部错误
502:网关错误
2xx = "好的,给您!"(成功)
3xx = "您找的东西搬到别处了,我帮您转过去"(重定向)
4xx = "这是您的问题哦"(您找错了/没权限)
5xx = "抱歉,是我们这边出了问题"(服务器故障)
Python 内置了一个简单的 HTTP 服务器模块,不用写代码,一行命令就能启动!
比如叫 my_website,在里面创建一个 index.html 文件,随便写点HTML内容(比如 <h1>Hello! 这是我的网页</h1>)。
Windows: cd Desktop\my_website Mac: cd ~/Desktop/my_website
# 在当前目录启动HTTP服务器,端口8080 python -m http.server 8080
地址栏输入 http://127.0.0.1:8080,你就能看到文件夹里的文件列表了!点击 index.html 就能看到你写的网页。恭喜,你搭建了人生中第一个Web服务器!
在 my_website 文件夹里多放几个 HTML 文件、图片,刷新浏览器看看效果。你也可以用手机浏览器访问 http://你电脑的IP:8080,在手机上看到你电脑上的网页!
Web服务器与Python应用之间的"标准翻译官"
CGI(Common Gateway Interface,通用网关接口)是最早让 Web 服务器和后端程序"对话"的方式。
想象一个饭店(Web服务器),顾客(浏览器)每点一道菜(请求),饭店就从街上临时雇一个厨师来做这道菜。做完菜,厨师就被辞退了。下一个顾客再点菜,又重新雇一个。
这就是 CGI 的工作方式 — 每个请求都启动一个新进程。10个人同时访问,就启动10个进程。能用,但效率很差,在高并发下服务器直接扛不住。
WSGI(Web Server Gateway Interface,Web服务器网关接口)是 Python 专门定义的一套标准接口,解决了 CGI 效率低的问题。
饭店升级了!现在有一个固定的后厨团队(Application)和一个专职的前台经理(Server)。
前台经理(Server)负责接待顾客、记录点单、上菜结账 — 这是 Nginx、Apache 这类Web服务器干的事。
后厨大厨(Application)负责看点单做菜 — 这是你用 Flask/Django 写的 Python 程序干的事。
WSGI 就是前台和后厨之间约定好的"点单格式和传菜窗口":前台把写好的点单(请求信息)从传菜窗口递进去,后厨做好菜从窗口递出来。
负责接收浏览器的 HTTP 请求,把请求信息打包(环境变量),通过 WSGI 接口递给 Application。最后把 Application 返回的结果发回给浏览器。
常见的 Server:Gunicorn、uWSGI、Python内置的 wsgiref
负责处理业务逻辑:根据用户请求的是哪个页面,生成对应的 HTML 内容,通过回调函数返回给 Server。
常见的 Application 框架:Flask、Django、FastAPI
有了 WSGI 标准,Web服务器和Python应用可以自由组合:你可以用 Nginx + Flask,也可以用 Apache + Django,因为它们都遵守同一套"对接规范"。就像 USB 接口标准一样 — 任何品牌的U盘都能插进任何品牌的电脑。
一句话:WSGI 让 Python Web 生态统一了。所以你学 Flask/Django 之前,要知道底层就是 WSGI 在把服务器和你的代码连起来。
| 对比项 | CGI | WSGI |
|---|---|---|
| 中文名 | 通用网关接口 | Web服务器网关接口 |
| 工作方式 | 每个请求新开一个进程 | 保持运行,复用进程/线程 |
| 效率 | ❌ 低(频繁创建/销毁进程) | ✅ 高(进程常驻内存) |
| 饭店类比 | 每来一个顾客临时雇厨师 | 固定厨师团队持续接单 |
| 适用语言 | 通用(任何语言) | Python专用 |
| 现在还用吗? | 基本淘汰 | Python Web 开发的标准 |
回顾核心知识点,建立完整知识框架
TCP/IP 是互联网的基础协议族;Socket 是程序访问网络的接口;HTTP 是Web通信的协议;WSGI 是 Python Web 开发的标准接口。
TCP 可靠但慢(网页/文件传输),UDP 快但不可靠(直播/游戏)。选择取决于应用场景。
用 TCP 实现了全班局域网聊天室,用 UDP 实现了匿名弹幕墙,用一行命令搭建了Web服务器。
WSGI 是理解 Flask/Django 的基础,也是从网络编程迈向 Web 开发的桥梁。下一站:Web 框架!