邵邵。

导航

socket

昨日内容

  • 面向对象复习(json序列化类)
# 对象,类,父类的概念
# 三大特性:封装,继承,多态
# 双下开头的方法(达到某个条件自动触发)
	__init__:对象实例化自动触发
	__str__:对象执行打印操作自动触发
	__call__:对象加括号时自动触发
# 反射:利用字符串操作对象的属性或方法
	hasattr
	getattr
	setattr
"""
json序列化非默认的python数据类型
	常见操作是手动转换字符串
	不常见操作重修cls指向的类
"""
  • 软件开发架构(To B----To C)
    B:商业化
    C:个人化

    • 本质:b/s架构本质也是c/s架构
  • 远程传输数据发展史
    所有的前沿技术几乎都诞生于军事
    要想实现远程传输需要满足"物理链接介质"

  • OSI七层协议
    应表会传网数物

  • 协议及硬件介绍

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

    • TCP:可靠协议,流式协议
      • 三次握手,建连接
      • 四次挥手,断连接
    • UDP:不可靠协议,数据报协议

概要

  • socket套接字编程
    • 掌握基本的客户端与服务端代码编写
  • 通信循环,链接循环,代码优化
  • TCP粘包现象(流式协议)
  • 数据报,报头制作,struct模块,封装形式

内容

1、socket套接字

要求:写一款可以数据交互的程序
只要涉及到远程数据交互必须要操作OSI七层协议,所以有现成的模块直接实现(socket模块)
架构先启动客户端,在启动服务端
image

1.1、socket套接字程序编写

1.1.1、服务端(最简单)
import socket


server = socket.socket()  # 默认基于网络TCP传输协议————>类似于买手机
server.bind(('0.0.0.0', 8080))  # 绑定IP&端口————>类似于插电话卡
server.listen(5)  # 半连接池————>类似于开机(注:接入客户端排队等待)

sock,address = server.accept()  # 监听————>三次握手的状态
data = sock.recv(1024)  # 接收客户端发送的消息(1024bytes)————>类似于电话拨通后听别人讲话
print(data)  # 打印出所说的话
sock.send(b'hello my big baby')  # 给别人说话
sock.close()  # 挂断电话
server.close()  # 电话关机

在python中bytes类型可以看成二进制
->:箭头函数,确认返回的类型
函数返回多个值,默认为元祖
养成阅读源代码的习惯

1.1.2、服务端(最简单)
import socket


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

client.send(b'hello')  # 讲话
data = client.revc(1024)  # 接收
print(data)  # 打印出接收的信息
client.close()  # 关机

1.2、自定义通讯循环及代码优化

上述代码问题:

  1. 客户端输入为空就会停住
  2. 服务端接收为空也会停住,注:mac&linux系统则不会
  3. 服务端重启频繁报端口错误
  4. 客户端异常关闭,服务端报错
    • 异常捕获解决
  5. 客户端结束,服务端同时结束
    • 链接循环解决
  6. 半连接池
    • 设置可以等待的客户端数量
1.2.1、服务端
import socket
from socket import SOL_SOCKET, SO_REUSEADDR

server = socket.socket()
server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
server.bind(('0.0.0.0', 8080))
server.listen(5)
while True:
	sock, address = server.accept()
	while True:
		try:
			data = sock.revc(1024)
			if len(data) == 0:continue
			print(data.decode('utf8'))
			sock.send('hello')
		except Excepint:
			break

1.2.2、客户端
import socket


cilent = socket.socket()
cilent.connect(('127.0.0.1', 8080))

while True:
	msg = input('请输入:').strip()
	if len(msg) == 0:continue
	cilent.send(msg.encode())
	data = cilent.recv(1024)
	print(data.decode('utf8'))  # 注:服务端为win系统是需要gbk解码

2、粘包问题

问题:

  • 当接收数据大于设定的recv时,数据传输管道内会剩余数据,下次再次recv时取出的数据还是剩余的数据问题。
  • 数据量小且发送间隔短,TCP协议一次性打包发送问题

原因:

  1. 数据管道内的数据没有被完全取出,下次命令执行之后取出的数据还是上次命令执行结果的剩余数据
  2. TCP协议自带的特性,当数据量比较小且时间间隔比较短的多次数据,TCP协议会自动打包成一个数据发送

解决:

  • 报头:能够标识即将到来的数据具体信息,包含数据量大小
    • 报头的长度必须是固定的
      image

2.1、解决粘包问题之struct模块

可以把一定量的数据编译成固定的数据大小(根据模式)
image
根据上述推理可以将即将发送的数据实际大小先发送过去
服务端:

import struct  # 导入可以转为固定字节的模块
import socket  # 导入网络传输模块
import json  # 导入json序列化模块


server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUDEADDR, 1)  # 解决服务端端口回收慢的问题
server.bind((0.0.0.0), 8080)  # 所有ip都可以访问
server.listen(5)  # 半连接池数量

while True:
	sock, address = server.accept()  # 监听
	while True:
		try:
			data = sock.recv(1024)  # 接收
			if len(data) == 0 :continue  # 如果接收为0
			print(data.encode())  # 打印接收并转码
			file_txt = 数据  
			file_txt_size = len(ifle_txt)
			dic = {
				'file_name': '数据',
				'file_size':file_txt_size
			}
			data_json = json.dunps(dic)  # 因为不能发送字典,所以序列化为json字符串
			user_data = len(data_json)  # 计算序列化字典的长度
			header = struct.pack('i',user_data)  # 将序列化的字典转为固定的字节
			
			sock.send(header)  # 现将序列化的字典发送过去
			sock.send(dic)  # 在发送字典
			sock.send(file_txt)  # 发送真实的数据

客户端:

import json
import socket
import struct


cilent = socket.socket()
cilent.connect((127.0.0.1), 8080)

while True:
	msg = input('请输入要下载的信息').strip()
	if len(msg) == 0 :continue
	cilent.send(msg.encode)
	data = cilent.recv(4)  # 因为第一次传过来的数据为4,所以设置接收四个字节
	dic_size = struct.unpack('i', data)[0]  # 解析出字典的大小
	dic = cilent.recv(dic_size)  # 接收字典
	dic_json = json.loads(dic_size)  # 将字典反序列化
	file_dic = dic_json,get('file_size')  # 取出字典内的数据
	file_size = cilent.recv(file_dic)  # 设置数据实际大小,并接收

上传文件代码

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)

posted on 2022-01-12 18:14  邵邵。  阅读(49)  评论(0)    收藏  举报