1.TCP
2.补充异常处理
3.UDP
一TCP.
1.socket基础版本通讯
服务端:

1 import socket 2 3 server = socket.socket()# 生成一个对象 4 server.bind(('127.0.0.1',8080))# 绑定ip和端口 5 server.listen(5)# 连接池 6 7 conn,addr = server.accept()# 等待别人 一个是ip 一个是地址 阻塞 8 data = conn.recv(1024) # 听别人说话 阻塞 9 print(data) 10 conn.send(b'hello boby')# 给对方回信息 11 12 conn.close()# 断开链接 13 server.close()# 结束掉进程
客户端:

1 import socket 2 3 4 client = socket.socket()# 生成一个对象 5 6 client.connect(('127.0.0.1',8080))# 发送你的ip和端口号 7 8 client.send(b'hello word')# 对别人说话 9 data = client.recv(1024) # 听别人说话 10 print(data) 11 12 client.close()# 断开链接
2 升级版实现多条通讯
服务端:

1 import socket 2 3 server = socket.socket() # 创建一个对象 4 server.bind(('127.0.0.1',8989)) # 绑定 ip 和端口 5 server.listen(5)# 最大连接数 6 7 while True: 8 conn,addr = server.accept() # 等待 别人链接进来 ip和端口 # 等到别人来 conn就类似于是双向通道 9 print(conn) 10 print(addr)## ('127.0.0.1', 51323) 客户端的地址 11 while True: 12 try: 13 data = conn.recv(1024)# 接收数据 14 # 判断接收的数据为不为空 15 if not data:break## b'' 针对mac与linux 客户端异常退出之后 服务端不会报错 只会一直收b'' 16 conn.send(data.upper()) 17 except ConnectionResetError as e: 18 print(e) 19 break 20 conn.close()
客户端:

1 import socket 2 3 client = socket.socket() # 创建一个对象 4 client.connect(('127.0.0.1',8989))# 写入要链接的ip 和端口号 5 6 while True: 7 msg = input('请输入:>>>').encode('utf-8')# 发送数据 8 # 判断数据是否为空 9 if not msg:continue 10 client.send(msg) 11 # 接收数据 12 data = client.recv(1024) 13 print(data)
tcp的沾包问题
客户端:
import time import socket client = socket.socket() client.connect(('127.0.0.1',8080)) client.send(b'hello') # time.sleep(5) client.send(b'word')
服务端:
import socket server = socket.socket() server.bind(('127.0.0.1',8080)) server.listen(5) conn,addr = server.accept() data = conn.recv(5) print(data) data = conn.recv(5) print(data) ''' 可以规定接收的数据长度,但是你不知道对方每次要发多少数据,这个接收数据的1024不能随便改,所以说要想其他的方法 解决方案就是你要提前告知收方数据的长度 '''
那么沾包是如何产生的:
TCP流式协议, 数据之间没有分界, 就像水 一杯水和一杯牛奶倒在一起了! UDP 用户数据报协议 粘包 仅发生在TCP协议中 1. 发送端 发送的数据量小 并且间隔短 会粘 2. 接收端 一次性读取了两次数据的内容 会粘 3. 接收端 没有接收完整 剩余的内容 和下次发送的粘在一起 无论是那种情况,其根本原因在于 接收端不知道数据到底有多少 解决方案就是 提前告知接收方 数据的长度
那么如何解决沾包:
.TCP粘包问题
struct模块 对数据进行打包处理 固定长度
pack
unpack
服务端
1.生成一个字典
2.制作该字典的报头
json序列化
编码 统计长度
3.发送字典的报头
4.发送字典
5.最后发真实数据
客户端
1.先接受固定长度的4个字节字典报头
2.解析获取字典数据的长度
unpack(...)[0]
3.接受字典数据
解码 反序列化
4.接受真实数据
ps:为什么要多加一个字典
1.打包的数据大小有限
2.可以携带更多的信息
代码实现解决沾包问题:
客户端:

1 import socket 2 import struct 3 import json 4 5 client = socket.socket() 6 client.connect(('127.0.0.1',8080)) 7 8 while True: 9 msg = input('msg:>>>').encode('utf-8') 10 if not msg:continue 11 client.send(msg) 12 # 接收字典报头 13 header = client.recv(4) 14 # 解析报头,拿到真实的数据长度 15 header_dict = struct.unpack('i',header)[0] 16 # 接收字典数据 17 dict_bytes = client.recv(header_dict) 18 json_dict = json.loads(dict_bytes.decode('utf-8')) 19 # 从字典中获取信息 20 print(json_dict) 21 recv_size = 0 22 real_data = b'' 23 # 循环接收字典中的信息 24 while recv_size < json_dict.get('file_size'): 25 data = client.recv(1024) 26 recv_size +=len(data) 27 real_data +=data 28 print(data.decode('gbk')) 29 print(recv_size)
服务端:

1 import socket 2 import struct 3 import json 4 import subprocess 5 6 server = socket.socket() 7 server.bind(('127.0.0.1',8080)) 8 server.listen(5) 9 10 while True: 11 conn,addr = server.accept() 12 while True: 13 try: 14 cmd = conn.recv(1024) 15 if not cmd:break 16 cmd = cmd.decode('utf-8') 17 obj = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) 18 res = obj.stdout.read()+obj.stderr.read() 19 # 定义一个字典 20 d = {'name':'zy','file_size':len(res)} 21 json_d = json.dumps(d) 22 #1.制作字典报头 23 header = struct.pack('i',len(json_d)) 24 #2. 发送字典报头 25 conn.send(header) 26 #3.发送字典 27 conn.send(json_d.encode('utf-8')) 28 #4.发送正式数据 29 conn.send(res) 30 except ConnectionResetError as e: 31 print(e) 32 break 33 conn.close()
补充:
解决沾包问题需要用到的struct模块:

1 import struct 2 # 3 # res = 'dgbdushfgcbdsjhgbcshjdgcbidjhgvKADJFCNalskCNkfjcnLJCHSDNCKLSD' 4 # print('最开始的',len(res)) 5 # 6 # res1 = struct.pack('i',len(res)) 7 # print('打包好的',len(res1)) 8 # 9 # res2 = struct.unpack('i',res1)[0] # 如果不加索引解包后的结果就是解包后的 (61,),索引拿到的是0号位 10 # print('解包后的',res2) 11 12 13 14 ''' 15 当原始数据特别大的时候 i模式打包不了 需要更换模式? 16 如果遇到数据量特别大的情况 该如何解决? 17 可以定义一个字典 18 ''' 19 d = { 20 'name':'zy', 21 'file_size':'123473985961732617417324627346723641746723462374613274164', 22 'info':'你好', 23 } 24 25 import json 26 json_d = json.dumps(d) 27 print(len(json_d)) 28 29 res1 = struct.pack('i',len(json_d)) 30 print(len(res1)) 31 32 res2 = struct.unpack('i',res1)[0] 33 print('解包后的数据',res2)
补充:复习subprocess模块

1 import subprocess 2 3 while True: 4 cmd = input('cmg:>>>') 5 if cmd == 'q':break 6 if not cmd:continue 7 obj = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) 8 print(obj.stdout.read().decode('gbk'))# 正确命令返回的结果 9 print(obj.stderr.read().decode('gbk'))# 错误的命令返回的结果 10 11 #注意:subprocess获取到的数据,拿完就没有了,不能重复的拿
练习:大文件上传:
客户端:

1 mport struct 2 import json 3 import socket 4 import os 5 6 client = socket.socket() 7 client.connect(('127.0.0.1',8080)) 8 9 while True: 10 # 获取文件路径 11 MOVIE_DIR = r'H:python视频python10期day29视频' 12 # 显示文件里的电影 13 movie_list = os.listdir(MOVIE_DIR) 14 # 循环打印电影 15 for i,movie in enumerate(movie_list,1): 16 print(i,movie) 17 # 选取电影 18 choice = input('请选择电影:>>>').strip() 19 if choice.isdigit(): 20 choice = int(choice)-1 21 # 判断是否在索引范围内 22 if choice in range(0,len(movie_list)): 23 # 获取要上传电影的文件路径 24 path = movie_list[choice] 25 # 获取要上传电影的绝对路径,拼接 26 file_path = os.path.join(MOVIE_DIR,path) 27 # 获取文件的大小 28 file_size = os.path.getsize(file_path) 29 # 定义一个字典 30 d = { 31 'file_name':'qwer.mp4', 32 'file_size':file_size, 33 } 34 # 序列化字典 35 json_d = json.dumps(d) 36 json_bytes = json_d.encode('utf-8') 37 # 制作字典报头 38 header = struct.pack('i',len(json_bytes)) 39 # 发送字典报头 40 client.send(header) 41 # 发送字典数据 42 client.send(json_bytes) 43 # 循环发送文件数据 44 with open(file_path,'rb') as f: 45 for line in f: 46 client.send(line) 47 else: 48 print('序号不在索引范围内') 49 else: 50 print('请输入数字')
服务端:

1 import socket 2 import struct 3 import json 4 5 6 server = socket.socket() 7 server.bind(('127.0.0.1',8080)) 8 server.listen(5) 9 10 while True: 11 conn,addr = server.accept() 12 while True: 13 try: 14 # 先接收报头 15 header = conn.recv(4) 16 # 解析字典报头 17 header_len =struct.unpack('i',header)[0] 18 # 接收字典数据 19 json_bytes = conn.recv(header_len) 20 json_dict = json.loads(json_bytes.decode('utf-8')) 21 # 获取字典长度 22 len_size = json_dict.get('file_size') 23 # 循环接收文件数据 24 recv_size = 0 25 with open(json_dict.get('file_name'),'wb') as f: 26 while recv_size < len_size: 27 data = conn.recv(1024) 28 f.write(data) 29 recv_size+=len(data) 30 print('上传成功') 31 print(recv_size) 32 except ConnectionResetError as e: 33 print(e) 34 break 35 conn.close()
二.异常处理
什么是异常处理:
程序在运行过程中出现了不可预知的错误,并且该程序没有对应的处理机制’,
那么就会以异常的形式表示出来,造成的影响就是整个程序无法在正常运行了,
那么我们一般不建议使用异常处理,一般的异常都是可以通过修改代码可以防止
出现异常的,只有那么你知道会报错,而不能解决的,
举例:比如说你和别人在打电话,那么可能会出现一些意外,可能手机关机了,或者没信号了
所以说在无法解决的时候在使用异常处理
异常的结构:
1.异常的类型:NAMEERROR
2.异常的信息:name 'fdsdfsdf' is not defined
3.异常的位置:Traceback (most recent call last):
File "D:/python脱产10期视频/day29/01 异常处理.py", line 1, in <module>
fdsdfsdf
异常的种类:
分为俩大类:
1.语法上的错误:
是你程序启动立刻就能解决的,这种错误也是不能被容忍的,
语法上的错误,发现之后应该立即解决
2.逻辑上的错误:
这种错误是可以被容忍的,因为一眼看不出来,
针对逻辑上的错误,可以采用异常处理机制进行捕获
常见的错误类型:
NAMERROR 名字错误
SyntaxError 语法错误
eyError 键不存在
ValueError 值错误
IndexError 索引错误
如何避免:
异常处理:
在你认为可能会出现的bug的代码上方try一下:注意try内部的代码块越少越好
try:
可能出现的错误
except 出错的类型 as e: 将报错信息赋值给变量
出错之后的处理机制
三.UDP协议
udp协议简介
用户数据报协议,是OSI模型中属于传输层的协议
提供,不可靠的,不要求顺序的,数据量小的,速度快的传输服务
不可靠:
发送完成后不需要确认信息 并且立即删除缓存中的数据
不要求顺序:
当一个数据较大时 会分为多个数据报来传输,对方无法获知数据的顺序,以及是否完整
数据量较小的:
数据越大丢包的可能性越高 ,建议的数据量不要超过1472
速度快:
相对于TCP而言快很多 不需要确认信息 ,也不需要建立链接
udp协议的通讯流程:
如果TCP比喻为手机打电话的过程 那么UDP可以看做是对讲机
1.买机器 创建UDP的socket
2.固定频道 bind一个ip和端口
3.收发数据 recvfrom sendto
接收
1.买机器 创建UDP的socket
2.收发数据 recvfrom sendto
注意 不能先收 要收数据 必须明确端口号 没有端口号是不可能使用网络服务的
TCP 与 UDP 的其他区别
1.没有连接
2.不会粘包 每次发送都是一个独立的数据包
TCP 对数据完整性要求较高 : 在线支付 ,文字信息
UDP: 对数据不要求完整性 但是要快 : 视频 语音 游戏
udp协议的基本使用:
服务端:

1 import socket 2 3 server = socket.socket(type=socket.SOCK_DGRAM)# UDP协议 4 server.bind(('127.0.0.1',8080)) 5 ''' 6 UDP不需要设置半链接池,它也没有半连接池的概念 7 因为没有双向通道,不需要accept 直接就是通信循环 8 ''' 9 while True: 10 data,addr = server.recvfrom(1024) 11 print('数据',data)# 客户端发来的信息 12 print('地址',addr)# 客户端发来的地址 13 server.sendto(data.upper(),addr) 14 break
客户端:

1 import socket 2 3 client = socket.socket(type=socket.SOCK_DGRAM) 4 #不需要建立链接,直接进入通信循环 5 server_address = ('127.0.0.1',8080) 6 7 while True: 8 client.sendto(b'hello',server_address) 9 data,addr = client.recvfrom(1024) 10 print('服务端发来的数据',data) 11 print('服务端发来的地址',addr) 12 break
udp通讯是不会沾包的:
服务端:

1 import socket 2 3 server = socket.socket(type=socket.SOCK_DGRAM) 4 server.bind(('127.0.0.1',8080)) 5 6 data,addr = server.recvfrom(1024) 7 print(data) 8 data,addr1 = server.recvfrom(1024) 9 print(data) 10 data,addr2 = server.recvfrom(1024) 11 print(data)
客户端:

1 import socket 2 3 4 client = socket.socket(type=socket.SOCK_DGRAM) 5 server_address = ('127.0.0.1',8080) 6 7 client.sendto(b'hello',server_address) 8 client.sendto(b'hello',server_address) 9 client.sendto(b'hello',server_address)
小练习:udp实现简易版本的qq
服务端:

1 import socket 2 3 server = socket.socket(type=socket.SOCK_DGRAM) 4 server.bind(('127.0.0.1',8080)) 5 6 7 while True: 8 data,addr = server.recvfrom(1024) 9 print(data.decode('utf-8')) 10 msg = input('msg:>>>').strip() 11 server.sendto(msg.encode('utf-8'),addr)
客户端1:

1 import socket 2 3 client = socket.socket(type=socket.SOCK_DGRAM) 4 server_address = ('127.0.0.1',8080) 5 6 while True: 7 msg = input('msg:>>>').strip() 8 msg = '来自客户端1的信息:%s'%msg 9 client.sendto(msg.encode('utf-8'),server_address) 10 data,server_addr = client.recvfrom(1024) 11 print(data.decode('utf-8'))
客户端2:

1 import socket 2 3 client = socket.socket(type=socket.SOCK_DGRAM) 4 server_address = ('127.0.0.1',8080) 5 6 while True: 7 msg = input('msg:>>>').strip() 8 msg = '来自客户端2的信息:%s'%msg 9 client.sendto(msg.encode('utf-8'),server_address) 10 data,server_addr = client.recvfrom(1024) 11 print(data.decode('utf-8'))
客户端3:

1 import socket 2 3 client = socket.socket(type=socket.SOCK_DGRAM) 4 server_address = ('127.0.0.1',8080) 5 6 while True: 7 msg = input('msg:>>>').strip() 8 msg = '来自客户端3的信息:%s'%msg 9 client.sendto(msg.encode('utf-8'),server_address) 10 data,server_addr = client.recvfrom(1024) 11 print(data.decode('utf-8'))