网络编程
网络编程
1. C/S B/S 架构
C : client 端 客户端
B : Browser , 浏览器
S : Server 服务器
C/S 架构 所有app都是C/S架构
优点 : 稳定性高,响应速度快,安全性高,功能全面 , 差异化个性化显示
缺点: 开发成本高,维护成本高,面向客户固定
B/S架构 浏览器 与服务器 之间的架构 特殊的 C/S架构
优点 : 开发维护成本低 , 面向用户广泛
缺点 : 安全性不高 , 响应速度慢 , 个性化设置单一
2. 互联网通信原理
数据交换
为了通信的广泛性 , 需要一个共通的协议
路由器
1. 虚拟分配ip地址:DHCP协议
- 网关: 连接外网,
- 计算最优路径
3.七层协议(五层协议)
-
物理层 : 物理传输介质 , 发送的是010101byte数据流 , 但不知道发送给谁,所以需要添加mac地址
-
数据链路层 : 根据以太网协议将数据进行分组 , 添加数据头, 每一组称为一帧
- head 数据头 18个字节 源地址6| 目标地址6| 数据类型6
- data 数据体 46个字节 < data < 1500字节
理论上来讲 , 这样就可以通过广播和任意一台电脑通信了 , 但是并不是所有电脑都在一个子网 , 所以还需要知道与之通信的局域网的位置
-
网络层: IP协议: 确定对方局域网位置(也就是ip地址) , 最后再确定通信软件
-
传输层 : TCP UDP 端口协议 1--65535个端口 1 -- 1023 系统端口
-
应用层 : 软件自己的协议
mac 地址 厂商编号 + 流水线编号
IP地址 通过ARP协议--->> mac地址 (ip地址通过软件帮我们实现)例如 0.0.0.0 - 255.255.255.255 (八位 所以最大是255)
子网掩码: 255.255.255.0(c类)
网络层通信:
1 .ip地址和子网掩码找到局域网位置
2 .将 ip 和 子网掩码 转换为 二进制 然后取 and 运算
3 . 如果不在同一个局域网 : 要通过网关-->路由协议-->通信
TCP
tcp好处: 安全 , 稳定
缺点: 传输速度慢,效率低
TCP 三次握手 四次挥手
## 三次握手
1. client syn = 1 seq = x 发送请求给 server
2. server 响应 ack = x+1 回复 client
3. server syn = 1 seq = y 请求 client
4 client ack = y+1 响应 server
2,3可以合成一步
第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。
---------------------
原文:https://blog.csdn.net/qq_38950316/article/details/81087809
## 四次挥手
1 client fin = 1 , seq = u 发送终止请求 (不发数据了) server 终止等待
2 server 响应 ack =1 ,seq = v , ack = u+1 client (已确定客户端不会再发数据了 , 但是服务器想发数据客户端还是可以接到) 关闭等待
3 server 发送 fin = 1 ack = 1 seq = w ack = u+1 最后确认
4 client 发送 ack = 1 , seq = u+1 , ack = w+1 (这时并没有断开连接,需要等server先释放连接)
【问题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报文。在Client发送出最后的ACK回复,但该ACK可能丢失。Server如果没有收到ACK,将不断重复发送FIN片段。所以Client不能立即关闭,它必须确认Server接收到了该ACK。Client会在发送出ACK之后进入到TIME_WAIT状态。Client会设置一个计时器,等待2MSL的时间。如果在该时间内再次收到FIN,那么Client会重发ACK并再次等待2MSL。所谓的2MSL是两倍的MSL(Maximum Segment Lifetime)。MSL指一个片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。如果直到2MSL,Client都没有再次收到FIN,那么Client推断ACK已经被成功接收,则结束TCP连接。
【问题3】为什么不能用两次握手进行连接?
答:3次握手完成两个重要的功能,既要双方做好发送数据的准备工作(双方都知道彼此已准备好),也要允许双方就初始序列号进行协商,这个序列号在握手过程中被发送和确认。
现在把三次握手改成仅需要两次握手,死锁是可能发生的。作为例子,考虑计算机S和C之间的通信,假定C给S发送一个连接请求分组,S收到了这个分组,并发 送了确认应答分组。按照两次握手的协定,S认为连接已经成功地建立了,可以开始发送数据分组。可是,C在S的应答分组在传输中被丢失的情况下,将不知道S 是否已准备好,不知道S建立什么样的序列号,C甚至怀疑S是否收到自己的连接请求分组。在这种情况下,C认为连接还未建立成功,将忽略S发来的任何数据分 组,只等待连接确认应答分组。而S在发出的分组超时后,重复发送同样的分组。这样就形成了死锁。
【问题4】如果已经建立了连接,但是客户端突然出现故障了怎么办?
TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。
---------------------
原文:https://blog.csdn.net/qq_38950316/article/details/81087809
UDP
效率高 , 速度快
socket 套接字(提供一套接口的关键字)
- 应用层与传输层之间的抽象层
- python模块
- 提升开发效率 , 不需要将过多的时间花在计算机底层实现上
模拟网络通信
server
# server
import socket
## 实例化对象 参数默认设置为TCP
server = socket.socket()
# 绑定ip地址 和端口号
server.bind(('127.0.0.1',8900))
# 开启监听
server.listen(5)
# 同意 并 开通管道
conn , addr = server.accept()
while 1:
## 接收内容
client_data = conn.recv(1024)
if client_data.decode()=='q':
break
print(f'男神:{client_data.decode()}')
data = input('>>>')
## 回复内容
conn.send(data.encode())
conn.close()
import socket
client = socket.socket()
client.connect(('127.0.0.1',8900))
while True:
s = input('>>>')
if s == 'q':
client.send('q'.encode())
break
client.send(s.encode())
server_data = client.recv(1024)
print(server_data.decode())
client.close()
import socket
import subprocess
# server
server = socket.socket()
server.bind(('127.0.0.1',8900))
server.listen(5)
while 1:
conn , addr = server.accept()
while 1:
try:
client_data = conn.recv(1024)
if client_data.decode() == 'q':
break
ret = subprocess.Popen(client_data.decode(),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
true_msg = ret.stdout.read()
err_msg = ret.stderr.read()
conn.send(true_msg+err_msg)
except:
break
# client
import socket
client = socket.socket()
## 请求连接
client.connect(('127.0.0.1',8900))
while 1:
data = input('>>>')
# 发送数据
client.send(data.encode())
if data.upper()=='Q':
break
# 接收服务器返回数据
server_data = client.recv(1024)
print(f"女神:{server_data.decode('gbk')}")
client.close()
数据缓冲区
保持稳定性 , 减少与磁盘的交互 , 提高上传下载的效率
为防止网络异常或波动,导致的数据中断
粘包现象
产生原因 : 对方不知道数据大小 , 不知道界限, 不知道取多少
数据的传输过程 : client端 send ---> 同时 resv 在缓冲区接收从server端传来的数据 , 有就收取 , 因为每次resv字节有限 , 所以如果server端一次传输的数据长度大于resv一次读取的数据长度 , 那么 缓存区就会有遗留 , 那么下一次再send时,因为缓冲区有数据 , 那么recv就会直接接收 , 这就会出现问题
解决
## 根据粘包生成原因 , 只要我让接收方 知道文件大小, 知道界限 , 不就可以了吗?
# server 端
import subprocess
import socket
import json
import struct
import os
server = socket.socket()
server.bind(('127.0.0.1',9000))
server.listen(5)
conn , addr = server.accept()
file_name = conn.recv(1024)
ret = subprocess.Popen(file_name.decode(),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
stderr_data = ret.stderr.read()
stdout_data = ret.stdout.read()
data = stdout_data+stderr_data
dict = {
'file_name':'file',
'size':os.path.getsize(file_name)
}
json_dic = json.dumps(dict).encode()
json_dic_size = len(json_dic)
total_byte = struct.pack('i',json_dic_size)
conn.send(total_byte)
conn.send(json_dic)
f = open(file_name,mode='rb')
while 1:
content = f.read(1024)
if content:
conn.send(content)
print(content)
else:
break
#client 端
import socket
import json
import struct
import time
import os
client = socket.socket()
client.connect(('127.0.0.1',9000))
while 1:
file_name = input('请输入:')
client.send(file_name.encode())
total_byte = client.recv(4)
total_int = struct.unpack('i',total_byte)[0]
json_dict = client.recv(total_int)
dict = json.loads(json_dict)
s = b''
f = open('new'+file_name,mode='wb')
print(dict['size'])
t1 = time.time()
while len(s)<dict['size']:
s += client.recv(2048)
print(len(s)/dict['size'])
f.write(s)
print(time.time()-t1)
f.close()