小结

随口补充:http协议:应用层,底层基于socket,无状态无连接,版本有1.0,1.1(目前主流) 最新2.0
上节课回顾:

  1. 加入通信循环,使客户端和服务端可以相互发送数据

  2. 加入连接循环, 可以接收多个客户端的连接请求

  3. 执行ssh命令的小案例subprocess
    -psutil模块,统计机器的内存占用率,硬盘占用率。。。。(有兴趣可以看)

  4. 粘包问题

    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协议是数据流协议)
#     客户端和服务端可以有一方没在线(因为不需要建立连接)