小结
随口补充:http协议:应用层,底层基于socket,无状态无连接,版本有1.0,1.1(目前主流) 最新2.0
上节课回顾:
-
加入通信循环,使客户端和服务端可以相互发送数据
-
加入连接循环, 可以接收多个客户端的连接请求
-
执行ssh命令的小案例subprocess
-psutil模块,统计机器的内存占用率,硬盘占用率。。。。(有兴趣可以看) -
粘包问题
tcp协议是流式协议
tcp协议会把数据量较小,时间间隔较短的数据,一次性发送
5. 解决粘包问题(代码略)
今日内容:
基于socket的udp
重点:sendto recvfrom
udp协议的特点:
1.可以发空(数据报协议,自带头)
2.不需要建连接
3.不会粘包
4.不可靠(客户端、服务端谁断开都不受影响)
socketserver的使用(并发)
-tcp的服务端
-server=ThreadingTCPServer 对象
-server.serve_forever()
-写一个类,类里面重写handler方法,方法内收发数据(实现并发起来了)
-udp的服务端
-server=ThreadingUDPServer 对象
-server.serve_forever
-写一个类,类里重写handler方法,方法内收发数据(实现并发)
-self.request(tcp/udp是不一样的)
-self.client_address 客户端地址
socketserver源码分析
-ThreadingTCPServer: init:创建socket,bind,listen
-server.serve_forever(): 创建线程,建立连接,和处理通信
上节课回顾
json模块
import json
# 把字典格式转成json格式字符串
#dic = {"name": "lqz", "xx": False, "yy": None}
#print(type(dic)) #<class 'dict'>
#dic = json.dumps(dic)
# print(dic) #{"name": "lqz", "xx": false, "yy": null} /json字符串
# print(type(dic)) #<class 'str'>
# 以下不是json字符串,注意,就是普通的字符串类型
# dic2 = str(dic)
# print(dic2) # {'name': 'lqz', 'xx': False, 'yy': None}
# print(type(dic2)) #<class 'str'>
'''
####### 用法:
json.dumps() # 把数据类型转成json字符串
json.dump() # 把数据类型转成json字符串,并存到文件中
json.loads() # 把json格式字符串转成数据类型 (字典、列表等)
json.load() # 从文件中把json格式字符串转成数据类型
'''
# 不是json格式字符串转换,直接报错
# ss = 'falsedddddd'
# # # json.loads() # 从python3.6以后,支持bytes格式直接转
# s = json.loads(ss) # 报错,ss不是json格式字符串
# print(type(s))
# print(s)
# sss = {"name": "lqz", "xx": False, "yy": None}
# s = json.dumps(sss) #{"name": "lqz", "xx": false, "yy": null}
# s = json.loads(sss)
# print(s)
'''
import os
size = os.path.getsize('E:老男孩学习内容与笔记day32作业client.py')
#获取一个文件里面所有的字节数量
print(size) # 4140
'''
服务端
import socket
server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)
while True: # 连接循环
#一旦有个客户连接成功,会返回两个值,如果没有,会一直卡在这
print('等待客户端连接: ')
conn, addr = server.accept()
print('有个客户端已连接', addr)
while True: # 通信连接
# 卡在这里,等待客户端来发
print('等待接收数据:')
data = conn.recv(1024)
print(data)
conn.send(data.upper())
conn.close()
server.close()
客户端
import socket
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect(('127.0.0.1', 8080))
while True:
msg = input('请输入你要发送的数据:')
client.send(msg.encode('utf-8'))
data = client.recv(1024)
print(data)
client.close()
服务端——粘包
import socket
import os
import json
import struct
server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)
def download_interface():
while True: # 连接循环
# 一旦有客户连接成功,会返回两个值(conn, addr),如果没有,会卡在这里
conn, addr = server.accept()
while True: #通信循环
#1.加个文件头
#{'size': os.path.getsize(), name:'xxx.py'}
#2.计算一下头的长度
dic = {'size': os.path.getsize('json模块.py'), 'name': 'xxx.py'}
#3.转成bytes格式, 此处以json模块为例
dic_s = json.dumps(dic)
dic_b = bytes(dic_s,encoding='utf-8')
#4.获得长度
dic_b_len = len(dic_b)
# 通过struct模块 将数据长度打包成固定长度字节
obj = struct.pack('i', dic_b_len)
# 5.发送四个字节
conn.send(obj)
# 6.发送头部内容
conn.send(dic_b)
# 7.直接以+形式打开文件,就不需要转换了 rb模式
with open('json模块.py', 'rb')as f:
#conn.send(f.read()) 不能这样运行,如果文件过大,内存无法负载
# 注:文件是可以迭代的
for line in f:
conn.send(line)
conn.close()
server.close()
def upload_interface():
while True: # 连接循环
# 一旦有客户连接成功,会返回两个值(conn, addr),如果没有,会卡在这里
conn, addr = server.accept()
while True: # 通信循环 (下载)
#1.先收4个字节
head=conn.recv(4)
#2.取出头部的长度
head_len = struct.unpack('i', head)[0]
#3.接收头部
head = conn.recv(head_len)
#4.通过json反序列化拿到原字典
dic = json.loads(head)
#5.拿到字典后取出内容真正的长度,以及文件名
l = dic['size']
name = dic['name']
# 准备接收
count = 0
data = b''
with open(name, 'wb')as f:
while count < l:
if l < 1024:
data_temp = conn.recv(l)
else:
if l - count >= 1024:
data_temp = conn.recv(1024)
print(data_temp)
else:
data_temp = conn.recv(l - count)
data += data_temp
count += len(data_temp)
f.write(data)
print('上传完成')
conn.send('ok'.encode())
客户端——粘包
import socket
import struct
import json
import os
user_info = {'user': None}
client = socket.socket()
client.connect(('127.0.0.1', 8080))
def register():
while True:
print('欢迎来到注册功能')
if not user_info['user']:
username = input('username').strip()
pwd = input('pwd').strip()
re_pwd = input('pwd')
if re_pwd == pwd:
with open('user_info.test', 'w',encoding='utf-8')as f:
f.write(f'{username}:{pwd}')
print(f'{username}注册成功')
break
else:
print('两次密码不一致')
def login():
while True:
print('欢迎来到登录功能:')
username = input('username:').strip()
pwd = input('pwd:').strip()
inp_user_info = f'{username}:{pwd}'
with open('user_info.test', 'r',encoding='utf-8')as f:
userinfo = f.read()
if inp_user_info not in userinfo:
print('账户或密码错误')
break
else:
print(f'{username}登录成功')
user_info['user'] = username
def download():
while True: # 通信循环 (下载)
msg = input('输入任意键下载:')
#1.先收4个字节
head=client.recv(4)
#2.取出头部的长度
head_len = struct.unpack('i', head)[0]
#3.接收头部
head = client.recv(head_len)
#4.通过json反序列化拿到原字典
dic = json.loads(head)
#5.拿到字典后取出内容真正的长度,以及文件名
l = dic['size']
name = dic['name']
# 准备接收
count = 0
data = b''
with open(name, 'wb')as f:
while count < l:
if l < 1024:
data_temp = client.recv(l)
else:
if l - count >= 1024:
data_temp = client.recv(1024)
print(data_temp)
else:
data_temp = client.recv(l - count)
data += data_temp
count += len(data_temp)
f.write(data)
print('接收完成')
client.close()
def upload():
while True: #通信循环 (上传)
msg = input('输入任意键上传:')
#1.加个文件头
#{'size': os.path.getsize(), name:'xxx.py'}
#2.计算一下头的长度
dic = {'size': os.path.getsize('json模块.py'), 'name': 'xxx.py'}
#3.转成bytes格式, 此处以json模块为例
dic_s = json.dumps(dic)
dic_b = bytes(dic_s,encoding='utf-8')
#4.获得长度
dic_b_len = len(dic_b)
# 通过struct模块 将数据长度打包成固定长度字节
obj = struct.pack('i', dic_b_len)
# 5.发送四个字节
client.send(obj)
# 6.发送头部内容
client.send(dic_b)
# 7.直接以+形式打开文件,就不需要转换了 rb模式
with open('json模块.py', 'rb')as f:
#conn.send(f.read()) 不能这样运行,如果文件过大,内存无法负载
# 注:文件是可以迭代的
for line in f:
client.send(line)
data = client.recv(1024)
print(data.decode())
client.close()
func_dic = {'1': register,
'2': login,
'3': download,
'4': upload
}
def run():
while True:
print('''
1. 注册
2. 登录
3. 下载
4. 上传
q. 退出
''')
choice = input('请选择需要的功能:')
if choice == 'q':
break
if choice not in func_dic:
print('选择不存在')
continue
func_dic.get(choice)()
socketserver模块
socketserver_服务端
# 使用socketserver 写服务端
# 导入模块
import socketserver
# 自己定义一个类,必须继承BaseRequestHandler
class MyTcp(socketserver.BaseRequestHandler):
# 必须重写 handler 方法
def handler(self):
try:
while True: # 通信循环
print(self)
# 给客户端回消息
# conn 对象就是 request
# 接收数据
print(self.client_address)
data = self.request.recv(1024)
print(data)
if len(data) == 0:
return
# 发送数据
self.request.send(data.upper())
except Exception:
pass
if __name__ == '__main__':
# 实例化得到一个tcp链接的对象,Threading意思是说,只要来了请求,它自动的开线程来处理连接跟交互数据
# 第一个参数是绑定的地址,第二个参数是传一个类
server = socketserver.ThreadingTCPServer(('127.0.0.1',8089), MyTcp)
#一直在监听
# 这么理解:只要来了一个请求,就起一个线程(造一个人,做交互)
server.serve_forever()
client_客户端
import socket
import time
soc = socket.socket()
soc.connect(('127.0.0.1', 8089))
while True:
soc.send('xxxxxx'.encode('utf-8'))
data = soc.recv(1024)
print(data)
socketserver模块的udp
socketserver_服务端
# 使用socketserver写服务端
# 导入模块
import socketserver
#自己定义一个类,必须继承BaseRequestHandler
class MyTcp(socketserver.BaseRequestHandler):
def handler(self):
print(self)
#数据
print(self.request[0])
print(self.request[1])
print(type(self.request[1]))
self.request[1].sendto('xxxx'.encode('utf-8'), self.client_address)
# try:
# while True: 通信循环
# data = self.request.recvfrom(1024)
# print(data)
# except Exception:
# pass
if __name__ == '__main__':
# 实例化得到一个tcp连接的对象,Threading意思是说,只要来了请求,它自动的开线程来处理连接跟交互数据
#第一个参数是绑定的地址,第二个参数传一个类
server = socketserver.ThreadingUDPServer(('127.0.0.1',8081), MyTcp)
#一直在监听
server.serve_forever()
client_客户端
import socket
#udp
client = socket.socket(type=socket.SOCK_DGRAM)
client.sendto('lqz'.encode('utf-8'),('127.0.0.1',8080))
# client.sendto('hello'.encode('utf-8'),('127.0.0.1',8080))
# client.sendto(''.encode('utf-8'),('127.0.0.1',8080))
data = client.recvfrom(1024)
print(data)
client2_服务端
import socket
import time
soc = socket.socket()
soc.connect(('127.0.0.1',8081))
while True:
soc.send('yyy'.encode('utf-8'))
print(soc.recv(1024))
udp协议
udp_服务端
# 基本版本
# import socket
# # udp
# server = socket.socket(type=socket.SOCK_DGRAM)
#
# server.bind(('127.0.0.1',8080))
#
# # udp不要建立连接,直接发
# #需不需要监听
# # 跟tcp的是不一样的
# ##data = server.recvfrom(1024)
# data,addr = server.recvfrom(1024)
# ## data是一个元组,一个参数是数据部分,第二个参数是客户地址
# print(data)
# server.sendto(data.upper(),addr)
# 加入通信循环
import socket
#udp
server = socket.socket(type=socket.SOCK_DGRAM)
server.bind(('127.0.0.1',8080))
while True:
data, addr = server.recvfrom(1024)
print(data)
server.sendto(data.upper(), addr)
udp_客户端
# import socket
# ## udp
# client = socket.socket(type=socket.SOCK_DGRAM)
#
# # 直接发
# client.sendto(b'lqz',('127.0.0.1', 8080))
# data = client.recvfrom(1024)
# print(data)
#加入通信循环
import socket
#udp
client = socket.socket(type=socket.SOCK_DGRAM)
while True:
msg = input('>>>:')
#直接发
client.sendto(msg.encode('utf-8'),('127.0.0.1', 8080))
data = client.recvfrom(1024)
print(data)
udp协议是否丢包
udp_服务端
# udp 协议没有粘包问题(udp协议又叫数据报协议),可以发空,tcp协议不行
import socket
server = socket.socket(type=socket.SOCK_DGRAM)
server.bind(('127.0.0.1', 8080))
while True:
data, addr = server.recvfrom(1024)
print(data)
server.sendto(data.upper(), addr)
udp_客户端
import socket
client = socket.socket(type=socket.SOCK_DGRAM)
while True:
msg = input('>>>:')
client.sendto(msg.encode('utf-8'), ('127.0.0.1', 8080))
data = client.recvfrom(1024)
print(data) #(b'HELLO', ('127.0.0.1', 8080))
print(data[0]) #b'HELLO' 通过索引来取出数据内容
# 注:此处客户端接收“client.recvfrom(1024)”会返回两个值,一个是数据,一个是地址,如果只用一个变量接收,那么会的到一个元组,里面包换数据及服务端ip地址
# data, addr = client.recvfrom(1024)
# print(data) #'HELLO'
# print(addr) #('127.0.0.1', 8080)
udp_客户端丢包
import socket
# udp 不会管客户端或者服务端是否收到,它只管发,所以不可靠
client = socket.socket(type=socket.SOCK_DGRAM)
while True:
msg = input('>>>:')
client.sendto(msg.encode('utf-8'),('127.0.0.1', 8080))
data, addr = client.recvfrom(1024)
print(data)
# udp特点:
# 可以发空(udp协议是数据报协议,自带头,tcp协议是数据流协议)
# 客户端和服务端可以有一方没在线(因为不需要建立连接)