24.网络编制【二】

【一】TCP的三次握手和四次挥手

  • TCP协议位于OSI七层协议中的传输层
  • 使用三次握手建立连接
  • 使用四次挥手断开连接
SYN:SYN=1	表示要建立连接
ACK:ACK=1	表示收到,允许
seq:随机码,建立连接无论客户端还剩服务端都要携带
ack:回应请求就要加1返回
FIN:断开连接

1)三次握手

发生在建立连接的阶段

  • 第一次亲求

    • 由客户端发起请求
    • SYN=1(表示自己是客户端要建立连接)
    • seq(发送给服务端)
      • 客户端 ---> 携带 SYN和SEQ ---> 发送给服务端
  • 第二次请求

    • 服务端接收到客户端的请求
    • ACK = 1 (表示收到了当前客户端发送的请求)
    • SYN = 1 (表示要建立连接)
    • seq(随机数)
      • 服务端 ---> 接收到客户端的请求,同意建立连接 ---> 发送给客户端
  • 第三次请求

    • 客户端接收到了服务器的请求
    • ACK = 1 (表示收到了当前服务端发送的请求)
    • SYN = 1 (表示要建立连接)
    • seq(随机码)
      • 和服务端建立连接成功

2)四次挥手

发送在断开连接上

  • 第一次
    • 客户端向服务端发送断开连接的请求
  • 第二次
    • 服务端接收到客户端的请求
    • 表示同意断开连接
  • 第三次
    • 服务端向客户端发送请求,表示当前还有数据未传输完成,请求等待数据传输完成
    • 服务端向客户端发送断开请求
  • 第四次
    • 客户端接收到服务端的请求
    • 直接断开连接

【二】UDP协议

  • setting.py

    # 创建(ip,端口)
    ip_port = ('127.0.0.2', 8003)
    
  • client.py

    # 客户端
    import setting
    import socket
    
    # 【一】创建一个client对象
    # AF_INET:当前连接是基于网络的套接字
    # SOCK_DGRAM:连接模式是UDP协议的报式模式
    client = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
    # 【二】直接发送数据
    send_server_data = input('向服务器发送的信息:')
    # 数据编码成二进制
    send_server_data = send_server_data.encode()
    # 向(ip,端口)传输数据
    client.sendto(send_server_data, setting.ip_port)
    # print(client)
    # <socket.socket fd=300, family=2, type=2, proto=0, laddr=('0.0.0.0', 53325)>
    # 【三】接受到服务端回的消息
    # 接收来自(ip,端口)的数据(最大为1024字节)
    receive_server_data, addr = client.recvfrom(1024)
    # 数据解码
    receive_server_data = receive_server_data.decode()
    print(f'接收到的消息:{receive_server_data}')
    # 【四】关闭连接
    client.close()
    
  • server.py

    # 服务端
    import setting
    import socket
    
    # 【一】创建一个server对象
    # AF_INET:当前连接是基于网络的套接字
    # SOCK_DGRAM:连接模式是UDP协议的报式模式
    server = socket.socket(family=socket.AF_INET, type=socket.SOCK_DGRAM)
    # 【二】绑定IP和PORT
    server.bind(setting.ip_port)
    # print(server)
    # <socket.socket fd=180, family=2, type=2, proto=0, laddr=('127.0.0.1', 8002)>
    # 【三】接受到客户端的消息
    # 接收来自(ip,端口)的数据(最大为1024字节)
    receive_client_data, addr = server.recvfrom(1024)
    # 数据解码
    receive_client_data = receive_client_data.decode()
    print(f'接收到的消息:{receive_client_data}')
    # 【四】返回给客户端的消息
    send_client_data = '服务端成功接收到来自客户端消息'
    send_client_data = send_client_data.encode()
    # 向(ip,端口)传输数据
    server.sendto(send_client_data, addr)
    # 【五】关闭连接
    server.close()
    

【三】TCP协议

1)初版

  • setting.py

    # 创建(ip,端口)
    ip_port = ('127.0.0.10', 8080)
    
  • client.py

    # 客户端
    import setting
    import socket
    
    # 【一】创建一个client对象
    # AF_INET:当前连接是基于网络的套接字
    # SOCK_STREAM:连接模式是TCP协议的流式模式
    client = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
    # 【二】绑定TP和PORT
    client.connect(setting.ip_port)
    # 【三】直接发送数据
    send_server_data = input('向服务器发送的信息:')
    # 数据编码成二进制
    send_server_data = send_server_data.encode()
    # 传输数据
    client.send(send_server_data)
    print(client)
    # client = <socket.socket fd=332, family=2, type=1, proto=0, laddr=('127.0.0.1', 51026), raddr=('127.0.0.10', 8080)>
    # 【四】接受到服务端回的消息
    # 接收数据(最大为1024字节)
    receive_server_data = client.recv(1024)
    # 数据解码
    receive_server_data = receive_server_data.decode()
    print(f'接收到的消息:{receive_server_data}')
    # 【五】关闭连接
    client.close()
    
  • server.py

    # 服务端
    import setting
    import socket
    
    # 【一】创建一个server对象
    # AF_INET:当前连接是基于网络的套接字
    # SOCK_STREAM:连接模式是TCP协议的流式模式
    server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
    # 【二】绑定IP和PORT
    server.bind(setting.ip_port)
    # 【三】监听连接对象
    server.listen(4)
    # 【四】建立连接对象
    conn, addr = server.accept()
    print(conn)
    # comm = <socket.socket fd=340, family=2, type=1, proto=0, laddr=('127.0.0.10', 8080), raddr=('127.0.0.1', 51026)>
    # 【五】接受到客户端的消息
    # 接收的数据(最大为1024字节)
    receive_client_data = conn.recv(1024)
    # 数据解码
    receive_client_data = receive_client_data.decode()
    print(f'接收到的消息:{receive_client_data}')
    # 【六】返回给客户端的消息
    send_client_data = '服务端成功接收到来自客户端消息'
    send_client_data = send_client_data.encode()
    # 传输数据
    conn.send(send_client_data)
    # 【七】关闭连接和服务
    conn.close()
    server.close()
    

2)优化版版

# 【一】引入socket模块
import socket

# 【二】创建一个server对象
# AF_INET:当前连接是基于网络的套接字
# SOCK_STREAM:连接模式是TCP协议的流式模式
client = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
# 【三】绑定IP和PORT
client.connect(settings.ADDR)
while True:
    # 【四】直接发送数据
    to_server_send_data = input("请输入发送给服务端的数据 :>>>> ").strip()
    if not to_server_send_data:
        print(f'不允许发送空的数据')
        continue
    if to_server_send_data == 'q':
        print(f'当前连接已退出!')
        break
    to_server_send_data = to_server_send_data.encode()
    client.send(to_server_send_data)
    # 【五】接收到服务端回的消息
    from_server_recv_data = client.recv(1024)
    from_server_recv_data = from_server_recv_data.decode()
    if from_server_recv_data == 'q':
        break
    print(f'这是来自服务端的数据 :>>>>  {from_server_recv_data}')
# 【六】关闭连接对象
client.close()
from conf import settings
# 【一】引入socket模块
import socket

# 【二】创建一个server对象
# AF_INET:当前连接是基于网络的套接字
# SOCK_STREAM:连接模式是TCP协议的流式模式
server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 【三】绑定IP和PORT
server.bind(settings.ADDR)
# 【四】监听连接对象
server.listen(5)
# 【五】建立连接对象
# 放在这里 进入的 while 循环中的哪个连接对象会一致不变
conn, addr = server.accept()
while True:
    try:
        # 这里面交流的对象永远是上面接收到的那一个
        # conn, addr = server.accept() : 接收到新的对象,忘记上一个连接过的对象
        # 【六】接收到客户端的数据
        from_client_recv_data = conn.recv(1024)
        from_client_recv_data = from_client_recv_data.decode()
        if not from_client_recv_data:
            break
        print(f'这是来自客户端的数据 :>>>>  {from_client_recv_data}')
        # 【七】返回给客户端数据
        while True:
            to_client_send_data = input("请输入发送给客户端的数据 :>>>> ").strip()
            if not to_client_send_data:
                print(f'不允许发送空的数据')
                continue
            if to_client_send_data == 'q':
                print(f'当前连接已退出!')
            to_client_send_data = to_client_send_data.encode()
            conn.send(to_client_send_data)
            break
    except Exception as e:
        break
# 【八】关闭连接和服务
conn.close()
server.close()

【四】粘包问题

只有TCP有粘包现象,UDP不存在

TCP为流式协议,不断的传输数据;UDP为报式协议,一次性传输数据

  • 原因

    • 客户端发送的数据远远超出服务器的接收范围
    • 导致不同数据之间出现数据混乱现象
    • 第一次只接收部分,另一部分无法接收,只能和第二次的数据合并输出
  • 解决方案

    • 客户端在发送数据的时候将数据的总大小一起发送给服务单
    • 服务单接收到总的大小的数据长度 , 根据自己的容量大小分批次接收

1.解决代码演示

import json

from conf import settings
# 【一】引入socket模块
import socket
import subprocess
import uuid


def run_cmd(command):
    result = subprocess.run(
        command,  # 子进程要执行的命令
        shell=True,  # 执行的是shell的命令
        # 存放的是执行命令成功的结果
        stdout=subprocess.PIPE,
        # 存放的是执行命令失败的结果
        stderr=subprocess.PIPE,
        encoding="gbk",
        timeout=1)
    # returncode属性是run()函数返回结果的状态。
    if result.returncode == 0:
        return result.stdout
    else:
        return result.stderr


# 【二】创建一个server对象
# AF_INET:当前连接是基于网络的套接字
# SOCK_STREAM:连接模式是TCP协议的流式模式
client = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
# 【三】绑定IP和PORT
client.connect(settings.ADDR)
while True:
    # 【四】直接发送数据
    command = input("请输入需要执行的命令 :>>>> ").strip()
    if not command:
        print(f'不允许发送空的数据')
        continue
    if command == 'q':
        print(f'当前连接已退出!')
        break
    # 【1】执行本地的命令,获取到当前命令结果
    result = run_cmd(command=command)
    # 【2】对命令的结果进行编码---> 转成二进制数据
    result_bytes = result.encode()
    # 【3】计算长度
    data_length = len(result_bytes)
    # 【4】增加一个数据概览 --> 字典格式 做数据概览
    # 存储当前文件名 / 结果名 / md5加密盐(用来校验数据的完整性)
    salt = uuid.uuid4().hex
    encrypted = settings.encrypt_data(data=result_bytes, salt=salt)
    send_data_info = {
        'command': command,
        'data_length': data_length,
        'salt': salt,
        'encrypted': encrypted
    }
    # 【5】将上面打包好的数据全部发送给服务端
    # (1)字典格式无法发送
    # 将字典转换为字符串数据 ----> json
    # dump : 处理文件数据
    # dumps : 做格式转换的
    json_str = json.dumps(send_data_info)
    # (2)将json字符串数据转换为二进制数据
    json_bytes = json_str.encode()
    # 【6】问题产生
    # JSON字符串转换为的二进制数据还是会很长
    # 让数据变短
    # struct 模块 ---> 将某几个数字转换为四个字节的二进制数据
    json_length_pack = settings.pack_data(data_length=len(json_bytes))
    # 【7】发送struct打包的数据(四个字节) + JSON数据 + 原始数据
    # JSON数据里面存的是所有数据信息而没有原始的二进制数据
    # 服务端接受的顺序取决于客户端发送的顺序
    # 先发送struct打包后的数据
    client.send(json_length_pack)  # 4 字节 --> 包含json二进制数据的长度
    # 先发送 json_bytes 打包后的数据
    client.send(json_bytes)  # 不知道
    # 再发送 result_bytes 原始数据
    client.send(result_bytes)
    # 【五】接收到服务端回的消息
    from_server_recv_data = client.recv(1024)
    from_server_recv_data = from_server_recv_data.decode()
    if from_server_recv_data == 'q':
        break
    print(f'这是来自服务端的数据 :>>>>  \n{from_server_recv_data}')
# 【六】关闭连接对象
client.close()
import json

from conf import settings
# 【一】引入socket模块
import socket

# 【二】创建一个server对象
# AF_INET:当前连接是基于网络的套接字
# SOCK_STREAM:连接模式是TCP协议的流式模式
server = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)
server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 【三】绑定IP和PORT
server.bind(settings.ADDR)
# 【四】监听连接对象
server.listen(5)
# 【五】建立连接对象
# 放在这里 进入的 while 循环中的哪个连接对象会一致不变
conn, addr = server.accept()
while True:
    try:
        # 这里面交流的对象永远是上面接收到的那一个
        # conn, addr = server.accept() : 接收到新的对象,忘记上一个连接过的对象
        # 【六】接收到客户端的数据
        # 【1】先接接收四个字节的数据 ---> struct打包好的四个字节的数据
        json_pack_data = conn.recv(4)
        if not json_pack_data:
            break
        json_bytes_length = settings.unpack_data(data=json_pack_data)
        # 【2】根据json二进制数据长度解出JSON二进制数据
        json_data_bytes = conn.recv(json_bytes_length)
        # 【3】将json二进制数据转为json字符串数据
        json_str = json_data_bytes.decode()
        # 【4】将json字符串数据转换为python的字典
        data_info = json.loads(json_str)
        # 【5】从字典中获取自定的参数
        # 获取到总的数据长度
        # 10000
        data_length = data_info.get('data_length')
        # 【6】定义参数
        # (1)总数据
        all_data = b''
        # (2)每次接收的数据大小
        size = 1024
        # data_length : 5
        # size : 2
        # count : 2 , last_size : 1
        count, last_size = divmod(data_length, size)
        # (3)已经接受的数据大小
        all_size = 0
        while all_size < count + 1:
            all_size += 1
            # 接收到每一次的数据并和总数据拼接
            if all_size == count + 1:
                all_data += conn.recv(last_size)
            else:
                all_data += conn.recv(size)
        from_client_recv_data = all_data.decode()
        print(f'这是来自客户端的数据 :>>>>  \n {from_client_recv_data}')
        # 【七】返回给客户端数据
        while True:
            to_client_send_data = input("请输入发送给客户端的数据 :>>>> ").strip()
            if not to_client_send_data:
                print(f'不允许发送空的数据')
                continue
            if to_client_send_data == 'q':
                print(f'当前连接已退出!')
            to_client_send_data = to_client_send_data.encode()
            conn.send(to_client_send_data)
            break
    except Exception as e:
        break
# 【八】关闭连接和服务
conn.close()
server.close()

【五】struct模块

# 【一】模块介绍
# ● struct.pack()是Python内置模块struct中的一个函数
# ● 它的作用是将指定的数据按照指定的格式进行打包
# 并将打包后的结果转换成一个字节序列(byte string),可以用于在网络上传输或者储存于文件中。
# 【二】参数简介
# struct.pack(fmt, v1, v2, ...)
# ● 其中,fmt为格式字符串,指定了需要打包的数据的格式,后面的v1,v2,...则是需要打包的数据。
# ● 这些数据会按照fmt的格式被编码成二进制的字节串,并返回这个字节串。

# 【三】示例
import struct

# 定义一个包含不同类型字段的格式字符串
format_string = 'i'

# 示例数据:整数、四个字节的原始数据、短整数
data_to_pack = '十七dasdadsad asd 撒大撒多所adsaddasdadsa da dsa asad撒大大带我去大青蛙大大大大大萨达去问问恰饭恰饭放散阀昂发昂发沙发阿发发发放上千万请发送方三房启发法阿发发发ad sada dsa dsa dsa sa dsa dsa as ad sad ad ada顿撒大大三大撒打我前端'
data_to_pack_bytes = data_to_pack.encode()

data_to_pack_len = len(data_to_pack_bytes)
print(data_to_pack_len)
# 使用 struct.pack 将数据打包成二进制字节串
packed_data = struct.pack(format_string, data_to_pack_len)

# 41000000
# 64000000
# 19010000
print("Packed data:", len(packed_data))  # 打印打包后的十六进制表示

# 解析二进制字节串,恢复原始数据
unpacked_data = struct.unpack(format_string, packed_data)
#
print("Unpacked data:", unpacked_data)  # 打印解析后的数据
posted on 2024-05-17 16:43  晓雾-Mist  阅读(3)  评论(0编辑  收藏  举报