软件开发架构
C/S架构
Client:客户端
Server:服务端
优点:
软件的使用稳定,并且可以节省网络资源
缺点
- 若用户想在同一设备上使用多个软件,必须下载多个客户端
- 软件的一次更新,客户端也必须跟着重新下载更新
B/S架构
Browser:浏览器
Server:服务端
优点:
以浏览器充当客户端,无需用户下载多个软件,也无需用户下载更新软件版本,直接在浏览器上访问需要使用的软件
缺点
消耗网络资源较大,当网络不稳定时,软件的使用也不稳定
互联网协议
互联网协议又称为网络七层协议,OSI七层协议,OSI是一个世界标准组织
OSI七层协议:
- 应用层
- 表示层
- 会话层
- 传输层
- 网络层
- 数据链路层
- 物理连接层
物理层
基于电信号的高低电压发送01二进制数据
数据链路层
数据链路层定义了对电信号的分组方式
以太网协议:
统一了标准的定义方式
每一台连接网线的电脑都有一块网卡
网卡由不同的厂商生产
每块网卡都有一个唯一的12位编号:mac地址
前6位: 厂商号
后6位: 流水号
交换机:
可以让多台电脑连接到一起
基于以太网协议发送数据:
特点:广播,单播
弊端:广播风暴,不能跨局域网通信
网络层
如果全世界都光靠数据链路层中的以太网协议,mac地址,广播来通信是不够的,因为一台机器发送信息的话,全世界的机器都将收到,这将会是一场灾难
所以就有了网络层,网络层定义了一个IP协议,IP协议将不同的局域网都分割开来
Mac地址是用来标识你这个教室的一个位置(一台电脑),IP地址是用来标识你在哪个教室(哪个局域网)
局域网中怎么获取对方的Mac地址:
肯定要知道对方的IP地址,这是最基本的,就像你要访问百度,肯定得知道百度的域名,域名就是百度的IP地址。自己的IP可以轻松获得,自己的Mac也轻松获取,目标Mac为12个F,我们叫广播地址,表达的意思是我想要获取这个目标IP地址172.16.10.11的机器的Mac地址。Mac为12个F代表的是一种功能,这个功能就是获取对方的Mac地址,计算机的Mac永远不可能是12个F。假设是在本教室广播,一嗓子吼出去了,所有人开始解包,只有IP地址是172.16.10.11的这个人才会返回他的Mac地址,其他人全部丢弃。发回来源Mac改成飞哥自己的Mac地址,同时把飞哥的Mac地址放在数据部分。
跨网络怎么获取对方的Mac地址:
通过IP地址区分,计算机运算判断出飞哥不在同一个教室,目标IP就变成了网关的IP了。网关的IP在计算机上配死了,可以轻松获取。
IP协议
规定网络地址的协议叫IP协议,它定义的地址称之为IP地址,广泛采用IPv4版本,它规定的网络地址由32位2进制表示,范围0.0.0.0 - 255.255.255.255, 还有一种叫IPv6版本的
本机IP: 回环地址 127.0.0.1 ---> localhost
arp协议
arp协议由来:计算机通信基本靠吼,即广播的方式,所有上层的包到最后都要封装上以太网头,然后通过以太网协议发送,在谈及以太网协议时候,我门了解到:通信是基于Mac的广播方式实现,计算机在发包时,获取自身的Mac是容易的,如何获取目标主机的Mac,就需要通过arp协议
arp协议功能:广播的方式发送数据包,获取目标主机的Mac地址
协议工作方式:每台主机IP都是已知的,例如:主机172.16.10.10/24访问172.16.10.11/24
传输层
IP地址帮助我们找到区分子网,Mac地址帮我们找到主机,但是主机上打开着许多个应用程序
那么我们如何标识一台计算机上的应用程序呢,那就是传输层中的端口,端口即应用程序与网卡关联的编号
传输层的功能就是建立端口到端口的通信
端口的范围是0-65535,其中0-1023为系统占用端口
开发中常用软件的默认端口号:
mysql:3306
Django:8000
Tomcat:8080
Flask:5000
TCP协议
可靠传输,TCP数据包没有长度限制,理论上可以无限长,但是为了保证网络的效率,通常TCP数据包的长度不会超过IP数据包的长度,以确保单个TCP数据包不必再分割
三次握手
第一次握手:
起初两端都处于CLOSED关闭状态,Client将标志位SYN置为1,随机产生一个值seq=x,并将该数据包发送给Server,Client进入SYN-SENT状态,等待Server确认
第二次握手:
Server收到数据包后由标志位SYN=1得知Client请求建立连接,Server将标志位SYN和ACK都置为1,ack=x+1,随机产生一个值seq=y,并将该数据包发送给Client以确认连接请求,Server进入SYN-RCVD状态,此时操作系统为该TCP连接分配TCP缓存和变量
第三次握手:
Client收到确认后,检查ack是否为x+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=y+1,并且此时操作系统为该TCP连接分配TCP缓存和变量,并将该数据包发送给Server,Server检查ack是否为y+1,ACK是否为1,如果正确则连接建立成功,Client和Server进入ESTABLISHED状态,完成三次握手,随后Client和Server就可以开始传输数据
(1)为什么A还要发送一次确认呢?可以二次握手吗?
主要为了防止已失效的连接请求报文段突然又传送到了B,因而产生错误。如A发出连接请求,但因连接请求报文丢失而未收到确认,于是A再重传一次连接请求。后来收到了确认,建立了连接。数据传输完毕后,就释放了连接,A工发出了两个连接请求报文段,其中第一个丢失,第二个到达了B,但是第一个丢失的报文段只是在某些网络结点长时间滞留了,延误到连接释放以后的某个时间才到达B,此时B误认为A又发出一次新的连接请求,于是就向A发出确认报文段,同意建立连接,不采用三次握手,只要B发出确认,就建立新的连接了,此时A不理睬B的确认且不发送数据,则B一致等待A发送数据,浪费资源
(2) Server端易受到SYN攻击?
服务器端的资源分配是在二次握手时分配的,而客户端的资源是在完成三次握手时分配的,所以服务器容易受到SYN洪泛攻击,SYN攻击就是Client在短时间内伪造大量不存在的IP地址,并向Server不断地发送SYN包,Server则回复确认包,并等待Client确认,由于源地址不存在,因此Server需要不断重发直至超时,这些伪造的SYN包将长时间占用未连接队列,导致正常的SYN请求因为队列满而被丢弃,从而引起网络拥塞甚至系统瘫痪
防范SYN攻击措施:降低主机的等待时间使主机尽快的释放半连接的占用,短时间受到某IP的重复SYN则丢弃后续请求
四次挥手
第一次挥手:
A的应用进程先向其TCP发出连接释放报文段(FIN=1,序号seq=u),并停止再发送数据,主动关闭TCP连接,进入FIN-WAIT-1(终止等待1)状态,等待B的确认
第二次挥手:
B收到连接释放报文段后即发出确认报文段,(ACK=1,确认号ack=u+1,序号seq=v),B进入CLOSE-WAIT(关闭等待)状态,此时的TCP处于半关闭状态,A到B的连接释放
A收到B的确认后,进入FIN-WAIT-2(终止等待2)状态,等待B发出的连接释放报文段
第三次挥手:
B没有要向A发出的数据,B发出连接释放报文段(FIN=1,ACK=1,序号seq=w,确认号ack=u+1),B进入LAST-ACK(最后确认)状态,等待A的确认
第四次挥手:
A收到B的连接释放报文段后,对此发出确认报文段(ACK=1,seq=u+1,ack=w+1),A进入TIME-WAIT(时间等待)状态。此时TCP未释放掉,需要经过时间等待计时器设置的时间2MSL后,A才进入CLOSED状态
(1)为什么连接的时候是三次握手,关闭的时候却是四次握手?
因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,"你发的FIN报文我收到了"。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手
(2)为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?
虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文
Socket套接字
多个客户端:
# server端 import socket server = socket.socket() server.bind( ('127.0.0.1', 8888) ) server.listen(5) # 半连接池,可以“等待5个用户接受服务” while True: # 等待客户端连接过来 conn, addr = server.accept() print(addr) # 循环接收客户端数据 while True: try: data = conn.recv(1024).decode('utf-8') print(data) # mac和linux系统bug: b'' if len(data) == 0: continue if data == 'q': break conn.send(data.encode('utf-8')) except Exception as e: print(e) break conn.close()
# client端 import socket client = socket.socket() client.connect( ('127.0.0.1', 8888) ) while True: send_msg = input('client---》server:') client.send(send_msg.encode('utf-8')) if send_msg == 'q': break data = client.recv(1024).decode('utf-8') print(data) client.close()