python网络编程
网络开发两大框架
c/s 架构 client server
b/s 架构 BRower server
c/s架构
B/S 架构
基础概念
一台主机两个重要标识
(1)mac地址:标记一台机器的物理地址 (不可变)
(2)ip地址:标记一台机器的逻辑地址 (可变)
网段
网段的作用,主要用来划分同一区域里的某些机器是否能够互相通信。在一个网段里可以不同过因特网,直接对话
判别的依据:如果IP地址和子网掩码相与得到的值相同就是同一网段
内网
以下地址为预留地址,永远不会被当做公网ip来分配
192.168.0.0 - 192.168.255.255
172.16.0.0 - 172.31.255.255
10.0.0.0 - 10.255.255.255
外网
在任何地方都可以访问的就是外网(排除防火墙的因素)
子网掩码
区分网段和主机
255.255.255.0 / 255.255.0.0 / 255.0.0.0
ip1:192.168.10.12 ip2:192.168.1.16
端口
"端口"是英文port的意译,是具体某个程序与外界通讯的出口。 取值范围:0~65535
局域网
在同一区域内由多台计算机互联形成通讯。【具有可重复的内网ip】
广域网
在不同区域内有多台计算机互联形成通讯。【具有唯一的公网ip】
交换机
对同一网段的不同机器之间进行数据转发的设备 [每一台机器和交换机相连,形成通信]
路由器
对不同网段的不同机器之间进行数据转发的设备 [每一个局域网和路由器相连,形成通信]
arp协议
每台主机都有arp缓存表 ,主要作用通过ip找mac的一个协议规则
【实现方式:通过交换机一次广播,一次单播找到的】
osi网络七层模型
人们按照分工不同把互联网协议从逻辑上划分了层级: osi4层,osi5层,osi7层 三类模型
局域网模型
广域网模型
tcp/udp协议
TCP(Transmission Control Protocol)一种面向连接的、可靠的、传输层通信协议(比如:打电话)
优点:可靠,稳定,传输完整稳定,不限制数据大小
缺点:慢,效率低,占用系统资源高,一发一收都需要对方确认
应用:Web浏览器,电子邮件,文件传输,大量数据传输的场景
UDP(User Datagram Protocol)一种无连接的,不可靠的传输层通信协议(比如:发短信)
优点:速度快,可以多人同时聊天,耗费资源少,不需要建立连接
缺点:不稳定,不能保证每次数据都能接收到
应用:IP电话,实时视频会议,聊天软件,少量数据传输的场景
客户端和服务端在建立连接时: 三次握手
客户端和服务端在断开连接时: 四次挥手
SYN 创建连接
ACK 确认响应
FIN 断开连接
三次握手
四次挥手
整体缩略图
socket
socket的意义:通络通信过程中,信息拼接的工具(中文:套接字)
开发中,一个端口只对一个程序生效,在测试时,允许端口重复捆绑 (开发时删掉)
在bind方法之前加上这句话,可以让一个端口重复使用
sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
tcp服务端客户端实现
server端步骤
(1)创建一个socket对象,默认安装tcp协议
(2)绑定ip和端口(注册主机)
(3)开启监听
(4)三次握手
(5)收发数据逻辑
(6)四次挥手
(7)退还端口
# 服务端 import socket # 1.创建一个socket对象 , 默认按照tcp协议创建 sk = socket.socket() # 2.绑定ip 和 端口 (在网络上注册该主机,让其他主机找到你) '''bind( 元组 ) 默认本地ip 127.0.0.1 (ip,端口) ''' sk.bind( ("127.0.0.1",9000) ) # 3.开启监听 sk.listen() # listen accept recv 都是阻塞;如果不满足条件,程序不会往下执行; # 5.收发数据 quit while True: # 4.三次握手 '''conn 是三次握手后的连接对象,addr是对方的ip和端口号''' conn,addr = sk.accept() while True: # 收消息 msg = conn.recv(1024) print(msg.decode()) # 发消息 message = input("请输入>>>") conn.send(message.encode("utf-8")) if message == "q": break # 6.四次挥手 conn.close() # 7.退还端口 sk.close()
client端步骤
(1)创建tcp对象
(2)与服务器主机建立连接
(3)收发逻辑
(4)关闭连接
# 客户端 import socket # 创建tcp对象 sk = socket.socket() # 直接与服务器主机建立连接 '''connect( 元组 ) (ip,端口号)''' sk.connect( ("127.0.0.1" , 9000) ) while True: # 发消息 message = input(">>>:") sk.send(message.encode("utf-8")) # 收消息 res = sk.recv(1024) if res == b'q': break print(res.decode("utf-8")) # 关闭连接 sk.close()
粘包
tcp协议在发送数据时会出现粘包现象
(1)数据粘包是因为在客户端/服务端都会有一个数据缓冲区,用来临时保存数据,保证完整的接受数据,缓冲区设置的都比较大
(2)在收发数据频繁时,tcp传输消息无边界,不清楚截取多少长度,导致客户端/服务端可能把多条数据当成一条,造成粘包
粘包出现的情况
(1)在发送端,由于两个数据短,发送的时间间隔短,在发送端形成粘包
(2)在接收端,两个数据几乎同时被发送到对方的缓存中,在接收端形成了粘包
总结:发送端,包之间时间间隔短或者接收端接受不及时,就会粘包。核心是因为tcp对数据无边界截取,不会按照发送的顺序判断
粘包示例:
# 服务端 import socket sk = socket.socket() sk.bind( ("127.0.0.1",9000) ) sk.listen() conn,addr = sk.accept() conn.send("hello,".encode("utf-8")) conn.send("world".encode("utf-8")) conn.close() sk.close() #客户端 import socket import time sk = socket.socket() sk.connect( ("127.0.0.1",9000) ) time.sleep(0.1) print(sk.recv(10)) print(sk.recv(10)) sk.close() # 执行结果 b'hello,worl' b'd'
解决粘包
# 服务端 import socket import struct sk = socket.socket() sk.bind( ("127.0.0.1" ,9000) ) sk.listen() conn,addr = sk.accept() # 收发数据逻辑 inp = input("请输入msg>>>:") msg = inp.encode("utf-8") # 把这个长度的数字转化成二进制字节流,然后发送给对面,按照这么大的长度进行截取 res = struct.pack("i",len(msg)) conn.send(res) conn.send(msg) conn.send("world".encode("utf-8")) # 四次挥手 conn.close() # 退还端口 sk.close() # 客户端 import socket import struct import time sk = socket.socket() sk.connect( ("127.0.0.1" ,9000) ) time.sleep(0.1) # 先接收要截取的长度是多少 n = sk.recv(4) n = struct.unpack("i",n)[0] # print(n) # 再去接收真实的数据,防止黏包 print(sk.recv(n)) print(sk.recv(10)) sk.close()
struct模块
(1)struct.pack 把任意长度的数字转化成具有固定长度的4个字节的值,组成字节流
(2)struct.unpack 把 4个字节的值恢复成原有的数据,返回的是元组
>>> import struct >>> res = struct.pack('i',10000) >>> res b"\x10'\x00\x00" >>> print(len(res)) 4 >>> res = struct.unpack('i',res)[0] >>> res 10000 >>> res = struct.pack('i',10000) >>> res = struct.unpack('i',res) >>> res (10000,)
粘包对比
tcp协议
优点:接收时数据之间无边界,有可能粘合几条数据成一条数据,造成黏包
缺点:不限制数据包的大小,稳定传输不丢包
udp协议
优点:接收时候数据之间有边界,传输速度快,不黏包
缺点:限制数据包的大小(受带宽路由器等因素影响),传输不稳定,可能丢包
tcp和udp协议
(1)对于数据包来说都可以进行拆包和解包,理论上来讲,无论多大都能分次发送
(2)tcp一旦发送失败,对方无响应(对方无回执),tcp可以选择再发,直到对应响应完毕为止
(3)udp一旦发送失败,是不会询问对方是否有响应的,如果数据量过大,易丢包
粘包场景
解决:应用场景在实时通讯时,需要阅读此次发的消息是什么
不解决:下载或者上传文件的时候,最后要把包都结合在一起,黏包无所谓
hashlib模块
hashlib模块是一堆加密算法的集合体,哈希算法的加密方式不止一种
md5在线解密
应用场景-校验
(1)用户密码 --> 加密,解密
(2)相关校验 --> 加密,解密
哈希算法
哈希算法也叫摘要算法,相同的数据始终得到相同的输出,不同的数据得到不同的输出
(1)哈希将不可变的任意长度的数据,变成具有固定长度的唯一值
(2)字典的键值对映射关系是通过哈希计算的,哈希存储的数据是散列(无序)
示例
基本用法
import hashlib import random hs = hashlib.md5() # 创建一个md5算法对象 hs.update("abc123".encode("utf-8")) # 把要加密的字符串update更新到hs对象中处理 res = hs.hexdigest() # 返回32位16进制的字符串 print(res,len(res)) # result """ e99a18c428cb38d5f260853678922e03 32 """
加盐
key 只有自己知道的关键字,目的是增加密码的复杂度
import hashlib import random hs = hashlib.md5("x".encode("utf-8")) hs.update("abc123".encode("utf-8")) res = hs.hexdigest() print(res) # result """ a191e01243ce9c58f4ac3ebe8622505d """
动态加盐
import hashlib import random res = str(random.randrange(1000,10000)) hs = hashlib.md5(res.encode("utf-8")) hs.update("abc123".encode("utf-8")) res = hs.hexdigest() print(res) # result """ 51114e76682b6b696a565a248a519b5b """
sha算法系列
import hashlib import random hs = hashlib.sha1() hs.update("abc123".encode()) res = hs.hexdigest() print(res,len(res)) # result """ 6367c48dd193d56ea7b0baad25b19455e529f5ee 40 """
hmac
import hashlib import random import hmac key = b"x" msg = b"abc123" hm = hmac.new(key,msg) res = hm.hexdigest() print(res,len(res)) # 随机返回长度为32位的二进制字节流 import os key = os.urandom(32) print(key,len(key)) hm = hmac.new(key,msg) res = hm.hexdigest() print(res) # result """ b1a7fbbe3502e9c1047989731380d228 32 b'abhw\x1e\xb4\xeeN\xb1\xde\x10\xa1\xbbG.\x9e\xdbx\xc1\xb9C\x8b\xa7\xba\x8d?\xca\xb4\xa6=,\x8f' 32 153fb21b4d8439e373bb886dd76d6e5e """
socketserver模块
网络协议最底层就是socket,基于原有socket模块又封装了一层,就是socketserver
socketserver为了实现tcp协议,server端的并发
# server import socketserver class MyServer(socketserver.BaseRequestHandler): def handle(self): # print(self.request.recv(1024).decode('utf-8')) # 相当于conn while True: msg = self.request.recv(1024).decode('utf-8') if msg == 'q': break print(msg) info = input('>>>') self.request.send(info.encode('utf-8')) if __name__ == '__main__': server = socketserver.ThreadingTCPServer(('127.0.0.1',8080),MyServer) server.serve_forever() #clinet import socket sk = socket.socket() sk.connect(('127.0.0.1',8080)) while True: msg = input('>>>') if msg == 'q': sk.send(b'q') break sk.send(msg.encode('utf-8')) ret = sk.recv(1024).decode('utf-8') print(ret) sk.close()
模块引入
(1)如果当前模块是作为主文件调用,__name__返回的是__main__ --> 主进程
(2)如果当前模块是通过import导入,__name__返回当前模块名 --> 子进程
(3)if __name__ == "__main__" 主要用来做测试,节省空间和效率
print(__name__) attr = "属性" def method(): print("方法") if __name__ == "__main__": method() # result """ __main__ 方法 """
用import引入上面内容后执行为上面文件名称即模块名