一、HTTP是什么
- http就是超文本传输协议 ,它基于TCP。是浏览器和服务器之间用于传输的一种规定,请求的时候按照什么什么发,回的时候按照什么什么发,它们都是基于TCP发送的二进制数据。现今,HTTP语句运用到了不在浏览器的方面。
- 浏览器一定是客户端
- 这一坨绿油油的就是HTTP协议。具体解释如下:
GET /happy.html HTTP/1.1 # 请求方式Get,请求文件happy.html,HTTP1.1版本
# 下面的东西,类似于字典(其实是字符串)
Host: 127.0.0.1:8080 # 主机端口
Connection: keep-alive # 长链接
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36 # 客户端浏览器版本
Sec-Fetch-User: ?1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3 # 浏览器能接收的格式
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Accept-Encoding: gzip, deflate, br # 能接收的压缩格式
Accept-Language: zh-CN,zh;q=0.9 # 能接收的语言
- 查看HTTP协议
- 如图操作
- 点击请求和应答后面的View Source
- 这就是浏览器发送的请求
- 这就是浏览器接收的应答
一去一回就是HTTP协议。
服务器返回的东西中,上面那个应答的部分,被称为应答头head。HTTP/1.1 200 OK是其中必不可少的。后面紧跟传输的内容body,是浏览器真正传输的东西。第一个空行上方是head,下方是body。
- 我们用网络调试助手,充当服务器给浏览器法信息。(需要有一个返回头,和一串信息。)
HTTP/1.1 200 OK
<h1>hello world</h1>
二、Web服务器
1.写代码框架
- 既然网络调试助手可以当服务器,那么之前写的python程序也可以
- 准备一个html文件
- 代码如下:
- 效果如下:
2.TCP的三次握手、四次挥手
2.1三次握手
- 三次握手就是双方准备资源
- connect默认堵塞,在三次握手成功时,解堵塞。也就是说,握手的起点是connect。
- 客户端:你准备好了吗?
客户端给服务器发送一个数字。
为了显示是第一次发送的请求,前面用syn标记
- 服务器:我好了,你呢?
服务器将这个数字+1返回,用ack标记
并返回一个新的值,用syn标记
- 客户端:愉快地开始玩耍吧
客户端将这个新的值+1返回,用ack标记
2.2四次挥手
- 四次挥手就是双方释放资源
- TCP是全双工的,必须得收发都关了,才是真正的关闭。
- 挥手的起点是close。
- 客户端:我他娘地以后不再跟你讲一句话。(客户端关闭发送)
客户端不和服务器说话时,服务器的
client_socket.recv()
会堵塞。可是这次客户端如此决绝,服务器就解堵塞。recv_data = client_socket.recv() if recv_data: """来欣赏妾身的舞姿""" else: """滚吧,渣男""" client_socket.close()
- 服务器:好吧,随你(服务器确认接收到客户端的信息,客户端关闭接收)
- 服务器:我也不理你了,滚吧,渣男(服务器关闭对他的发送)
- 客户端:fine o( ̄︶ ̄)o(客户端关闭接收)
a. 客户端调用close(),客户端关闭发送
b. 服务器给客户端回应自己收到了。
c. 服务器的client_socket.recv()还在堵塞。如果加上一个判断其的条件语句,当收到空时,也就是客户端执行了close()操作时,服务器解堵塞。服务器关闭对中国客户端的发送。
e. 客户端知道服务器不给自己发数据时,客户端就关闭了收数据。并且向服务器发送一个确认的包。
- 但问题是: 客户端怎么知道服务器收到了?答,返回一个确认包。怎么确认对方收到了返回的确认包?对方再返回一个确认确认的确认包…这不就是死循环吗?
- 解决办法: 规定,谁先调用的最后一次Close,谁就等待一个时间,如果超过这个时间没收到确认包,就再发一个。于是!接收包的这方(在这里是C),回复一个确认包后,要等待两份的这个时间,记做2MSL,如果对方没收到确认包,它需要再次接收一次确认包。
- 注意: 谁先发起close,谁就要等待。所以要客户端先发。不然服务器强制关闭后,会出现某某端口被占用。(客户端端口不固定换就完事,服务器是固定端口,所以会出问题)
- 添加这一行代码,就可以在调用close后,立马结束进程
3.应用户需求打开页面
- 获取用户需求的页面名
- 切割发送的请求
- 用re模块,获取页面名
- 为了防止页面名为空,设定一个主页index.html
- 打开相应文件
- 代码:
4.完善代码
- 有一个小BUG:输入不存在的文件,应该返回404
三、并发HTTP服务器
1.多进程实现
- 关于为什么要对客户端套接字调用两次close:
主进程创建套接字后,由于子进程会复制一模一样的资源,所以对这个套接字就会有两个硬链接。
主进程调用close(),并不会把这个套接字释放,只会减少一个硬链接。子进程中的代码执行完时,调用的close(),才会真正四次挥手。
2.多线程实现
- 对刚才多进程文件
:%s/multiprocessing/threading/g
:%s/Process/Thread/g
- 删除主进程里的close()
多线程并不存在有两个硬链接的问题,当主进程调用close时,就已经被关闭了。
3.多协程实现
4.单进程单线程非阻塞
- 套接字有个.setblocking(False)可以设置为非堵塞。那么哪里会阻塞呢?没有新客户端接入和未收到信息会阻塞。
- 由于非堵塞时,未接受到一定会报错,所以使用try结构。下面我们用一种取巧的方式验证(没有就输入0)
while True:
try:
new_socket = int(input("请输入一个新的套接字"))
zero_s = 5/new_socket
except Exception as ret:
print(">>>>>>>没有新的客户端<<<<<<<")
else:
print("-------新客户端-------")
try:
recv = int(input("请输入接收的消息"))
zero_c = 3/recv
except Exception as ret:
print("]]]]]]]没有接收的消息[[[[[[[")
else:
print("=======新消息=======")
- 结果验证:
在这个套接字未接收到消息后,是不会继续服务它的。所以放在外部。可以用个列表存放套接字,在需要时取用。
- 最终代码:
- 如果发消息过快,会发现消息有可能会在同一行内,说明?
说明电脑有一个缓存区,消息是放在缓存区里的。需要时再取用。进程每隔一段时间来看有没有它的数据,没有的话,要么阻塞要么异常。
5.长链接&短链接
- HTTP1.0时期是短链接,现在的HTTP1.1是长链接
- 为了获取一个对象的三个数据可以有两种方式
- 短链接:
- 三次握手,接收第一个数据,四次挥手
- 三次握手,接收第二个数据,四次挥手
- 三次握手,接收第三个数据,四次挥手
- 长链接:
- 三次握手,接收第一个数据
- 接收第二个数据
- 接收第三个数据,四次挥手
5.2非阻塞实现长链接
- close关闭服务套接字,那不就相当于短链接了吗?
- 注释掉close,浏览器就会一直转圈圈。(因为不知道接收完数据了)
- 可以用Content-Length请求头,并获取文件体长度,只要长度达到了,浏览器就知道接收完数据了。
- 代码实现:
6.epoll
- epoll是当今Linux中采用的方式。比如gevent底层的实现就是用epoll。
- nginx和apache都是用的epoll。
- 这部分了解怎么用就行。
- epoll是单进程单线程的。
- 操作系统有内存,应用程序也有内存。两部分内存相互独立。
- 单线程非阻塞,是在应用程序内部有一个服务套接字列表。每当for循环(轮询)到某一个套接字时,会复制这个对象的fd(文件索引),然后给操作系统内存。操作系统就会查看是否有属于它的数据,没有就异常或阻塞。因为存在复制过程,列表越大,性能就会显著降低。
- epoll可以让应用程序和操作系统内核共用某一块内存。并且不遍历了,以事件通知的方式。
6.2epoll代码实现
- 导入select,创建一个共享内存,并将监听套接字写入
- 返回触发事件的列表
- 当新客户端连接时,生成一个新套接字
- 当服务套接字接收到信息时、断开时
- 最终代码:
四、网络通信
1.TCP/IP协议
- tcp-ip协议是一类协议的简称:
- 链路层→网路层→传输层→应用层
- 应用层: 包括应用自己规定的协议,浏览器之类的还有HTTP协议
- 传输层: 一个应用可以既走TCP又走UDP,并且端口可以一致。(TCP内部不能存在两个8090端口,UDP同理。但是可以TCP8090端口,一个UDP8090端口)
- ICMP:比如Ping,判断对方是否在线
- 原始套接字:可以直接从应用到IP。(传输层会检查IP,若使用此种方式,可以伪造IP)
- 以浏览器发送消息为例:
- 应用层POST /xxx.html HTTP/1.1\r\n\r\nHello World
- 传输层在前面加tcp端口、源端口、目的端口
- 网络层在前面加源IP、目的IP
- 链路层在前后加MAC.地址
- 然后层层拆解。不匹配就扔。
1.2另一套标准OSI
- OSI只是理论标准
2.Wireshark抓包工具
- 下面我们来通过抓包工具抓取数据包,然后进行分析
- 这个工具是在发送接收网络数据,收不到或者发不出时,来判断原因的。
- 原理是利用原始套接字,穿到操作系统最核心的部分,把所有数据拷贝了一份。
- 以Windows系统为例(Linux和Mac版安装较复杂)
2.1安装
- 下载工具,一路Next
- 记得勾选(这个工具只是作展示用,而勾选才可以抓包)
2.2抓包
- 点开一个
- 红框里点击一个,蓝框里会出现详细信息
- 黄框里是数据在内存中的表现(看不懂是因为加密了)
Source | Destination | Protocal | Length | info |
---|---|---|---|---|
源IP | 目的IP | 协议 | 数据长度 | 简单信息描述 |
蓝色的为Mac地址
蓝色的为IP地址
蓝色的显示为UDP方式
蓝色为应用程序的数据
2.3过滤
- 看图识字吧:
- 只看TCP
- 只看目的IP为192.168.1.105
- ip.src可以指定源IP
- 可以组合条件,用 and 连接(not和or也能用)
- udp.port指定UDP端口(同理)
3.电脑通信&网络掩码
1.1子网掩码
- 两台电脑通过网线连接,需要设置IP地址和网络掩码
- 子网掩码可以确定网络号是谁,主机号是谁。
- 把IP地址转换为2进制。然后执行按位与操作。
- 按位与操作: 都为1 ,才为1,只要有1个0,那么就是0
- 比如192为1100 0000。255为1111 1111。两者按位与结果为:1100 0000。
- 也就是说,子网掩码255所在位为网络号,0所在位为主机号。
1.2集线器&交换机
1.2.1集线器Hub
- 网络里传输的是数据的信号,而不是电流
- 集线器发出的数据是以广播的形式。容易卡,所以被淘汰了。
- 扩展坞也是一种Hub。
1.2.2交换机switch
- 该广播广播,该单播单播
- 要发送数据,必须得知道Mac地址
- ARP协议可以根据IP地址找到Mac地址(Windows的cmd下输入arp -a可以查看)
- ARP查找的过程:
- 电脑广播一个数据包
- 所有的网卡,默认还接受另一个Mac地址(广播Mac地址):FF.FF.FF.FF.FF.FF
- 对应的IP地址会接包,并单播回送一个数据。然后电脑接收到Mac地址,会根据IP储存起来。
1.2.3arp攻击
- 假如A要给B发信息。
- C主动给A发自己的Mac地址,然后说是B的。给B发自己的Mac地址,然后说是A的。这样A给B发信息,C可以修改、劫持、监听
1.3路由器Router
- 交换机连接多台电脑进一个网络。路由器连接多个网络。
- 路由器至少有两个网卡。每个网卡与其对应的交换机处于同一个小网络内。路由器内的多张网卡可以发生数据通信。
- 一台电脑要给另一个网络的电脑发送消息,需要通过默认网关。默认网关一般就是路由器。
- 网关: 收到了数据,并把数据发出去的代理,就叫网关。
- 两个不同网络内的电脑在通信过程中IP地址不发生任何变化,Mac地址会发生变化(通过网关代理)(类似在课堂上两个相距甚远的情侣传纸条)
- Mac地址仅仅是收发双方的,逐级变化。IP是最终希望通信的两方之间的,所以不会变。IP是一个逻辑上的地址。
- 路由器之间有一个路由发现协议。可以知道彼此的MAC、IP地址
1.4复杂通信过程
- 解析域名
- 发送数据给默认网关
- 数据转手给其它网关,直到到达DNS服务器网络内
- 数据发送给DNS服务器,得到IP地址
- 再一路往回传
- 向目标服务器发送tcp的三次握手
- 发送HTTP请求数据,等待服务器应答
- 发送TCP四次挥手