python 网络编程之http、tcp/ip
报文格式地图——重庆网管博客 (023wg.com)
一、软件开发架构
1、B / S :
browser 浏览器 --------〉 server
2、C / S:
client 客户端 --------〉 server
二、OSI 七层协议
1、OSI的七层协议
-
物理层(Physical Layer):物理层负责传输比特流(0和1)的物理介质,例如电缆、光纤和无线电频谱。它处理与物理设备之间的电压、电流和光强度等相关的细节。
-
数据链路层(Data Link Layer):数据存在形式为帧,数据链路层提供了一个可靠的点对点数据传输的通道,通过帧的方式将数据分成更小的单元进行传输。它还负责错误检测、纠正和流控制,以确保数据在物理介质上的正确传输。
-
网络层(Network Layer):数据存在形式为包,网络层负责将数据包从源主机传输到目标主机,处理分组的路由和转发。它使用IP(Internet Protocol)地址来确定数据包的目标位置,并在不同的网络之间进行跨网络的路由选择。
-
传输层(Transport Layer):数据存在形式为段,传输层提供端到端的数据传输服务,确保数据在源和目标之间可靠、顺序地传输。它处理数据的分段、重组、流量控制和错误恢复,同时提供不同的传输协议(如TCP和UDP)供应用程序使用。
-
会话层(Session Layer):会话层管理应用程序之间的通信会话,建立、维护和终止会话连接。它负责会话的同步、对话控制以及数据的检查点和恢复。
-
表示层(Presentation Layer):表示层处理数据的格式转换、加密和解密,以确保不同系统上的应用程序能够正确解释数据。它处理数据的压缩、编码和加密,以及数据格式的转换。
-
应用层(Application Layer):应用层是最高级别的层次,它提供了用户与网络服务之间的接口。应用层包括各种网络应用程序,如电子邮件、文件传输协议(FTP)、域名系统(DNS)等。
这些层次按照从底层到顶层的顺序,共同构成了OSI模型,提供了一个结构化的框架来描述和理解计算机网络通信的不同方面。值得注意的是,实际的网络协议栈可能会结合多个层次的功能,合并成更少的层次,如TCP/IP模型将物理层和数据链路层合并为网络接口层。
2、数据链路层:
该层中的数据称之为是一个数据帧,数据帧有两部分组成:head(报头)+data(真实数据)
head部分:固定18个字节
○ 发送者/源地址,6个字节
○ 接收者/目标地址,6个字节
○ 数据类型,6个字节
data:
最短46字节
# 数据报的具体内容:head长度+data长度=最短64字节
该层使用的是以太网协议,以太网协议规定,任何接入互联网的计算机都应该有一个网卡,默认的就叫以太网
Mac地址:每一个网卡都要有一个Mac地址,每块网卡出厂时都被烧制上一个世界唯一的Mac地址,长度为48位2进制,通常由12位16进制数表示
# Mac地址可以确定局域网中唯一一台计算机
三、HTTP协议
1、http 超文本传输协议
HTTP(Hypertext Transfer Protocol)是一种用于在Web上传输数据的协议,规定了服务端和客户端之间的数据传输格式及其标准。
HTTP协议是一种应用层协议,它是基于TCP/IP协议开发的。HTTP在TCP之上,而TCP在IP之上,形成了一个层次化的协议栈,这种组合允许HTTP协议通过互联网进行通信。HTTP协议负责定义客户端和服务器之间请求和响应的格式和规则,而TCP和IP协议负责确保数据包的可靠传输和路由。
浏览器客户端访问
服务端发来的数据要符合http协议要求
2、http协议的五大特性
请求-响应模型(Request-Response)
HTTP协议采用客户端-服务器模型,客户端发送请求到服务器,服务器处理请求并返回响应给客户端。
短连接(Connectionless)
HTTP协议是一种无连接的协议,即每个请求和响应都是独立的,服务器在完成客户端请求的响应后,立即断开连接。这样的特性使得服务器能够处理大量的请求,而不需要长时间保持连接状态,从而节省服务器资源。
无状态(Stateless)
HTTP协议是无状态的,即服务器不会保存之前请求的任何信息。每个请求都是独立的,服务器不能识别请求是否来自同一个客户端或之前的请求历史。这使得服务器设计简单,并且能够更好地扩展,但同时也要求在需要保持状态的应用场景下使用额外的机制,如Cookie或Session。
支持多媒体(Media-independent)
HTTP协议不仅仅支持传输HTML文本,还可以传输各种类型的数据,包括图片、音频、视频、文件等。这使得HTTP成为一个通用的应用层协议,可以用于传输各种不同类型的数据。
长连接( Keep-Alive)连接
-
减少连接建立和断开的开销:TCP连接的建立和断开会消耗一定的时间和资源,通过使用长连接,可以减少这些开销,从而提高性能和响应速度。
-
减少服务器负担:长连接使得服务器可以处理多个请求,而不必为每个请求建立新的连接。这在高并发的情况下特别有用,可以减轻服务器的负担。
-
并行传输:由于长连接可以在同一时间内传输多个请求和响应,客户端可以并行发送多个请求,而不必等待每个请求的响应。这有助于提高页面加载速度和资源获取效率。
虽然长连接可以提供更高的性能和效率,但它也需要服务器维护打开的连接,并在适当的时候关闭空闲连接,以防止资源浪费和连接过多导致的问题。为了解决长连接中的潜在问题,HTTP/1.1引入了"Connection: keep-alive"头部来明确指示使用长连接。在HTTP/1.1中,默认情况下,连接都是持久的,除非显式地指定"Connection: close"头部,以关闭连接。
值得注意的是,HTTP/2及更高版本使用了全新的多路复用技术,不再依赖于长连接来实现并行传输,因此在HTTP/2中,长连接的意义不同于HTTP/1.1时代。
3、http 协议的请求数据格式
常用的版本号就是http1.X版本,新版本应该是http2.x
请求首行(请求方式、协议/版本号)
请求头
\r\n
请求体(不是什么请求方式都有的,GET请求方式没有)
4、HTTP协议的响应数据格式
响应首行(响应状态码)
响应头
\r\n
响应体
5、请求方式(重要)
GET
用于请求获取指定资源的数据
协议:// 域名/后缀/?参数
协议:// 域名/后缀/?k=v&k1=v1&k2=v2...
POST
用于向服务器提交数据,通常用于创建新的资源。例如百度的登录功能,把用户名和密码提交到百度的后端做验证.用的就是POST请求
PUT
用于向服务器更新或替换指定资源的数据。PUT请求是幂等的,多次请求同一个URL应该产生相同的结果。
DELETE
用于请求服务器删除指定资源。DELETE请求是幂等的,多次请求同一个URL应该产生相同的结果。
PATCH
用于对资源进行部分更新,只传输需要修改的部分。PATCH请求是非幂等的。
HEAD
类似于GET请求,但服务器只返回响应头部信息,不返回实际数据。主要用于获取资源的元数据,比如资源是否存在、最后修改时间等。
OPTIONS
用于获取目标资源支持的请求方法和其他选项信息。
TRACE
用于追踪请求-响应的传输路径,主要用于调试和测试。
CONNECT
用于建立与代理服务器的隧道连接,通常用于代理服务器的身份验证。
6、get请求和post请求的区别
-
参数传递位置:
- GET请求:参数是通过URL的查询字符串传递的,在URL中以键值对的形式出现,例如:
http://www.example.com/page?param1=value1¶m2=value2
- POST请求:参数是通过请求体(Request Body)传递的,参数不会直接显示在URL中。
- GET请求:参数是通过URL的查询字符串传递的,在URL中以键值对的形式出现,例如:
-
安全性:
- GET请求:参数在URL中明文显示,因此不适合传递敏感信息,比如密码等。GET请求可以被缓存、浏览器历史记录等留存,可能会暴露敏感信息。
- POST请求:参数在请求体中传递,不会明文显示在URL中,相对于GET请求更安全,适合传递敏感信息。
-
请求体:
- GET请求:没有请求体,因为参数是在URL中传递的。
- POST请求:有请求体,参数包含在请求体中,格式可以是多种类型,比如JSON、表单数据等。
-
请求长度限制:
- GET请求:受浏览器和服务器限制,一般有长度限制,较大的数据可能会被截断。
- POST请求:没有固定的长度限制,可以传输大量数据。
-
幂等性:
- GET请求:通常是幂等的,多次请求同一个URL应该返回相同的结果。
- POST请求:不一定是幂等的,多次请求同一个URL可能会产生不同的副作用。
-
使用场景:
- GET请求:适合用于获取资源,比如获取页面、图片等。
- POST请求:适合用于提交数据,比如表单提交、上传文件等。
四、
1、TCP协议
TCP协议也叫可靠协议,流式协议(数据是可以分多次发送的)
2、三次握手(目的:建立连接)
SYN、ACK、seq的含义:
-
SYN(Synchronize):SYN是TCP协议中的一个标志位,用于建立连接的初始化阶段。当一个主机(客户端)想要与另一个主机(服务器)建立TCP连接时,它会发送一个带有SYN标志的数据包,表明它想要发起一个连接请求。
-
seq(Sequence Number):seq是TCP协议中的一个字段,用于对数据包进行排序和重组。在TCP通信中,每个数据包都会携带一个序列号(seq number),用于标识发送方发送的数据字节的顺序。接收方使用这些序列号来正确地重组数据包,并按正确的顺序交付给应用程序。
-
ACK(Acknowledgment):ACK是TCP协议中的一个标志位,用于确认数据包的接收情况。在TCP三次握手的过程中,除了发送SYN标志的数据包之外,还需要使用ACK标志的数据包来确认对方的数据包接收情况。例如,在服务器接收到客户端的SYN数据包后,它会发送一个带有SYN和ACK标志的数据包给客户端,表示服务器接收到了客户端的连接请求,并愿意接受连接。
综上所述,在TCP三次握手过程中,客户端首先发送一个带有SYN标志的数据包给服务器,服务器接收到后会回应一个带有SYN和ACK标志的数据包,最后客户端发送一个带有ACK标志的数据包进行确认。通过这样的交互,建立了双方之间的TCP连接。seq字段用于排序和重组数据包,确保数据的正确传输。ACK字段用于确认数据包的接收情况。
TCP头部通常由以下字段组成(按顺序):
-
源端口号(Source Port):表示发送方的应用程序使用的端口号。
-
目标端口号(Destination Port):表示接收方的应用程序期望接收的端口号。
-
序列号(Sequence Number):用于对TCP数据流进行排序和重组,表示数据流中第一个字节的序列号。
-
确认号(Acknowledgment Number):表示期望接收的下一个字节的序列号,用于确认已接收的数据。
-
数据偏移(Data Offset):指示TCP头部的长度,以4字节为单位。
-
保留(Reserved):保留字段,暂时未使用。
-
控制位(Control Bits):包括以下标志位:
- URG(Urgent):指示紧急指针字段是否有效。
- ACK(Acknowledgment):表示确认号字段是否有效。
- PSH(Push):表示接收方应尽快将数据交给应用程序,而不是等待缓冲区填满。
- RST(Reset):表示重置连接。
- SYN(Synchronize):用于建立连接。
- FIN(Finish):用于关闭连接。
-
窗口大小(Window Size):表示发送方可以接收的字节数量,用于流量控制。如果窗口大小为0,可以发送窗口探测
-
校验和(Checksum):用于检测TCP头部和数据的传输错误。
-
紧急指针(Urgent Pointer):仅当URG标志位设置时,表示紧急数据的结束位置。
-
选项(Options):可选字段,用于提供额外的控制和功能。
-
填充(Padding):用于对齐TCP头部的长度。
数据部分(Payload)包含应用程序传输的实际数据。
通过这些字段,TCP协议可以提供可靠的、面向连接的数据传输服务,并在数据通信过程中实现序列号、确认机制、流量控制等功能。
3、使用tcpdump观察三次握手的过程
tcpdump -i <interface> 'tcp[tcpflags] & (tcp-syn|tcp-ack) != 0'
在给定的tcpdump命令中,tcp[tcpflags] & (tcp-syn|tcp-ack) != 0
是过滤表达式的一部分,用于筛选具有特定TCP标志的数据包。
让我们逐个解释这个过滤表达式的含义:
-
tcp[tcpflags]
表示TCP头部的标志字段。它将检查每个TCP数据包的标志位。 -
&
是位操作符,表示进行位与操作。 -
(tcp-syn|tcp-ack)
是一个组合条件,使用位或操作符|
将两个TCP标志tcp-syn
和tcp-ack
结合起来。tcp-syn
表示SYN标志位,tcp-ack
表示ACK标志位。 -
!= 0
表示不等于零。这个条件确保与组合条件进行位与操作后的结果不为零,即数据包的标志字段中同时设置了 SYN 和 ACK 标志位。
综上所述,tcp[tcpflags] & (tcp-syn|tcp-ack) != 0
的过滤表达式会筛选出具有同时设置了 SYN 和 ACK 标志位的TCP数据包,这通常是在TCP三次握手过程中建立连接时使用的数据包。
或者:
# 在本机启动nginx服务,curl 本机 tcpdump -i lo port 80
root@debian:~# tcpdump -i lo port 80 -S #-S 将tcp的序列号以绝对值形式输出,而不是相对值。
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode listening on lo, link-type EN10MB (Ethernet), snapshot length 262144 bytes # 第一条数据包 16:16:44.426638 IP6 localhost.54654 > localhost.http: Flags [S], seq 2442014618, win 65476, options [mss 65476,sackOK,TS val 2910674353 ecr 0,nop,wscale 7], length 0 # 第二条数据包 16:16:44.426684 IP6 localhost.http > localhost.54654: Flags [S.], seq 1828937919, ack 2442014619, win 65464, options [mss 65476,sackOK,TS val 2910674354 ecr 2910674353,nop,wscale 7], length 0 # 第三条数据包 16:16:44.426728 IP6 localhost.54654 > localhost.http: Flags [.], ack 1828937920, win 512, options [nop,nop,TS val 2910674354 ecr 2910674354], length 0 # 第四条数据包 16:16:44.426842 IP6 localhost.54654 > localhost.http: Flags [P.], seq 2442014619:2442014692, ack 1828937920, win 512, options [nop,nop,TS val 2910674354 ecr 2910674354], length 73: HTTP: GET / HTTP/1.1 # 第五条数据包 16:16:44.426852 IP6 localhost.http > localhost.54654: Flags [.], ack 2442014692, win 511, options [nop,nop,TS val 2910674354 ecr 2910674354], length 0 # 第六条数据包 16:16:44.427294 IP6 localhost.http > localhost.54654: Flags [P.], seq 1828937920:1828938770, ack 2442014692, win 512, options [nop,nop,TS val 2910674354 ecr 2910674354], length 850: HTTP: HTTP/1.1 200 OK # 第七条数据包 16:16:44.427316 IP6 localhost.54654 > localhost.http: Flags [.], ack 1828938770, win 506, options [nop,nop,TS val 2910674354 ecr 2910674354], length 0 # 第八条数据包 16:16:44.429676 IP6 localhost.54654 > localhost.http: Flags [F.], seq 2442014692, ack 1828938770, win 512, options [nop,nop,TS val 2910674357 ecr 2910674354], length 0 # 第九条数据包 16:16:44.430228 IP6 localhost.http > localhost.54654: Flags [F.], seq 1828938770, ack 2442014693, win 512, options [nop,nop,TS val 2910674357 ecr 2910674357], length 0 # 第十条数据包 16:16:44.430251 IP6 localhost.54654 > localhost.http: Flags [.], ack 1828938771, win 512, options [nop,nop,TS val 2910674357 ecr 2910674357], length 0
分析:
三次握手(Three-way handshake):
- 第一条数据包:客户端向服务器发送SYN(Synchronize Sequence Numbers)报文,请求建立连接。这里,客户端选择了一个初始的序列号(seq 2442014618),并设置窗口大小(win 65476)以及其他选项。
- 第二条数据包:服务器收到SYN报文后,回复一个SYN-ACK(Synchronize-Acknowledge)报文。服务器也选择了一个初始的序列号(seq 1828937919),并确认客户端的序列号,即 seq+1(ack 2442014619)。服务器还设置了窗口大小(win 65464)和其他选项。
- 第三条数据包:客户端收到SYN-ACK报文后,发送一个ACK(Acknowledge)报文。客户端确认服务器的序列号(ack 1828937920),上一次的seq+1,并设置窗口大小(win 512)以及其他选项。至此,三次握手过程完成,连接建立。
四次挥手(Four-way handshake):⚠️:实验效果没出来,第8和9之间还缺少一次服务端同意客户端的请求
- 第八条数据包:在数据传输完成后,客户端发送一个FIN(Finish)报文,表示关闭连接。客户端的序列号(seq 2442014692)不变。
- 第九条数据包:服务器收到FIN报文后,回复一个ACK报文,确认客户端的序列号(ack 2442014693)。服务器的序列号(seq 1828938770)不变。然后,服务器发送一个FIN报文,表示同意关闭连接。
- 第十条数据包:客户端收到服务器的FIN报文后,回复一个ACK报文。客户端确认服务器的序列号(ack 1828938771),并设置窗口大小(win 512)以及其他选项。至此,四次挥手过程完成,连接关闭。
4、四次挥手(目的:断开连接)
5、arp协议(根据IP地址计算出来Mac地址)在数据链路层
ARP(Address Resolution Protocol)是一种网络协议,用于将网络层的IP地址解析成物理层的MAC地址。它在局域网中起到了IP地址到MAC地址的映射作用,以便在数据链路层上正确地传输数据。
当一台主机需要与另一台主机通信时,它首先会检查自己的ARP缓存(ARP Cache)中是否已经有了目标主机的MAC地址。如果缓存中存在对应的记录,则直接使用该MAC地址进行通信。如果缓存中没有对应记录,则发送一个ARP请求广播包(ARP Request
Broadcast),该包中包含目标主机的IP地址,询问局域网上的其他主机:“谁知道这个IP地址对应的MAC地址?”局域网上的其他主机收到该请求后,会检查自己的ARP缓存,如果有匹配的记录,就直接发送一个ARP响应包(ARP Reply)回复请求主机,包含自己的
MAC地址。请求主机收到响应包后,将目标主机的IP地址和MAC地址添加到自己的ARP缓存中,并使用该MAC地址进行通信。
ARP协议在局域网中起到了重要的作用,它使得主机能够通过IP地址找到目标主机的MAC地址,实现数据包在局域网内的正确传输。
6、TCP协议的三次握手为什么可以保证数据安全?是因为建立了双向通道吗?
原因是:TCP协议的反馈机制
7、发现系统存在大量TIME_WAIT状态的连接,通过调整linux内核参数解决
vi /etc/sysctl.conf 编辑文件,加入以下内容: net.ipv4.tcp_syncookies = 1 net.ipv4.tcp_tw_reuse = 1 net.ipv4.tcp_tw_recycle = 1 net.ipv4.tcp_fin_timeout = 30 然后执行 /sbin/sysctl -p 让参数生效。 net.ipv4.tcp_syncookies = 1 表示开启SYN Cookies。当出现SYN等待队列溢出时,启用cookies来处理,可防范少量SYN攻击,默认为0,表示关闭; net.ipv4.tcp_tw_reuse = 1 表示开启重用。允许将TIME-WAIT sockets重新用于新的TCP连接,默认为0,表示关闭; net.ipv4.tcp_tw_recycle = 1 表示开启TCP连接中TIME-WAIT sockets的快速回收,默认为0,表示关闭。 net.ipv4.tcp_fin_timeout 修改系統默认的 TIMEOUT 时间
五、一台服务器,最大支持的TCP连接数是多少?
面对这个问题第一反应就是端口的限制,端口号最多是 65536个,那就最多只能支持 65536 条 TCP 连接。实际上这是不对的!
TCP 连接本质上在内核里就是一个 socket 对象。这个 socket 对象也就是一个数据结构,里面包含了 TCP 四元组的信息:源IP、源端口、目标IP、目标端口。
只要确认了【源IP、源端口、目标IP、目标端口】这四个信息,就能在内核中找到这个 socket 对象,也就能确定一条 TCP 连接。
一个服务端进程,意味着他的 IP地址和端口号是固定的(0.0.0.0:443
)。
也就是当客户端与服务端建立一条 TCP 连接的时候,这个 TCP 连接的四元组信息中服务端的 IP地址和端口号是固定的,能产生变化的就是客户端的 IP 地址和端口号了。
因此,一个服务端进程最大能支持的 TCP 连接个数的计算公式如下:
最大TCP连接数=客户端的ip数x客户端的端口数
对 IPv4,客户端的 IP 数最多为 2
的 32
次方,客户端的端口数最多为 2
的 16
次方。
那么一个服务端进程理想情况下,最大的 TCP 连接数约为 2
的 48
次方(2^32 (ip数) * 2^16 (端口数
),这数值是非常夸张的了,约等于两百多万亿!
当然,服务端进程最大能支持的 TCP 连接数远不能达到理论上限,还会受到文件描述符、内存大小资源的限制,毕竟 socket 在 Linux 的视角其实就是文件资源,而且一个 socket 对象也会占用一定的内存资源。
因此,会受以下因素影响:
- 文件描述符限制,每个 TCP 连接都是一个文件,如果文件描述符被占满了,会发生 Too many open files。Linux 对可打开的文件描述符的数量分别作了三个方面的限制:
- 系统级:当前系统可打开的最大数量,通过
cat /proc/sys/fs/file-max
查看; - 用户级:指定用户可打开的最大数量,通过
cat /etc/security/limits.conf
查看; - 进程级:单个进程可打开的最大数量,通过
cat /proc/sys/fs/nr_open
查看; - 内存限制,每个 TCP 连接都要占用一定内存,操作系统的内存是有限的,如果内存资源被占满后,会发生 OOM。
Linux每维护一条TCP连接都要花费资源,处理连接请求,保活,数据的收发时需要消耗一些CPU,维持TCP连接主要消耗内存。
我们题目的问题是考虑最大多少个连接,所以我们先不考虑数据的收发,那么TCP在静止的状态下,就不怎么消耗CPU了,主要消耗内存,而Linux上内存是有限的。
首先,我们要知道一条处于 ESTABLISH 状态的 TCP 连接具体占用多大内存?
一个 TCP 对象占用的大小,等于它所包含的一些数据结构占用大小的总和,也是就把上面这些数据结构的大小累加起来,就是一个 TCP 连接占用的大小了。
一条处于 ESTABLISH 状态的 TCP 连接占用的大小是 3.44 KB(0.81K+2.19K+0.19K+0.25K)
也就是,每一条静止状态的TCP连接大约需要吃 3.44K 的内存。
8 GB 物理内存的服务器,最大能支持的 TCP 连接数=8GB/3.44KB=2,438,956(约240万)
!当然, 实际过程中的 TCP 连接,肯定不是静止状态的,还会进行发送数据和接收数据了,那么这些过程还是会额外消耗更多的内存资源的,并发很难达到百万级别。