提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
-
- 网络
- IP
-
- 什么是IP
- 子网掩码
- IPv4地址的分类
- 公网与私网
- IPv4与IPv6
- [一、 网络编程三要素(快递怎么寄?)](#一、 网络编程三要素(快递怎么寄?))
- [二、 TCP/IP 协议族与分层模型(流水线打包工作)](#二、 TCP/IP 协议族与分层模型(流水线打包工作))
- [三、 IP 地址大解剖(小区地址怎么看?)](#三、 IP 地址大解剖(小区地址怎么看?))
-
- [1. IP地址是什么?](#1. IP地址是什么?)
- [2. 子网掩码 (Subnet Mask)](#2. 子网掩码 (Subnet Mask))
- [3. IPv4 地址的分类 (A/B/C类)](#3. IPv4 地址的分类 (A/B/C类))
- [四、 公网、私网 与 NAT转换(绝妙的发明!)](#四、 公网、私网 与 NAT转换(绝妙的发明!))
-
- [1. 为什么要有公网和私网?](#1. 为什么要有公网和私网?)
- [2. NAT 转换技术(公司前台小姐姐)](#2. NAT 转换技术(公司前台小姐姐))
- [五、 IPv4 与 IPv6(旧号码薄与新号码薄)](#五、 IPv4 与 IPv6(旧号码薄与新号码薄))
- 端口
- socket套接字
- UDP
- 案例
- TCP
- HTTP
- 案例:发送HTTP请求以及获取响应数据
-
- [1. 为什么不用 Socket 直接写业务?](#1. 为什么不用 Socket 直接写业务?)
- [2. Request 和 Response 做了什么?](#2. Request 和 Response 做了什么?)
- [3. 总结它俩的区别](#3. 总结它俩的区别)
- 核心结论:
- [案例:通过Starlette 构建web接口](#案例:通过Starlette 构建web接口)
- 一、整体总结(把这章串起来)
- 二、易错点/纠错清单(建议你在文章里标出来)
-
- 1)端口范围与分类的易错点
- [2)IP "唯一标识"这句话容易被误解](#2)IP “唯一标识”这句话容易被误解)
- [3)127.0.0.0/8 的表述](#3)127.0.0.0/8 的表述)
- [4)socket 示例代码的常见坑(你文中有几处是真会报错/会卡死的)](#4)socket 示例代码的常见坑(你文中有几处是真会报错/会卡死的))
- [5)TCP "消息边界"是重点易错点(概念型大坑)](#5)TCP “消息边界”是重点易错点(概念型大坑))
- [6)requests 示例代码的易错点](#6)requests 示例代码的易错点)
- [7)"Socket vs Request/Response(HTTP)"容易混淆的点](#7)“Socket vs Request/Response(HTTP)”容易混淆的点)
- [8)Starlette 示例:最大易错点是"伪异步"(性能坑)](#8)Starlette 示例:最大易错点是“伪异步”(性能坑))
网络
使用网络能够把多方电脑等设备链接在一起进行数据传递。网络编程就是让在不同的电脑上的软件能够进行数据传递,即进程之间的通信。
网络编程三要素
IP:网络中每台计算机的唯一标识,通过IP地址可以找到计算机。
端口:标识进程的逻辑地址,通过端口找到计算机中指定的进程(应用软件)。
协议:定义通信规则。
TCP/IP协议族
1)通信协议
通信协议是一组用于规定不同设备或计算机之间如何进行数据交换和通信的规则和约定。它定义了通信的各个方面,包括数据的格式、传输的顺序、错误检查机制、如何处理不同情况(如重传丢失的数据包)等。协议的目的是确保在网络中传输的数据能够被正确、可靠地理解和处理。
通信协议可以应用于计算机网络、电话网络、无线通信等领域。在不同的应用场景下,会使用不同的协议来实现数据交换、控制信息传递等任务。
2)TCP/IP
TCP/IP 协议族,简称TCP/IP,是一组通信协议,用于互联网的数据传输和网络通信,定义了数据如何在不同的计算机之间传输和路由。是现代计算机网络中最常用的网络协议之一。TCP/IP得名于该协议家族的两个核心协议:TCP(传输控制协议)和IP(网际协议)。
3)分层网络模型
OSI 七层网络模型由国际标准化组织制定,但其实现过于复杂,且制定周期过长,在其整套标准推出之前,TCP/IP 模型已经在全球范围内被广泛使用。TCP/IP 模型定义了应用层、传输层、网络层、网络接口层这四层网络结构,但并没有给出网络接口层的具体内容,因此在学习和开发中,通常将网络接口层替换为 OSI 七层模型中的数据链路层和物理层来进行理解,这就是五层网络模型。

4)常见网络协议

IP
什么是IP
IP地址由一串数字组成,用来标识一台电脑在网络中的位置。当设备连接网络,设备将被分配一个IP地址,用作标识。通过IP地址设备间可以互相通讯。IP地址有两个主要功能:标识设备或网络,以及寻址。
Windows下可以在命令提示符中使用ipconfig查看网络适配器的IP。
Linux下可以在终端中使用ifconfig或ip addr查看IP。
子网掩码
IP网络可以在IPv4和IPv6中划分子网。为此将IP地址识别成由两部分组成:网络前缀和主机编号。子网掩码(subnet mask)或无类别域间路由(CIDR)表示法确定了IP地址如何分为网络部分和主机部分。
子网掩码一词仅用于IPv4地址中。但是 IPv4和IPv6都使用CIDR概念和符号。在此,在IP地址后面加斜杠和用于标识网络部分的位数(十进制)。例如:IPv4地址及其子网掩码分别可以是 192.168.10.2 和 255.255.255.0 。因为IP地址的前24位表示网络和子网,所以相同的IP地址和子网的CIDR表示法为192.168.10.2/24。
主机编号全为0,表示网络号,主机编号全为1,表示网络广播。

IPv4地址的分类

公网与私网
公网IP在任何地方都可以访问。而私网IP只能在局域网内访问。
国际规定有一部分IP地址是用于局域网使用,也就是属于私网IP,不在公网中使用的,它们的范围是:
10.0.0.0~10.255.255.255
172.16.0.0~172.31.255.255
192.168.0.0~192.168.255.255
其中127.0.0.1~127.255.255.255用于回路测试,如127.0.0.1可以代表本机IP地址。
网络地址转换(NAT)是一种在IP数据包通过路由器或防火墙时重写来源或目的IP地址或端口的技术。这种技术普遍应用于有多台主机,但只通过一个公有IP地址访问互联网的私有网络中。1990年代中期,NAT是作为一种解决IPv4地址短缺以避免保留IP地址困难的方案而流行起来的,并成了家庭和小型办公室网络连接上的路由器的一个标准特征,因为对他们来说,申请独立的IP地址的代价要高于所带来的效益。
IPv4与IPv6
常见的IP地址分为IPv4与IPv6两大类。IPv4为32位长,通常书写时以四组十进制数字组成,并以点分隔,如:172.16.254.1。IPv6为128位长,通常书写时以八组十六进制数字组成,以冒号分割,如:2001:db8:0🔢0:567:8:1。
随着互联网的快速成长,IPv4的42亿个地址最终于2011年2月3日用尽。相应的科研组织已研究出128位的IPv6,其IP地址数量最高可达3.402823669×1038个,届时每个人家居中的每件电器,每件对象,甚至地球上每一粒沙子都可以拥有自己的IP地址。
网络编程涉及的概念确实非常抽象,因为它看不见摸不着。但是,如果你把整个计算机网络想象成"全球快递物流系统"或者"公司寄信系统",这些原本晦涩的专业名词瞬间就会变得非常接地气!
我用最通俗的"生活类比"帮你把这段话从头到尾翻译一遍。
一、 网络编程三要素(快递怎么寄?)
两台电脑想要聊天(传数据),就像你要给远方的朋友寄一个包裹,必须具备三个条件:
- IP地址(收件人地址)
- 官方解释:网络中计算机的唯一标识。
- 人话版:这就是**"小区的具体地址"**(比如:北京市朝阳区长安街1号)。送快递的(路由器)看着这个地址,就能把包裹(数据)送到你家楼下(你的电脑)。
- 端口(门牌号/收件人姓名)
- 官方解释:标识进程的逻辑地址。
- 人话版 :光送到小区不行啊,你的电脑里同时运行着QQ、微信、浏览器。包裹到底给谁?端口就是"门牌号"。比如 80 端口是给浏览器的,4000 端口是给 QQ 的。快递员按门牌号把包裹精确交给对应的软件(进程)。
- 协议(快递包装与填写规范)
- 官方解释:定义通信规则。
- 人话版 :寄顺丰要有顺丰的单子,寄邮政有邮政的格式。协议就是大家商量好的"规则"。比如包裹必须方方正正,第一行写发件人,第二行写收件人。如果大家不遵守同一个规则,拿到包裹也拆不开、看不懂。
二、 TCP/IP 协议族与分层模型(流水线打包工作)
- TCP/IP 协议族
- 目前互联网最火、最通用的"快递打包规则"。其实它包含了一大堆规则(几百个),但因为 TCP 和 IP 这两个规则最出名,所以整个家族就叫 TCP/IP。
- 分层网络模型(五层/七层)
- 为什么网络要"分层"?因为寄包裹的过程太复杂,必须流水线作业!
- 想象一下你在公司寄一封重要的商业信件(发送数据):
- 应用层(老板):你写好信,决定要发给马云。(决定发什么内容)
- 传输层(秘书):把信装进信封,贴上挂号信标签,确保这封信必须要马云亲自签收,丢了还要重新寄。(TCP协议就在这层,负责包裹安全到达)。
- 网络层(快递分拣中心):根据地址,规划好是走空运还是陆运,规划最优路线。(IP协议就在这层,负责指路)。
- 数据链路层 & 物理层(卡车司机与马路):真正开着车,通过光缆、网线、WiFi 把包裹运过去。(纯纯的物理搬运)。
- 分层的好处:老板不用管卡车是怎么开的,卡车司机也不用管信里写了什么。各司其职!
三、 IP 地址大解剖(小区地址怎么看?)
1. IP地址是什么?
- 比如
192.168.1.10,就像一串电话号码或者身份证号,连上网的设备都有一个。
2. 子网掩码 (Subnet Mask)
- 官方解释:区分网络号和主机号。
- 人话版 :这就是用来区分"区号"和"座机号"的!
- 假设有个电话号码
010-8888888。我们人眼一看就知道010是北京(网络号/小区名),8888888是具体的电话(主机号/哪一户)。 - 但电脑比较傻,它看到
192.168.1.10不知道哪部分是小区名,哪部分是门牌号。 - 这时候就需要子网掩码 (比如
255.255.255.0)。它就像一把尺子,扣在 IP 地址上,告诉电脑:"前三个数字192.168.1是小区名,最后一个数字10是这个小区的第 10 户人家!"
- 假设有个电话号码
3. IPv4 地址的分类 (A/B/C类)
- 人话版 :根据小区的规模,划分了不同类别的 IP 地址。
- A类:超级大省。省份很少(网络号少),但每个省里能容纳的人口极其庞大(主机号多,能容纳1600万台电脑)。一般给国家级、超大型跨国公司用。
- C类:小村庄。村庄的名字极其多(网络号多),但每个村里只能住 254 户人家(主机号少)。这是我们平时最常接触的。
四、 公网、私网 与 NAT转换(绝妙的发明!)
1. 为什么要有公网和私网?
- 背景:IPv4 一共只有 42 亿个地址。但这世界上有几百亿部手机、电脑、冰箱要联网,**IP 地址早就被抢光了!**怎么办?
- 人话版 :IP 地址就像**"直线电话号码"。号码不够分了,于是聪明人发明了 "公司内线(分机号)"**!
- 公网 IP:全球唯一的直线电话号码(比如:李彦宏的私人号码,全球唯一,谁都能打通)。要在全球上网,必须得有公网 IP。
- 私网 IP :公司内部的分机号(比如
192.168.x.x就是最经典的内线号码)。
你家路由器给你手机分配的192.168.1.5就是个分机号。隔壁老王家的路由器,也会给他手机分配192.168.1.5。
因为这是私有内线,只能在你家这个屋子里互相通信,一出门这个号码就没用了,所以大家都可以重复用,这就省下了几百亿的 IP 地址!
2. NAT 转换技术(公司前台小姐姐)
- 问题来了 :你的手机拿着内线号码(私网 IP
192.168.1.5),想要访问远方的百度服务器,百度怎么给你回信?百度根本找不到你的内线号码啊! - NAT 的作用 :你家的路由器 就是兼职了 NAT 的功能。它相当于公司的**"前台小姐姐"**。
- 你(内线801) 想点外卖,你不能直接打给外卖店。你打电话给前台(路由器)。
- 前台(路由器) 拥有一个全球唯一的公网 IP (假设是
114.114.114.114)。前台用这个公网 IP 打电话给外卖店:"喂,给我送份烤肉"。 - 外卖店把烤肉送到了前台(只认公网 IP)。
- 前台小姐姐拿着记事本查看:"哦,刚才这份烤肉是内线 801 点的"。于是她把烤肉悄悄转交给了你。
- 这就是 NAT(网络地址转换)! 百度服务器永远不知道你手机的私网 IP,它只跟你的路由器(公网 IP)打交道。路由器帮你收发快递,然后悄悄分发给家里连着 WiFi 的各部手机。
五、 IPv4 与 IPv6(旧号码薄与新号码薄)
- IPv4 :旧版电话本。格式是
192.168.1.1,最多组合出 42 亿个。虽然用上了 NAT 这种"分机号"技术续命,但随着物联网时代到来,连个灯泡都要联网,还是不够用。2011年已经彻底分配完了。 - IPv6 :新版超级电话本。格式是
2001:db8:85a3::8a2e,它太长了,能组合出的数量是一个天文数字(3.4 乘以 10 的 38 次方)。 - 有多大? 科学家打了个比方:"地球上的每一粒沙子,都能分到一个 IPv6 地址"。有了 IPv6,我们就再也不需要复杂的 NAT 路由器"前台"了,每个设备都能拥有全球唯一的"直线电话号码",直接和全世界对话!
端口
什么是端口
这里的端口指的是逻辑端口,即TCP/IP协议中的端口。端口用于进程(应用软件)在同一设备或不同设备之间通信。每个端口有一个对应的端口号。端口号有65536个。
可以使用netstat -ano查看端口信息。
端口号的分配
1)公认端口
0 ~ 1023,它们紧密绑定于一些服务。通常这些端口的通讯明确表明了某种服务的协议。端口号0是被保留的,不可使用。1~1023系统保留,只能由root用户使用。
2)动态端口
1024~65536,之所以称为动态端口,是因为它一般不固定分配某种服务,而是动态分配。当一个系统进程或应用程序进程需要网络通信时,它向主机申请一个端口,主机从可用的端口号中分配一个供它使用。当这个进程关闭时,同时也就释放了所占用的端口号。
3)常见端口
| 端口 | 服务 |
|---|---|
| 0/TCP,UDP | 保留端口,不使用 |
| 7/TCP,UDP | Echo(回显)协议 |
| 21/TCP,UDP | FTP文件传输协议 |
| 22/TCP,UDP | SSH安全远程登录协议 |
| 23/TCP,UDP | Telnet终端仿真协议 |
| 25/TCP,UDP | SMTP简单邮件传输协议 |
| 53/TCP,UDP | DNS域名服务系统 |
| 80/TCP,UDP | HTTP超文本传输协议 |
| 110/TCP | POP3邮局协议第3版 |
| 137/TCP,UDP | NetBIOS名称服务 |
| 138/TCP,UDP | NetBIOS数据报文服务 |
| 139/TCP,UDP | NetBIOS会话服务 |
| 143/TCP,UDP | IMAP用于检索电子邮件 |
| 445/TCP | Microsoft-DS (Active Directory、Windows 共享、震荡波蠕虫、Agobot、Zobotworm) |
| 445/UDP | Microsoft-DS服务器消息块(SMB)文件共享 |
| 666/UDP | 毁灭战士,电脑平台上的一系列第一人称射击游戏。 |
| 873/TCP | Rsync文件同步协议 |
| 902 | VMware服务器控制台 |
| 3306/TCP,UDP | MySQL数据库系统 |
| 3389/TCP | 远程桌面协议(RDP) |
socket套接字
什么是socket
socket(套接字)是同一或不同电脑的进程(任务、应用软件)间通信的一个工具,进程之间想要进行网络通信需要基于socket。只要与网络相关的应用程序或者软件都使用到了socket。
socket的使用
Python中提供了socket模块用于创建套接字。
python
import socket
# AF_INET 用于 Internet 进程间通信;SOCK_STREAM 流式套接字,TCP
tcp_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
# AF_INET 用于 Internet 进程间通信;SOCK_DGRAM 数据报套接字,UDP
udp_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
UDP
用户数据报协议(UDP:User Datagram Protocol)是一个简单的面向数据报的通信协议。UDP只提供数据的不可靠传递,它一旦把应用程序发给网络层的数据发送出去,就不保留数据备份。
UDP避免了协议栈中执行错误检查和纠正处理的开销,适用于对时间有较高要求的应用程序,因为某些场景下丢弃数据包比等待或重传导致延迟更可取。流媒体、在线游戏流量通常使用UDP传输。

案例
UDP服务端:
python
"""udp服务端"""
import socket
# 创建udp套接字
udp_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
# 绑定ip和端口
udp_socket.bind(("127.0.0.1", 8080))
while True:
# 接收数据
recv_data, client_addr = udp_socket.recvfrom(1024)
client_ip = client_addr[0]
client_port = client_addr[1]
print(f"{client_ip}:{client_port}>> {recv_data.decode("utf-8")}")
# 发送数据
udp_socket.sendto("你好".encode("utf-8"), client_addr)
# 关闭套接字
udp_socket.close()
UDP客户端:
python
"""udp客户端"""
import socket
# 创建udp套接字
udp_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
while True:
try:
# 发送数据
server_ip = "127.0.0.1"
server_port = 8080
udp_socket.sendto(input(f"{server_ip}:{server_port}<< ").encode("utf-8"), (server_ip, server_port))
# 接收数据
recv_data, client_addr = udp_socket.recvfrom(1024)
client_ip = client_addr[0]
client_port = client_addr[1]
print(f"{client_ip}:{client_port}>> {recv_data.decode("utf-8")}")
except KeyboardInterrupt:
break
# 关闭套接字
udp_socket.close()
TCP
什么是TCP
传输控制协议(TCP:Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议。TCP协议的运行可划分为三个阶段:连接建立、数据传送和连接终止。
很多重要的机制保证了TCP的可靠性和强壮性,包括:
使用序号,对收到的TCP报文段进行排序以及检测重复的数据。
使用校验和检测报文段的错误,即无错传输。
使用确认和计时器来检测和纠正丢包或延时。
流控制。
拥塞控制。
丢失包的重传。
TCP编程
1)TCP编程流程

2)案例
python
"""tcp服务端"""
import socket
# 创建tcp套接字
tcp_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
# 绑定ip和端口
tcp_socket.bind(("127.0.0.1", 8080))
# 设置监听
tcp_socket.listen(2)
# 等待客户端连接
client_socket, client_addr = tcp_socket.accept()
while True:
# 接收数据
recv_data = client_socket.recv(1024)
print(f"{client_addr[0]}:{client_addr[1]}>> {recv_data.decode('utf-8')}")
# 发送数据
client_socket.send("你好".encode("utf-8"))
# 关闭套接字
tcp_socket.close()
TCP客户端:
python
"""tcp客户端"""
import socket
# 创建tcp套接字
tcp_socket = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
# 连接服务器
server_ip = "127.0.0.1"
server_port = 8080
tcp_socket.connect((server_ip, server_port))
while True:
try:
# 发送数据
tcp_socket.send(input(f"{server_ip}:{server_port}<< ").encode("utf-8"))
# 接收数据
recv_data = tcp_socket.recv(1024)
print(f"{server_ip}:{server_port}>> {recv_data.decode("utf-8")}")
except KeyboardInterrupt:
break
# 关闭套接字
tcp_socket.close()
UDP 是什么
UDP 是一种传输协议,特点是:
无连接
速度快
不保证可靠
不保证顺序
面向报文的
不重传
所以它适合:
直播
语音
游戏实时数据
因为这些场景更怕"卡",而不是更怕"丢一点点数据"。
- TCP 是什么
TCP 是另一种传输协议,特点是:
面向连接
可靠
有确认机制
有重传机制
保证顺序
面向字节流
所以适合:
网页
登录
下单
文件传输
聊天记录同步
那 TCP 怎么解决消息边界问题?
既然 TCP 本身不管"每条消息到哪结束",那就只能由应用层协议自己规定。
常见做法有三种:
方法1:固定长度
比如规定:
每条消息固定 1024 字节
那接收方每次就按 1024 字节读。
缺点:浪费空间,不灵活。
方法2:特殊分隔符
比如规定:
每条消息结尾加 \n
发送:
text
hello\n
world\n
接收方看到 \n 就知道一条消息结束了。
这像"按行读取"。
缺点:如果正文里也可能出现这个分隔符,就得转义,比较麻烦。
方法3:长度前缀
这是最常用、最正规的方法。
比如先发消息长度,再发消息内容:
text
长度:5\]\[hello
长度:5\]\[world
接收方先读长度,再按长度读内容。
HTTP
HTTP(超文本传输协议)是一种用于分布式、协作式和超媒体信息系统的应用层协议。是万维网的数据通信的基础。设计HTTP最初的目的是为了提供一种发布和接收HTML页面的方法。通过HTTP或者HTTPS协议请求的资源由统一资源标识符(Uniform Resource Identifiers,URI)来标识。
HTTP 上的一个典型工作流程是客户端计算机向服务器发出请求,然后服务器发送响应消息。通常,由HTTP客户端发起一个请求,建立一个到服务器指定端口(默认是80端口)的TCP连接。HTTP服务器则在那个端口监听客户端的请求。一旦收到请求,服务器会向客户端返回一个状态,比如"HTTP/1.1 200 OK",以及返回的内容,如请求的文件、错误消息、或者其它信息
HTTP消息结构
1)客户端请求消息
客户端发送一个 HTTP 请求到服务器的请求消息包括以下格式:请求行、请求头、空行和请求体四个部分组成。

(1)请求行
请求方法:如 GET、POST、PUT、DELETE等,指定要执行的操作。
请求 URI:请求的资源路径,通常包括主机名、端口号(如果非默认)、路径和查询字符串。
协议版本:如 HTTP/1.1 或 HTTP/2。
请求行的格式示例:GET /index.html HTTP/1.1
(2)请求头
包含了客户端环境信息、请求体的大小(如果有)、客户端支持的压缩类型等。
常见的请求头包括Host、User-Agent、Accept、Accept-Encoding、Content-Length等。
(3)空行
请求头和请求体之间的分隔符,表示请求头的结束。
(4)请求体
在某些类型的HTTP请求(如 POST 和 PUT)中,请求体包含要发送给服务器的数据。

2)服务端响应消息
HTTP 响应由四个部分组成,分别是:状态行、消息报头、空行和响应正文。

(1)状态行
HTTP 版本:与请求消息中的版本相匹配。
状态码:三位数,表示请求的处理结果,如 200 表示成功,404 表示未找到资源。
状态信息:状态码的简短描述。
状态行的格式示例:HTTP/1.1 200 OK
(2)响应头
包含了服务器环境信息、响应体的大小、服务器支持的压缩类型等。
常见的响应头包括Content-Type、Content-Length、Server、Set-Cookie等。
(3)空行
响应头和响应体之间的分隔符,表示响应头的结束。
(4)响应体
包含服务器返回的数据,如请求的网页内容、图片、JSON数据等。

HTTP请求方法
HTTP/1.1 协议中共定义了八种方法来以不同方式操作指定的资源,HTTP 服务器至少应该实现 GET 和 HEAD 方法,其他方法都是可选的。
1)GET
向指定的资源发出"显示"请求。使用 GET 方法应该只用在读取资料,而不应当被用于产生"副作用"的操作中,例如在网络应用程序中。其中一个原因是 GET 可能会被网络爬虫等随意访问。
2)HEAD
与 GET 方法一样,都是向服务器发出指定资源的请求。只不过服务器将不传回资源的本文部分。它的好处在于,使用这个方法可以在不必传输全部内容的情况下,就可以获取其中"关于该资源的元信息(或称元数据)"
3)POST
向指定资源提交数据,请求服务器进行处理(例如提交表单或者上传文件)。数据被包含在请求本文中。这个请求可能会建立新的资源或修改现有资源,或二者皆有。每次提交,表单的数据被浏览器用编码到 HTTP 请求的 body 里。
4)PUT
向指定资源位置上传其最新内容。
5)DELETE
请求服务器删除 Request-URI 所标识的资源。
6)TRACE
回显服务器收到的请求,主要用于测试或诊断。
7)OPTIONS
这个方法可使服务器传回该资源所支持的所有 HTTP 请求方法。用"*"来代替资源名称,向 Web 服务器发送 OPTIONS 请求,可以测试服务器功能是否正常运作。
8)CONNECT
HTTP/1.1协议中预留给能够将连接改为隧道方式的代理服务器。通常用于SSL加密服务器的链接(经由非加密的HTTP代理服务器)。
HTTP状态码
HTTP状态码是服务器对客户端请求的响应,状态码分为五类:
1)1xx(信息状态码)
表示接收的请求正在处理。例如:
100:继续。客户端应继续其请求。
101:切换协议。服务器根据客户端的请求切换协议。只能切换到更高级的协议。
2)2xx(成功状态码)
表示请求正常处理完毕。例如:
200:请求成功。一般用于 GET 与 POST 请求。
202:已接受。已经接受请求,但未处理完成。
3)3xx(重定向状态码)
需要后续操作才能完成这一请求。例如:
300:多种选择。请求的资源可包括多个位置,相应可返回一个资源特征与地址的列表用于用户终端(例如:浏览器)选择。
301:永久移动。请求的资源已被永久的移动到新 URI,返回信息会包括新的 URI,浏览器会自动定向到新 URI。今后任何新的请求都应使用新的 URI代替。
302:临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有 URI。
304:未修改。所请求的资源未修改,服务器返回此状态码时,不会返回任何资源。客户端通常会缓存访问过的资源,通过提供一个头信息指出客户端希望只返回在指定日期之后修改的资源。
305:使用代理。所请求的资源必须通过代理访问。
4)4xx(客户端错误状态码)
表示请求包含语法错误或无法完成。例如:
400:客户端请求的语法错误,服务器无法理解。
403:服务器理解请求客户端的请求,但是拒绝执行此请求。
404:服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面。
405:客户端请求中的方法被禁止。
5)5xx(服务器错误状态码)
服务器在处理请求的过程中发生了错误。例如:
500:服务器内部错误,无法完成请求。
501:服务器不支持请求的功能,无法完成请求。
502:作为网关或者代理工作的服务器尝试执行请求时,从远程服务器接收到了一个无效的响应。
案例:发送HTTP请求以及获取响应数据
python
import requests
# 一言网的 API 地址
url = 'https://v1.hitokoto.cn/'
# 海外:url = 'https://international.v1.hitokoto.cn'
# 请求参数,指定返回中文内容,这里使用默认的所有类型
params = {
'c': 'a', # 可以根据需要修改类型,a 代表动画,b 代表漫画等
'encode': 'json'
}
try:
print(f"正在发送 GET 请求到: {url},参数: {params}")
response = requests.get(url, params=params)
status_code = response.status_code
if status_code == 200:
print(f"请求成功!状态码: {status_code}")
data = response.json()
hitokoto = data['hitokoto']
from_who = data['from_who'] if data['from_who'] else '未知'
print(f"随机名言: {hitokoto} - {from_who}")
elif status_code == 404:
print(f"请求的资源未找到!状态码: {status_code}")
elif status_code == 500:
print(f"服务器内部错误!状态码: {status_code}")
else:
print(f"发生未知错误,状态码: {status_code}")
except requests.RequestException as e:
print(f"请求过程中出现错误: {e}")
既然有 Socket,为什么还要用 Request 和 Response?
这个问题直指网络分层的本质。
简单来说:Socket 是"搬运工",Request/Response(HTTP)是"翻译官"和"包装盒"。
1. 为什么不用 Socket 直接写业务?
如果你用 Socket 直接去访问百度网页,你的代码要这么写:
python
import socket
# 1. 建立 Socket 连接
s = socket.socket()
s.connect(("www.baidu.com", 80))
# 2. 你必须手动按照 HTTP 协议的格式,拼接出一段又臭又长的字符串
req_data = "GET / HTTP/1.1\r\nHost: www.baidu.com\r\nConnection: close\r\n\r\n"
s.send(req_data.encode("utf-8"))
# 3. 接收数据
resp_data = b""
while True:
chunk = s.recv(1024)
if not chunk:
break
resp_data += chunk
# 4. 最痛苦的来了:你要自己从这堆 raw 数据中,把头部剔除,把真正的 HTML 网页挖出来,还要自己处理各种由于 TCP "没有消息边界"导致的问题!
print(resp_data.decode("utf-8"))
发现了吗?
Socket 只管传递 0 和 1(字节流)。它不懂 什么是网页,不懂 什么是图片,不懂什么是乱码。如果你只用 Socket,你每天都要在"拼字符串"和"切字符串"中痛苦挣扎。
2. Request 和 Response 做了什么?
当你使用 requests 库(比如 requests.get())时,你用的是**应用层(HTTP)**的代码。
python
import requests
# 你只需要这一句话
response = requests.get("http://www.baidu.com")
# 它自动帮你把网页内容拿出来了
print(response.text)
requests 库在底层悄悄帮你做了什么?
- 它底层其实也是在使用 Socket 去连接百度。
- 它自动帮你拼好了
GET / HTTP/1.1...这种请求格式(这就是 Request)。 - 它通过 Socket 收到了自来水一样的一大堆杂乱的字节流。
- 最关键的: 它懂 HTTP 协议!它自动用
\r\n\r\n(边界)把服务器的返回拆开,把状态码(200)、响应头、真正的网页内容提取出来,打包成一个漂亮的Response对象交给你。
3. 总结它俩的区别
| 对比项 | Socket (底层) | Request/Response (如 HTTP) |
|---|---|---|
| 角色 | 卡车司机、搬运工 | 快递公司的打包员、客服 |
| 处理的数据 | 原始的字节流(0和1的自来水) | 有明确意义的数据(网页、JSON、图片) |
| 消息边界 | TCP Socket 没有边界(会粘包) | HTTP 帮你处理好了边界(利用 Content-Length 等) |
| 你的感受 | 极度自由,但也极度繁琐,什么都要自己写 | 非常爽,只管发请求和拿结果,底层细节全屏蔽了 |
核心结论:
- TCP Socket 没有消息边界,它就像连绵不断的自来水,你必须自己规定怎么"断句",否则会粘包。
- Request 和 Response 是建立在 Socket 之上的高级封装(HTTP协议) 。之所以用它们,是因为前辈大神们已经写好了代码,帮你把 Socket 收到的"自来水"自动断句、自动解析成了你人类能看懂的数据!底层其实还是 Socket!
案例:通过Starlette 构建web接口
Starlette 是一个轻量级的 Python 异步 Web 框架,专为构建高性能的异步应用程序而设计,它具有简洁、灵活的特点,并且可以与其他库(如 FastAPI 就是基于 Starlette 构建的)很好地集成。我们可以结合 Starlette 构建一个Web 服务,将上面获取随机名言的功能封装成一个 API 接口,这样可以带来一些优势,例如实现更灵活的交互、支持多用户访问。
Uvicorn :它是一个基于 Python 的 ASGI(Asynchronous Server Gateway Interface)服务器。ASGI 是 Python 中用于异步 Web 应用的标准接口,Uvicorn 能够高效地处理并发请求,基于 uvloop(一个快速的异步事件循环)和 httptools(一个快速的 HTTP 解析器)构建,为 Python 异步 Web 应用提供了高性能的运行环境。
Starlette :是一个轻量级的 Python 异步 Web 框架,它遵循 ASGI 标准,专注于提供简洁、灵活的 API 来构建 Web 应用和服务。Starlette 提供了路由、中间件、请求和响应处理等核心功能,允许开发者快速搭建 Web 应用的逻辑。
协作方式:Uvicorn 为 Starlette 应用提供了运行的基础环境。当你使用 Starlette 编写好一个 Web 应用后,无法直接运行,需要借助像 Uvicorn 这样的 ASGI 服务器来启动和部署
python
from starlette.applications import Starlette
from starlette.responses import JSONResponse
from starlette.routing import Route
import requests
import uvicorn
# 一言网的 API 地址
HITOKOTO_URL = 'https://v1.hitokoto.cn/'
# 定义异步函数来获取随机名言
async def get_hitokoto():
try:
# 请求参数,指定返回中文内容,这里使用默认的所有类型
params = {
'c': 'a', # 可以根据需要修改类型,a 代表动画,b 代表漫画等
'encode': 'json'
}
response = requests.get(HITOKOTO_URL, params=params)
status_code = response.status_code
if status_code == 200:
data = response.json()
hitokoto = data['hitokoto']
from_who = data['from_who'] if data['from_who'] else '未知'
return {'hitokoto': hitokoto, 'from_who': from_who}
else:
return {'error': f'请求一言网 API 失败,状态码: {status_code}'}
except requests.RequestException as e:
return {'error': f'请求过程中出现错误: {str(e)}'}
# 定义处理根路径请求的异步函数
async def homepage(request):
result = await get_hitokoto()
return JSONResponse(result)
# 创建 Starlette 应用实例
app = Starlette(debug=True, routes=[
Route('/', homepage),
])
if __name__ == "__main__":
# 使用 uvicorn 运行应用
uvicorn.run(app, host='0.0.0.0', port=8000)
3)代码说明
get_hitokoto 函数
该函数负责发送 HTTP 请求到一言网的 API,获取随机名言。处理请求过程中可能出现的错误,包括请求失败和网络异常。返回一个包含名言和来源信息的字典,或者包含错误信息的字典。
homepage 函数
作为 Web 服务的根路径处理函数。调用 get_hitokoto 函数获取随机名言,并将结果封装成 JSON 响应返回给客户端。
Starlette 应用
创建 Starlette 应用实例,并定义路由规则,将根路径 / 映射到 homepage 处理函数。使用 uvicorn 作为ASGI服务器运行应用。
通过 Starlette 构建 Web 服务,将获取随机名言的功能封装成 API 接口,方便其他应用程序调用。虽然 requests 库是同步的,但 Starlette 本身支持异步处理。
一、整体总结(把这章串起来)
你这篇网络编程内容的主线非常清楚,可以浓缩成一条链路:
IP(找机器) → 端口(找进程/应用) → 协议(定规则) → socket(代码里收发数据的工具) → UDP/TCP(传输层协议差异) → HTTP(应用层协议,基于 TCP) → requests/Starlette(对 HTTP 的高级封装与 Web 服务化)
进一步分块总结:
-
网络三要素:
- IP:定位主机
- 端口:定位主机上的应用程序
- 协议:规定通信格式与流程(如 TCP/UDP/HTTP)
-
TCP/IP 分层模型:分层是为了"分工",应用层不需要关心底层怎么传输比特流。
-
IP 相关:子网掩码/CIDR 用于划分网络部分与主机部分;公网/私网 + NAT 解释了"为什么你家很多设备只靠一个公网 IP 也能上网"。
-
端口:端口号用于区分同一台机器上的不同网络服务。
-
socket :是编程层面创建网络端点的工具(Python 的
socket模块)。 -
UDP/TCP:
- UDP:无连接、面向报文、有消息边界、不可靠但快
- TCP:面向连接、可靠、有序、面向字节流(无消息边界)
-
HTTP :一种应用层 request/response 协议,常运行在 TCP 之上;
requests是 HTTP 客户端的高级封装。 -
Starlette + Uvicorn:把"脚本调用外部 API"升级为"Web 接口",方便多用户/多系统调用。Uvicorn 提供 ASGI 运行环境,Starlette 提供路由与请求响应抽象。
二、易错点/纠错清单(建议你在文章里标出来)
1)端口范围与分类的易错点
-
端口号范围 :是 0 ~ 65535 (共 65536 个)。
你文中"动态端口 1024~65536"应改为 1024~65535。
-
端口分类更严谨写法(可选补充,不写也行,但别写错范围):
- 0~1023:well-known(知名端口)
- 1024~49151:registered(注册端口)
- 49152~65535:dynamic/ephemeral(动态/临时端口,客户端常用)
-
"1~1023 只有 root 才能用" :这是 Linux/Unix 的典型规则(特权端口),Windows 不完全按这个说法。
-
TCP 和 UDP 端口是两套空间:同一个数字端口在 TCP 和 UDP 下可以分别被占用(例如 TCP 53 和 UDP 53 是两个不同的 socket)。
-
端口表里很多"TCP,UDP 都用"容易误导:
例如 HTTP 基本是 TCP/80,SSH 基本是 TCP/22;DNS 才是常见的 TCP/UDP 都可能用。建议表格里别轻易写成"TCP,UDP 都是",可以写成"主要使用"。
2)IP "唯一标识"这句话容易被误解
- "IP 是每台计算机唯一标识"作为入门说法可以,但更准确是:
- IP 是网络接口地址,可能变化(DHCP 动态分配)
- 私网环境里不同局域网可重复使用同一段私网 IP(NAT 让它们共用公网出口)
- "公网 IP 才是全球可路由的唯一地址"
3)127.0.0.0/8 的表述
127.0.0.1是最常用 loopback(本机回环)。- 更严格地说:整个 127.0.0.0/8 都是回环地址段 ,但一般只用
127.0.0.1讲解就够了。
4)socket 示例代码的常见坑(你文中有几处是真会报错/会卡死的)
(1)f-string 引号嵌套导致语法错误
你 UDP/TCP 打印处有这种写法:
python
print(f"{client_ip}:{client_port}>> {recv_data.decode("utf-8")}")
外层和内层都用双引号会直接语法报错。改成:
python
print(f"{client_ip}:{client_port}>> {recv_data.decode('utf-8')}")
(2)无限循环 + close 写在后面 = 永远执行不到
while True: 后面的 udp_socket.close() 基本到不了。建议用:
try/except KeyboardInterrupt- 或
try/finally
(3)127.0.0.1 只能本机访问
你服务端 bind 到 127.0.0.1,局域网其它机器访问不了。若想让同网段访问,改为:
python
bind(("0.0.0.0", 8080))
(4)TCP 服务端没处理"对端关闭连接"
TCP 中 recv() 返回 b'' 表示对方断开,应写:
python
data = client_socket.recv(1024)
if not data:
break
(5)TCP 的 send() 不保证一次发完
更推荐 sendall():
python
client_socket.sendall(b"你好")
(6)TCP 服务端只 accept 一次,只能服务一个客户端
你现在的 TCP 服务端只接收一个连接,想支持多个客户端需要循环 accept() 并为每个连接开线程/进程/协程。
5)TCP "消息边界"是重点易错点(概念型大坑)
- 你已经写到了:TCP 是字节流、无消息边界。这里建议加一句"结论式提醒":
- TCP 保证有序可靠,但不保证一次 recv 就是一条完整消息
- 所以要在应用层做定长/分隔符/长度前缀协议来"切包"
(你文中这块思路是对的。)
6)requests 示例代码的易错点
你这段:
python
except requests.RequestException as e:
print(f"请求过程中出现错误: {e}")
print 没缩进,会报 IndentationError。应缩进。
另外建议补两个实践点:
-
加
timeout,避免网络卡死一直等:pythonrequests.get(url, params=params, timeout=5) -
处理异常时可以用
response.raise_for_status()简化状态码判断。
7)"Socket vs Request/Response(HTTP)"容易混淆的点
你解释得总体正确,但建议强调一句避免误解:
- socket 是传输通道(端点抽象)
- HTTP request/response 是建立在 TCP 之上的应用层协议格式
requests是"帮你自动拼 HTTP 报文 + 解析响应"的库,底层仍然用 socket/TCP。
8)Starlette 示例:最大易错点是"伪异步"(性能坑)
python
async def get_hitokoto():
response = requests.get(...)
这是最常见错误之一:
requests.get()是同步阻塞的- 放在
async def里会阻塞事件循环 - 并发一多,请求会排队,吞吐下降
两种修正方案(写在文章里会很加分):
方案 A(推荐):改用 httpx 异步客户端
httpx.AsyncClient+await client.get(...)
方案 B(折中):requests 放线程池
await run_in_threadpool(requests.get, ...)
另外建议补充:
-
开发运行推荐:
bashuvicorn main:app --reload --host 0.0.0.0 --port 8000 -
debug=True只用于开发环境,不用于生产。
法则一:await 只服务于"异步函数"。如果你用的是 requests 这种同步库,它没有 await 的能力。
法则二:当你看到代码里有 async 时,里面涉及到的所有网络操作(数据库查询、HTTP 请求、文件读取),都要找对应的"异步版本"。
requests -> 换成 httpx 或 aiohttp
time.sleep -> 换成 asyncio.sleep
pymysql -> 换成 aiomysql
记住一句话:async 只是声明,await 是开关,但底层的"水管"(库)必须本身就是异步的,否则你只是在对着一根死水管谈异步。