2、网络并发编程--套接字编程、黏包问题、struct模块、制作简易报头、上传文件数据

昨日内容回顾

  • 面向对象复习(json序列化类)

    对象、类、父类的概念
    三大特性:封装 继承 多态
    双下开头的方法(达到某个条件自动触发)
        __init__:对象实例化自动触发
        __str__:对象执行打印操作自动触发
        __call__:对象加括号调用的时候自动触发
      	...
    反射:利用字符串操作对象的属性或方法
        hasattr,getattr
        
        class MyClass(object):
        name = 'cx'
        
        def __call__(self, *args, **kwargs):
            print('lalala')
    
        def __str__(self):
            return 'print'  # 必须return字符串类型
    
        def run(self):
            print('run fun')
    
        obj = MyClass()
        obj()  # 调用对象触发__call__()
        print(obj)  # 触发__str__()
    
        if hasattr(obj, 'name'):
            getattr(obj, 'run')()
    
        print(hasattr(obj, 'name'))
        print(getattr(obj, 'nonono'))  # getattr没找到属性则会报错,所以有hasattr来判断
        
    json序列化非默认的python数据类型
    	常见操作是手动转字符串
    	不常见操作重写cls指向的类
    
  • 软件开发架构

    c/s架构
    b/s架构
    本质:b/s架构也c/s架构
    
  • 远程传输数据的发展史

    所有前言的技术几乎都是诞生于军事
    
    要想实现远程传输首先需要满足的时候"物理连接介质"
    
  • OSI七层协议

    应、表、会、传、网、数、物
    
    应用层
    传输层
    网络层
    数据链路层
    物理连接层
    
  • 各种协议及常见硬件介绍

    # 物理连接层
    	网线 网卡 
    # 数据链路层
    	电信号分组方式、以太网协议
        	mac地址 12位16进制数
            	mac地址只能在局域网内实现数据交互
    交换机
    路由器
    局域网
    互联网
    	上网其实就是顺着网线访问其他计算机上面的资源(网络只有更安全)
    广播与单播
    	广播风暴
    # 网络层
    	IP协议
                IP地址用于表示接入互联网的一台计算机
            IPV4与IPV6
    	PORT协议
        		端口地址用于表示计算机上面某一个应用程序
        	动态分配、范围限制(0-65535)
        '''IP+PORT:唯一标识计算机上面的某一个应用程序'''
    # 传输层
    	TCP、UDP
    # 应用层
    	HTTP、FTP、HTTPS
    
  • TCP与UDP

    TCP	可靠协议 流式协议
    	三次握手建链接
        四次挥手断链接
    UDP 不可靠协议 数据报协议
    	
    """
    TCP类似于打电话
    UDP类似于发短信
    """
    

今日内容概要

  • socket套接字编程

    掌握基本的客户端与服务端代码编写

  • 通信循环

  • 代码健壮性校验

  • 链接循环

  • TCP黏包现象(流式协议)

  • 报头制作、struct模块、封装形式

今日内容详细

socket套接字编程

要求:我们自己想写一款可以数据交互的程序
# 只要涉及到远程数据交互必须要操作OSI七层 所以有现成的模块直接实现

socket模块
	
架构启动肯定是先启动服务端再启动客户端

简易代码

![image-20220112172629280](C:\Users\xiao's computer\AppData\Roaming\Typora\typora-user-images\image-20220112172629280.png)

# 服务端 6步
import socket
server = socket.socket()  # 默认就是基于网络的TCP传输协议   
server.bind(('127.0.0.1', 8080))  # 绑定ip和port        
server.listen(5)  # 半连接池            
sock, address = server.accept()  # 监听   
print(address)  # 客户端地址
data = sock.recv(1024)  # 接收客户端发送的消息  
print(data)
sock.send(b'hello my big baby~~~') 
sock.close()  
server.close()  

# 客户端 4步
import socket
client = socket.socket()  
client.connect(('127.0.0.1', 8080))  
client.send(b'hello big DSB DSB DSB!')
data = client.recv(1024)
print(data)
client.close()

通信循环及代码优化

1.客户端校验消息不能为空
2.服务端添加兼容性代码(mac linux)
3.服务端重启频繁报端口占用错误
	from socket import SOL_SOCKET, SO_REUSEADDR
    server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)  # 在bind前加
4.客户端异常关闭服务端报错的问题
	异常捕获
5.服务端链接循环
6.半连接池
	设置可以等待的客户端数量

黏包现象

TCP协议黏包特性:
	"""
	1、数据管道的数据太大没有被完全取出
	
	2、当数据量比较小 且时间间隔比较短的多次数据
	那么TCP会自动打包成一个数据包发送
	"""
    
解决思路:报头
	能够标识即将到来的数据具体信息
	eg:数据量多大 
	# 报头的长度必须是固定的

struct模块

import struct
import json


d = {
    'file_name': '很好看.mv',
    'file_size': 1231283912839123123424234234234234234324324912,
    'file_desc': '拍摄的很有心 真的很好看!!!',
    'file_desc2': '拍摄的很有心 真的很好看!!!'
}
d = json.dumps(d)
res = struct.pack('i',len(d))
print(len(res))
res1 = struct.unpack('i',res)[0]
print(res1)

subprocess模块

import subprocess

sub = subprocess.Popen('ipconfig', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)

print(sub.stdout.read().decode('gbk'))
print(sub.stderr.read().decode('gbk'))

简易版本报头

# 服务端
import socket
import subprocess
import json
import struct

server = socket.socket()
server.bind(('127.0.0.1', 8080))
server.listen(5)

while True:
    sock, address = server.accept()
    while True:
        data = sock.recv(1024)  # 接收cmd命令
        command_cmd = data.decode('utf8')
        sub = subprocess.Popen(command_cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        res = sub.stdout.read() + sub.stderr.read()  # 结果可能很大
        # 1.制作报头
        data_first = struct.pack('i', len(res))
        # 2.发送报头
        sock.send(data_first)
        # 3.发送真实数据
        sock.send(res)
 
# 客户端
import socket
import struct

client = socket.socket()  # 买手机
client.connect(('127.0.0.1', 8080))  # 拨号

while True:
    msg = input('请输入cmd命令>>>:').strip()
    if len(msg) == 0:
        continue
    client.send(msg.encode('utf8'))
    # 1.先接收固定长度为4的报头数据
    recv_first = client.recv(4)
    # 2.解析报头
    real_length = struct.unpack('i',recv_first)[0]
    # 3.接收真实数据
    real_data = client.recv(real_length)
    print(real_data.decode('gbk'))

上传文件数据

# 客户端
import json
import socket
import struct
import os

client = socket.socket()  # 买手机
client.connect(('127.0.0.1', 8080))  # 拨号

while True:
    data_path = r'D:\金牌班级相关资料\网络并发day01\视频'
    # print(os.listdir(data_path))  # [文件名称1 文件名称2 ]
    movie_name_list = os.listdir(data_path)
    for i, j in enumerate(movie_name_list, 1):
        print(i, j)
    choice = input('请选择您想要上传的电影编号>>>:').strip()
    if choice.isdigit():
        choice = int(choice)
        if choice in range(1, len(movie_name_list) + 1):
            # 获取文件名称
            movie_name = movie_name_list[choice - 1]
            # 拼接文件绝对路径
            movie_path = os.path.join(data_path, movie_name)
            # 1.定义一个字典数据
            data_dict = {
                'file_name': 'XXX老师合集.mp4',
                'desc': '这是非常重要的数据',
                'size': os.path.getsize(movie_path),
                'info': '下午挺困的,可以提神醒脑'
            }
            data_json = json.dumps(data_dict)
            # 2.制作字典报头
            data_first = struct.pack('i', len(data_json))
            # 3.发送字典报头
            client.send(data_first)
            # 4.发送字典
            client.send(data_json.encode('utf8'))
            # 5.发送真实数据
            with open(movie_path,'rb') as f:
                for line in f:
                    client.send(line)
                    
  
# 服务端/接收端
# 1.先接收固定长度为4的字典报头数据
        recv_first = sock.recv(4)
        # 2.解析字典报头
        dict_length = struct.unpack('i', recv_first)[0]
        # 3.接收字典数据
        real_data = sock.recv(dict_length)
        # 4.解析字典(json格式的bytes数据 loads方法会自动先解码 后反序列化)
        real_dict = json.loads(real_data)
        # 5.获取字典中的各项数据
        data_length = real_dict.get('size')
        file_name = real_dict.get("file_name")

        recv_size = 0
        with open(file_name,'wb') as f:
            while recv_size < data_length:
                data = sock.recv(1024)
                recv_size += len(data)
                f.write(data)

扩展知识

在阅读源码的时候
	1.变量名后面跟冒号 表示的意思是该变量名需要指代的数据类型
	2.函数后更横杆加大于号表示的意思是该函数的返回值类型

作业

1.cs架构的简易版本视频管理系统
	电影的上传功能
	电影的下载功能
posted @ 2022-01-12 17:35  简爱cx  阅读(56)  评论(0编辑  收藏  举报