python 网络传输
网络开发架构
C/S架构 : 需要安装一下才能使用
client 客户端 我们用的 需要安装的
server 服务端
B/S架构 : 百度 博客园 谷歌 码云
browser 浏览器
server 服务端
b/s和c/s什么关系?
B/S架构也是C/S架构中的一种
C/S架构的好处
可以离线使用/功能更完善/安全性更高
B/S架构的好处
不用安装就可以使用
统一PC端用户的入口
应用层 python
传输层 port udp tcp 四层路由 四层交换
网络层 ipv4 ipv6 路由器 三层交换机
数据链路层 mac arp协议 网卡 二层交换
物理层
osi五层协议 应用层 传输层 tcp协议 : 效率低 面向连接\可靠\全双工的通信 三次握手 客户端向服务器端发送syn请求, 服务端向客户端回复ack并发送syn请求, 客户端接收到请求之后再回复ack表示建立连接 由客户端的connect + 服务端的accept 四次挥手 客户端向服务端发送fin请求, 服务端回复ack确认 服务端向客户端发送fin请求, 客户端回复ack确认 有客户端的close和服务端的close udp协议 : 效率高 无连接的\不可靠 四层交换机 四层路由器 网络层 ip协议(ipv4 ipv6) 路由器\三层交换机 数据链路层 arp协议 地址解析协议 通过ip找到mac地址 交换机\网卡 : 单播 广播 组播 物理层
七层协议
应用层
表示层
会话层
传输层
网络层
数据链路层
物理层
tcp协议与 udp协议 区别
tcp协议 : 效率低 面向连接\可靠\全双工的通信
udp协议 : 效率高 无连接的\不可靠
三次握手
客户端向服务器端发送syn请求,
服务端向客户端回复ack并发送syn请求,
客户端接收到请求之后再回复ack表示建立
四次挥手
客户端向服务端发送fin请求,
服务端回复ack确认
服务端向客户端发送fin请求,
客户端回复ack确认
socket套接字
socket套接字充当的就是内置模块的角色
socket 套接字,它存在于传输层与应用层之间的抽象层
import socket sk=socket.socket() #创建一个server端口 sk.bind(("127.0.0.1",9001)) #申请操作系统的资源 sk.listen() #开始监听(可以接收)客户端给我的连接 conn,addr=sk.accept() #创建连接 conn是连接信息 addr是端口号 conn.send(b"hello") #发送内容 msg=conn.recv(1024) #收到字节 print(msg) conn.close() #挥手 断开连接 sk.close () #申请归还的操作系统资源
普通版
server
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import socket sk=socket.socket() #创建一个server端口 sk.bind(("127.0.0.1",9001)) #申请操作系统的资源 sk.listen() #开始监听(可以接收)客户端给我的连接 conn,addr=sk.accept() #创建连接 conn是连接信息 addr是端口号 conn.send(b"hello") #发送内容 msg=conn.recv(1024) #收到字节 print(msg) conn.close() #挥手 断开连接 sk.close () #申请归还的操作系统资源
client
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import socket sk=socket.socket() sk.connect(("127.0.0.1",9001)) msg=sk.recv(1024) print(msg) sk.send(b"beybey") sk.close()
server
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import socket sk=socket.socket() sk.bind(("127.0.0.1",9001))# 申请操作系统资源 sk.listen() while True: #为了和多个客户端进行握手 conn,addr=sk.accept() while True: send_msg=input(">>>") conn.send(send_msg.encode("utf-8")) if send_msg.upper()=="Q": break msg=sk.recv(1024).decode("utf-8") if msg.upper()=="Q":break print(msg) conn.close() sk.close() # str -encode('utf-8')-> bytes # str -encode('gbk')-> bytes
client
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import socket sk = socket.socket() sk.connect(("127.0.0.1",9001)) while True msg=sk.recv(1024) msg2=msg.decode("utf-8") if msg2.upper()=="Q":break print(msg,msg2) send_msg=input(">>>") sk.send(send_msg.encode("utf-8")) if send_msg.upper()=="Q":break sk.close()
注意:UDP协议不用sk.close()
server两发两收
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import socket sk = socket.socket(type = socket.SOCK_DGRAM) sk.bind(('127.0.0.1',9001)) while True: # print(sk.recv(1024)) # b'Your message' # print(sk.recvfrom(1024)) # (b'Your message', ('127.0.0.1', 60182)) 注意这个本身是个大元组 recv_msg,addr = sk.recvfrom(1024) # 如果这里用recv,能接收信息,但是无法获得对方IP地址和端口号。所以这里要用recvfrom同时获得消息和地址 print(recv_msg.decode('utf-8')) send_msg = input('>>>') sk.sendto(send_msg.encode('utf-8'),addr) improt socket sk=socket.socket(type = socket.SOCK_DGRAM) sk.bind(("127.0.0.1",9001)) while True: msg,addr=sk.recvfrom(1024) print(msg.decode("utf-8")) msg=input(">>>") sk.sendto(msg.encode('utf-8'),addr)
client:三发三收,都先if
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import socket sk = socket.socket(type=socket.SOCK_DGRAM) server = ('127.0.0.1',9001) while True: send_msg = input('>>>') if send_msg.upper() == 'Q': break sk.sendto(send_msg.encode('utf-8'),server) recv_msg = sk.recv(1024).decode('utf-8') #此时已经有对面的IP地址和端口了,无须再recvfrom if recv_msg.upper() == 'Q': break print(recv_msg)
上述程序中导致程序阻塞的语句
input():等待,直到用户输入enter键 sk.accept():阻塞,有客户端来和我建立完连接之后 sk.connect:阻塞,直到server端结束了对一个client的服务,开始和当前client建立连接的时候 recv():阻塞,直到收到对方发过来的消息之后 recvfrom():阻塞,直到收到对方发过来的消息之后
粘包:两条或更多条分开发送的数据连在一起的现象
粘包导致的原因:
1. 发送端 : 两条消息都很短,发送的间隔时间也非常短,由于优化机制就合并在一起发送了
2. 接收端 : 多条消息由于没有及时接收,而在接收方的缓存端堆在一起导致的粘包
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import socket sk = socket.socket() sk.bind(('127.0.0.1',9001)) sk.listen() conn,addr = sk.accept() msg1 = input('>>>').encode('utf-8') #注意要在len前先encode msg2 = input('>>>').encode('utf-8') num = str(len(msg1)) ret = num.zfill(4) conn.send(ret.encode('utf-8')) conn.send(msg1) conn.send(msg2) conn.close() sk.close()
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import socket sk = socket.socket() sk.connect(('127.0.0.1',9001)) length = int(sk.recv(4).decode('utf-8')) msg1 = sk.recv(length) msg2 = sk.recv(1024) print(msg1.decode('utf-8')) print(msg2.decode('utf-8')) sk.close()
精髓在于把第一条数据精确地规定成4字节,由于第一条数据是第二条数据的长度,因此第二条和第三条数据也不粘了。但是,当第二条数据长度很长,即第一条数据用4字节表示不下时,此方法失效,由此引出第二种方法——struct模块
struct.pack():把-2**23到2**23
范围内的整数转化成4字节bytes
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import struct num1 = 129469649 num2 = 123 num3 = 8 ret1 = struct.pack('i',num1) print(len(ret1)) ret2 = struct.pack('i',num2) print(len(ret2)) ret3 = struct.pack('i',num3) print(len(ret3)) print(struct.unpack('i',ret1)) print(struct.unpack('i', ret2)) print(struct.unpack('i', ret3))
server
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import struct import socket sk = socket.socket() sk.bind(('127.0.0.1',9001)) sk.listen() conn,addr = sk.accept() msg1 = input('>>>').encode('utf-8') #注意要在len前先encode msg2 = input('>>>').encode('utf-8') #使用struct.pack()把len(msg1)转化成固定长度为4的bytes blen = struct.pack('i',len(msg1)) conn.send(blen) conn.send(msg1) conn.send(msg2) conn.close() sk.close() # 粘包现象 # 只出现在tcp协议中,因为tcp协议 多条消息之间没有边界,并且还有一大堆优化算法 # 发送端 : 两条消息都很短,发送的间隔时间也非常短 # 接收端 : 多条消息由于没有及时接收,而在接收方的缓存短堆在一起导致的粘包 # 解决粘包问题的本质 :设置边界
client
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import struct import socket sk = socket.socket() sk.connect(('127.0.0.1',9001)) #使用struct.unpack()把接收到的4字节bytes转化成一个元组,元组的第一项就是即将接收的msg1的长度,进而保证了msg1和msg2不会粘在一起 length = struct.unpack('i',sk.recv(4))[0] msg1 = sk.recv(length) msg2 = sk.recv(1024) print(msg1.decode('utf-8')) print(msg2.decode('utf-8')) sk.close()
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
port json import struct import socket # 接收 sk = socket.socket() sk.bind(('127.0.0.1',9001)) sk.listen() conn,_ =sk.accept() msg_len = conn.recv(4) dic_len = struct.unpack('i',msg_len)[0] msg = conn.recv(dic_len).decode('utf-8') msg = json.loads(msg) with open(msg['filename'],'wb') as f: while msg['filesize'] > 0: content = conn.recv(1024) msg['filesize'] -= len(content) f.write(content) conn.close() sk.close()
client
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import os import json import struct import socket # 发送 sk = socket.socket() # sk.connect(('192.168.14.109',9012)) sk.connect(('127.0.0.1',9001)) # 文件名\文件大小 abs_path = r'D:\python22期\day28 课上视频\3.网络基础概念.mp4' filename = os.path.basename(abs_path) filesize = os.path.getsize(abs_path) dic = {'filename':filename,'filesize':filesize} str_dic = json.dumps(dic) b_dic = str_dic.encode('utf-8') mlen = struct.pack('i',len(b_dic)) sk.send(mlen) # 4个字节 表示字典转成字节之后的长度 sk.send(b_dic) # 具体的字典数据 with open(abs_path,mode = 'rb') as f: while filesize>0: content = f.read(1024) filesize -= len(content) sk.send(content) sk.close()
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import os import socket import hashlib secret_key = b'alex_sb' sk = socket.socket() sk.bind(('127.0.0.1',9001)) sk.listen() conn,addr = sk.accept() # 创建一个随机的字符串 rand = os.urandom(32) # 发送随机字符串 conn.send(rand) # 根据发送的字符串 + secrete key 进行摘要 sha = hashlib.sha1(secret_key) sha.update(rand) res = sha.hexdigest() # 等待接收客户端的摘要结果 res_client = conn.recv(1024).decode('utf-8') # 做比对 if res_client == res: print('是合法的客户端') # 如果一致,就显示是合法的客户端 # 并可以继续操作 conn.send(b'hello') else: conn.close() # 如果不一致,应立即关闭连
client
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import socket import hashlib secret_key = b'alex_sb979' sk = socket.socket() sk.connect(('127.0.0.1',9001)) # 接收客户端发送的随机字符串 rand = sk.recv(32) # 根据发送的字符串 + secret key 进行摘要 sha = hashlib.sha1(secret_key) sha.update(rand) res = sha.hexdigest() # 摘要结果发送回server端 sk.send(res.encode('utf-8')) # 继续和server端进行通信 msg = sk.recv(1024) print(msg)
第二种方法
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import socket import os import hmac sk=socket.socket() sk.bind(("127.0.0.1",218)) sk.listen(5) conn,addr = sk.accept() rand = os.urandom(32) conn.send(rand) h=hmac.new(b"alex_sb",rand) ret=h.digest() a=conn.recv(1024) if a==ret: print('是合法的客户端') # 如果一致,就显示是合法的客户端 # 并可以继续操作 conn.send(b'hello') else: conn.close()
client
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import socket import os import hmac sk=socket.socket() sk.connect(("127.0.0.1",218)) a=sk.recv(1024) h=hmac.new(b"alex_sb",a) ret=h.digest() sk.send(ret) msg=sk.recv(1024) print(msg)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import time import socketserver class Myserver(socketserver.BaseRequestHandler): def handle(self): conn = self.request while True: try: content = conn.recv(1024).decode('utf-8') conn.send(content.upper().encode('utf-8')) time.sleep(0.5) except ConnectionResetError: break server = socketserver.ThreadingTCPServer(('127.0.0.1',9001),Myserver) server.serve_forever()
clinet
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import socket sk = socket.socket() sk.connect(('127.0.0.1',9001)) while True: sk.send(b'hello') content = sk.recv(1024).decode('utf-8') print(content)
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import os import sys import json import struct import socket import hashlib # 登录成功 100 101 # 注册成功 102 103 # 上传成功 104 105 # 下载成功 106 107 def my_send(conn,dic): str_dic = json.dumps(dic) b_dic = str_dic.encode('utf-8') mlen = struct.pack('i', len(b_dic)) conn.send(mlen) # 4个字节 表示字典转成字节之后的长度 conn.send(b_dic) # 具体的字典数据 def download(): abs_path = r'D:\python22期\day28 课上视频\3.网络基础概念.mp4' filename = os.path.basename(abs_path) filesize = os.path.getsize(abs_path) dic = {'filename': filename, 'filesize': filesize} my_send(conn,dic) with open(abs_path, mode='rb') as f: while filesize > 0: content = f.read(1024) filesize -= len(content) conn.send(content) def my_recv(conn): msg_len = conn.recv(4) dic_len = struct.unpack('i', msg_len)[0] msg = conn.recv(dic_len).decode('utf-8') msg = json.loads(msg) return msg def get_md5(username,password): md5 = hashlib.md5(username.encode('utf-8')) md5.update(password.encode('utf-8')) return md5.hexdigest() def login(conn): flag = True while flag: # 登录 msg = my_recv(conn) with open('userinfo') as f: for line in f: name, pwd = line.strip().split('|') if name == msg['username'] and pwd == get_md5(name, msg['password']): res, flag = True, False break else: res = False dic = {'operate': 'login', 'result': res} my_send(conn, dic) # 接收 sk = socket.socket() sk.bind(('127.0.0.1',9001)) sk.listen() conn,_ =sk.accept() # 有了一个客户端来连接你 login(conn) # 接收消息,根据用户的选择进行上传/下载操作 opt_dic = my_recv(conn) if hasattr(sys.modules[__name__],opt_dic['operate']): getattr(sys.modules[__name__],opt_dic['operate'])() conn.close() sk.close()
client
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import os import sys import json import struct import socket def download(sk): # 下载 opt_dic = {'operate':'download'} my_send(sk,opt_dic) msg = my_recv(sk) with open(msg['filename'], 'wb') as f: while msg['filesize'] > 0: content = sk.recv(1024) msg['filesize'] -= len(content) f.write(content) def login(sk): while True: usr = input('用户名:').strip() pwd = input('密 码 :').strip() dic = {'username': usr, 'password': pwd} my_send(sk, dic) ret = my_recv(sk) if ret['operate'] == 'login' and ret['result']: print('登录成功') break else: print('登录失败') def my_recv(sk): # 接收 msg_len = sk.recv(4) dic_len = struct.unpack('i', msg_len)[0] msg = sk.recv(dic_len).decode('utf-8') msg = json.loads(msg) return msg def my_send(sk,dic): # 发送 str_dic = json.dumps(dic) b_dic = str_dic.encode('utf-8') mlen = struct.pack('i', len(b_dic)) sk.send(mlen) # 4个字节 表示字典转成字节之后的长度 sk.send(b_dic) # 具体的字典数据 sk = socket.socket() # sk.connect(('192.168.14.109',9012)) sk.connect(('127.0.0.1',9001)) login(sk) # 登录 # 上传\下载 opt_lst = ['upload','download'] for index,opt in enumerate(opt_lst,1): print(index,opt) num = int(input('请选择您要操作的序号 :')) getattr(sys.modules[__name__],opt_lst[num-1])(sk) sk.close()
基础版
server
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import json import struct import socket # 接收 sk = socket.socket() sk.bind(('127.0.0.1',931)) sk.listen() conn,_ =sk.accept() msg_len = conn.recv(4) dic_len = struct.unpack('i',msg_len)[0] msg = conn.recv(dic_len).decode('utf-8') msg = json.loads(msg) with open(msg['filename'],'wb') as f: while msg['filesize'] > 0: content = conn.recv(1024) msg['filesize'] -= len(content) f.write(content) conn.close() sk.close()
client
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
import os import json import struct import socket # 发送 sk = socket.socket() # sk.connect(('192.168.14.109',9012)) sk.connect(('127.0.0.1',9001)) # 文件名\文件大小 abs_path = r'D:\python22期\day28 课上视频\3.网络基础概念.mp4' filename = os.path.basename(abs_path) filesize = os.path.getsize(abs_path) dic = {'filename':filename,'filesize':filesize} str_dic = json.dumps(dic) b_dic = str_dic.encode('utf-8') mlen = struct.pack('i',len(b_dic)) sk.send(mlen) # 4个字节 表示字典转成字节之后的长度 sk.send(b_dic) # 具体的字典数据 with open(abs_path,mode = 'rb') as f: while filesize>0: content = f.read(1024) filesize -= len(content) sk.send(content) sk.close()