python基础系列 —— TCP/UDP网络协议通信原理实现

目录

一、网络层面基础概念

         1、osi 网络七层模型

2、交换机与路由器

3、arp协议原理

4、TCP三次握手 

5、TCP四次挥手

二、利用socket模块实现TCP/UDP通信

1、TCP消息通信

2、UDP消息通信

三、利用hashlib模块进行加密校验

扫描二维码关注公众号,回复: 14707324 查看本文章

1、数据加密几种方式

2、文件校验


一、网络层面基础概念

1、osi 网络七层模型

应用层(应用层,表示层,会话层)
   封装数据 
      依据不同的协议,封装对应格式的数据消息
      HTTP [超文本传输协议]
      HTTPS[加密传输超文本传输协议]
      FTP  [文件传输协议]
      SMTP [电子邮件传输的协议]

传输层:
   封装端口
      指定传输的协议(TCP协议/UDP协议)
网络层:   
   封装ip
      版本ipv4  / ipv6

数据链路层:
   封装mac地址
      指定链路层的协议(arp协议(ip->mac)/rarp协议(mac->ip))
      
物理层:
   打成数据包,变成二进制字节流,通过网络进行传输.

2、交换机与路由器

# 交换机:对同一网段的不同机器之间进行数据转发的设备     
[每一台机器和交换机相连,形成通信]
交换机从下到上拆2层,拆物理层和数据链路层,可以找到mac
# 路由器:对不同网段的不同机器之间进行数据转发的设备     
[每一个局域网和路由器相连,形成通信]    
交换机从下到上拆3层,拆物理层和数据链路层和网络层,可以找到ip

3、arp协议原理

 通过ip -> mac (arp地址解析协议)

1、主机A发送请求时,如果ARP列表没有mac,主机一开始会发送arp请求包(发出找mac的请求)
把mac标记一个全FF-FF-FF-FF-FF-FF的广播地址。
2、交换机接收到arp的广播包,从下到上进行拆包,拆2层,到数据链路层得到mac
发现mac是全F的广播地址,重新打包,交换机开始广播,所有连接在交换的设备都会收到arp广播包。
3、各大主机在接受arp请求包的时候,都会去对照自己本机当中的arp解析表(ip->mac)
如果没有,这个arp得请求包舍弃,如果有,会把自己的ip和mac封装在arp的响应包当中给交换机进行单播。
4、如果有,则首先从数据包中取出源主机的IP和mac地址写入到ARP列表中,如果以存在,则覆盖
然后将自己的ip和mac封装在arp的响应包当中给交换机进行单播。
5、源主机收到ARP响应包后,将目的主机的IP和mac地址写入arp列表,并利用此信息发送数据
如果源主机一直没有收到arp响应数据包,表示arp查询失败。

4、TCP三次握手 

SYN 创建连接(0否1是)
ACK 确认响应
FIN 断开连接
seq 随机数据包

第一次握手:主机A发送位码为syn=1,随机产生seq number=x的数据包到服务器,主机B由SYN=1知道,A要求建立联机;
第二次握手:主机B收到请求后要确认联机信息,向A发送ack number=(主机A的seq+1),syn=1,ack=1,随机产生seq=y的包;
第三次握手:主机A收到后检查ack number是否正确,即第一次发送的seq number+1,以及位码ack是否为1,若正确,主机A会再发送ack number=(主机B的seq+1),ack=1,主机B收到后确认seq值与ack=1则连接建立成功。

5、TCP四次挥手

1、客户端进程发出连接释放报文,并且停止发送数据。FIN=1,其序列号为seq=u,此时,客户端进入终止等待状态。
2、服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE-WAIT(关闭等待)状态。
3、客户端收到服务器的确认请求后,此时,客户端就进入FIN-WAIT-2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。
4、服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST-ACK(最后确认)状态,等待客户端的确认。
5、客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME-WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2∗∗MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
6、服务器只要收到了客户端发出的确认,立即进入CLOSED状态。同样,撤销TCB后,就结束了这次的TCP连接。可以看到,服务器结束TCP连接的时间要比客户端早一些。

 常见面试题
【问题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个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。

二、利用socket模块实现TCP/UDP通信

1、TCP消息通信

server.py

# ### tcp 服务端

import socket
# 1.创建一个socket对象
sk = socket.socket()

# 让当前端口重复绑定多个程序(仅仅在测试阶段用)
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)

# 2.在网络中注册主机(绑定ip和端口号)
sk.bind( ("127.0.0.1",9000) )

# 3.监听端口
sk.listen()

# 4.三次握手
# conn,addr = sk.accept()

# 5.收发数据
'''
数据类型:二进制的字节流
b修饰的字符串 => 代表的是二进制的字节流
里面的字符必须是ascii编码中的字符.不能是中文,否则报错
'''
"""
conn.send(b"i love you")
"""

while True:
	conn,addr = sk.accept()
	while True:
		res = conn.recv(1024)
		print(res.decode())
		strvar = input("请输入服务端给客户端发送的消息:")
		conn.send(strvar.encode())
		# 退出
		if strvar == "q":
			break


# 6.四次挥手
conn.close()

# 7.退还端口
sk.close()


client.py

# ### tcp 客户端
import socket

# 1.创建一个socket对象
sk = socket.socket()

# 2.连接服务端
sk.connect( ("127.0.0.1",9000) )

# 3.收发数据
"""
res = sk.recv(1024) # 一次接受的最大字节数是1024
print(res)
"""

while True:
	strvar = input("请输入您要发送的数据")
	sk.send(strvar.encode())

	res = sk.recv(1024)
	if res == b"q":
		break
	print(res.decode())


# 4.关闭连接
sk.close()

2、UDP消息通信

udpserver.py

# ### 服务端
import socket
# type=socket.SOCK_DGRAM => 返回udp协议对象
# 1.创建udp对象
sk = socket.socket(type=socket.SOCK_DGRAM)

# 2.绑定地址端口号
sk.bind( ("127.0.0.1",9000) )

# 3.接受消息(udp作为服务端的时候,第一次一定是接受消息)
while True:
	# 接受消息
	msg,cli_addr = sk.recvfrom(1024)
	print(msg.decode("utf-8"))
	print(cli_addr)

# 服务端给客户端发消息
message = input("服务端要发送的消息:")
sk.sendto(message.encode("utf-8"), cli_addr)


# 4.关闭连接
sk.close()
udpclite.py

# ### 客户端
import socket
# type=socket.SOCK_DGRAM => 返回udp协议对象
# 1.创建udp对象
sk = socket.socket(type=socket.SOCK_DGRAM)

# 2.发送数据
while True:
	# 发送数据
	message = input("请输入客户端发送的消息")
	sk.sendto(message.encode("utf-8") , ("127.0.0.1",9000) )

# 客户端接受服务端发过来的数据
msg, addr = sk.recvfrom(1024)
print(msg.decode("utf-8"))


# 3.关闭连接
sk.close()

三、利用hashlib模块进行加密校验

1、数据加密几种方式

import hashlib
import random

# 基本用法
# (1) 创建一个md5算法的对象
hs = hashlib.md5()
# (2) 把要加密的字符串通过update更新到hs对象中运算
hs.update("123456".encode("utf-8")) # 里面的数据时二进制字节流
# (3) 获取32位16进制的字符串
res = hs.hexdigest()
print(res , len(res))

# 加盐 (加key => Xboy_ 加一个关键字配合原字符串,让密码更加复杂,不容易破解)
hs = hashlib.md5("XBoy_^".encode())
hs.update("123456".encode())
res = hs.hexdigest()
print(res,len(res))

# 动态加盐
res = str(random.randrange(100000,1000000))
print(res)
hs = hashlib.md5(res.encode("utf-8"))
hs.update("123456".encode())
res = hs.hexdigest()
print(res)

# sha算法
"""
sha 算出来的十六进制的串是40位 加密稍慢 , 安全性稍高
md5 算出来的十六进制的串是32位 加密速度快,安全性一般
"""
hs = hashlib.sha1()
hs.update("123456".encode())
res = hs.hexdigest()
print(res, len(res))

hs = hashlib.sha512()
hs.update("123456".encode())
res = hs.hexdigest()
print(res, len(res))


# ### hmac
"""hmac 加密算法更加复杂,不容易破解"""
import hmac
# 必须指定盐
key = b"abc"
# 密码
msg = b"123456"
hm = hmac.new(key,msg)
res = hm.hexdigest()
print(res,len(res))

# 动态加盐
import os

"""
# 基本使用
# urandom 返回随机的二进制字节流, 参数:代表的长度
res = os.urandom(64)
print(res,len(res))
"""
key = os.urandom(32)
msg = b'123456'
hm = hmac.new(key,msg)
res = hm.hexdigest()
print(res,"<1>",len(res))

2、文件校验

# ### 文件校验
import hashlib
hm = hashlib.md5()
hm.update("123".encode())
res = hm.hexdigest()
print(res)

# (1) 针对于小文件进行内容校验
def check_md5(file):
	with open(file, mode="rb") as fp:
		hs = hashlib.md5()
		hs.update(fp.read())
	return hs.hexdigest()

res1 = check_md5("ceshi1.txt")
res2 = check_md5("ceshi2.txt")
print(res1,res2)

# (2) 针对于大文件进行内容校验
hs = hashlib.md5()
hs.update("今天是星期一".encode())
res = hs.hexdigest()
print(res) #a33fc073e6be76154e58874c4ac7cee1

hs = hashlib.md5()
hs.update("今天是".encode())
hs.update("星期一".encode())
res = hs.hexdigest()
print(res) # a33fc073e6be76154e58874c4ac7cee1

# 方法一
def check_md5(file):
	hs = hashlib.md5()
	with open(file,mode="rb") as fp:
		while True:
			# read(5) 一次最多读取5个字节,
			content = fp.read(5)
			# 如果有内容就进行计算
			if content:
				# 分批进行更新计算;
				hs.update(content)
			else:
				break
		return hs.hexdigest()

print("<=====>")
print(check_md5("ceshi1.txt"))
print(check_md5("ceshi2.txt"))

# 方法二
import os
def check_md5(file):
	hs = hashlib.md5()
	# 计算文件大小返回的字节的个数
	file_size = os.path.getsize(file)  # 计算文件大小
	with open(file,mode="rb") as fp:
		while file_size:
			# 一次最多5个
			content = fp.read(5)
			hs.update(content)
			# 按照实际的读取个数进行相减;
			file_size -= len(content)
		return hs.hexdigest()
print("<=====>")
print(check_md5("ceshi1.txt"))
print(check_md5("ceshi2.txt"))

猜你喜欢

转载自blog.csdn.net/weixin_39855998/article/details/122656101
今日推荐