zoukankan      html  css  js  c++  java
  • Python32 1.半连接数 2.粘包问题解决

    复习:

    1.socket   

    套接字,本质上是一个模块,里面封装了一些网络通讯协议

    2.TCP通讯

        网络通讯一定分为两端,服务器和客户端

    3.常见的异常

    4.循环通讯

    详解:

    1.socket

        套接字,本质上是一个模块,里面封装了一些网络通讯协议
        是处于传输层和应用层之间的一个抽象层,实际在OSI中并不存在
        也就是没有socket也能能够通讯 ,但是这样一来 我们必须完全按照OSI规定的各种协议来编码
        这是一个重复,复杂的过程,为了提高开发效率,就出现了socket模块,专门帮我们封装了传输层以下的一堆协议
        有socket模块之后 当我们需要编写网络通讯的程序时,就不需要在关心具体的协议细节,直接使用socket提供的功能接口即可
        门面设计模式,隐藏复杂的内部细节,仅提供最简单的使用接口 本质就是封装

    2.TCP通讯

        网络通讯一定分为两端,服务器和客户端
        服务器:
            1.创建socket对象   server = socket.socket()
            2.绑定一个固定的ip和端口  server.bind    ip必须是本机ip    端口1024-65535    不要使用常见的端口   web:80 / 8080  mysql 3306   ssh:22  ftp:21
            3.开始监听客户端的到来   server.listen
            4.接收客户端的链接请求   server.accept    # 阻塞直到客户链接到来  没有新连接则不可能执行该函数
            5.收发数据   需要循环
                 recv 收    收多少字节数
                 send 发    只能发二进制数据
        客户端:
            1.创建socket对象  client = socket.socket()
            2.链接服务器    client.connect((ip,port))
            3.收发数据   通常需要循环
                send 发    只能发二进制数据
                recv 收    收多少字节数
            4.断开链接   client.close()
     

    3.常见的异常
        1.一方异常下线,另一方还要收发数据  ,ConnectionResetError
        在send  recv的地方 加上try   客户端也需要异常处理
        2.端口占用异常
        重复运行服务器
        之前的进程没有正确关闭
        关闭和打开端口 都是操作系统负责  在一些极端情况下   可能应用程序已经正确接收并且通知了操作系统要关闭端口
        但是操作系统 没及时处理
            2.1 更换端口
            2.2 查后台进程 杀掉它
            2.3 重启服务器电脑
    4.循环通讯
           添加循环 用来重复收发数据
     
     
     

    今日内容 

    1.半连接数

    涉及内容:server.accept    # 阻塞直到客户链接到来  没有新连接则不可能执行该函数

    2.粘包问题与解决

    涉及知识内容:

     1.socket模块---套接字,----一套封装好的模块

        是一套编程接口,内部封装了一堆底层协议, 隐藏了内部复杂的实现细节, 提供简单的使用接口 作用:用于服务器与客户端之间收发数据

      2.subprocess模块---子进程

           subprocess.Popen(指令,shell=True,in,out,err) 当你需要执行系统指令时使用它

       3.struct模块--一个内置某块---解决粘包的方法

            作用:将Python中的类型,转为字节数据,并且长度固定'

    3.补充:finally最终  语法:配合try...exccept....

    详解:

    1.半连接数

      半连接数(与阻塞的作用有关(client,addr=server.accept()))
     代码:server.listen(5)
    上列代码中,数字5就是他的最大半连接数

    #最大半连接:本质来说,就是一个数组,未完成链接的socket,
    这个案例说明:
    1)每次执行accept 就会挑一个来完成三次握手,如果达到最大限制的额外客户端将直接被拒绝
    2)我们可以调整内核参数来修改,最大等待时长,如果超时客户还没有回复第三次握手信息,就直接删除
    socket模块封装的是:
    正常的三次握手过程,一般不会出现半连接
    1.1.什么是半连接?
    客户端:给服务器发送一次握手信息, 服务器回复确认后, 进入等待状态, 但是客户端没有回应第三次握手信息, 服务器:会保存着客户端的一些信息, 一直等,这种情况称为半连接
    应用场景:
        1.黑客攻击:(洪水攻击)指黑客通过不断的向服务器发送链接请求,但是不确认第三次信息,服务器出现大量time_wait状态,资源耗尽,正常客户无法享受服务
       2.另一种出现time_wait的原因:客户端实在太多,服务器处理不过来

    1.1简单说:  三次握手没有完成 称之为半连接 

             原因1   恶意客户端没有返回第三次握手信息

            原因2   服务器没空及时处理你的请求

           socket中  listen(半连接最大数量)

    2.粘包问题与解决

    TCP流式协议, 数据之间没有分界, 就像水  一杯水和一杯牛奶倒在一起了!

    UDP 用户数据报协议   

    粘包 仅发生在TCP协议中  

    1. 发送端 发送的数据量小 并且间隔短 会粘

    2. 接收端 一次性读取了两次数据的内容    会粘
    3. 接收端 没有接收完整  剩余的内容 和下次发送的粘在一起

    无论是那种情况,其根本原因在于  接收端不知道数据到底有多少

    解决方案就是 提前告知接收方 数据的长度 

    # 解决方案

    先发长度给对方  再发真实数据 

     

    #发送端

    1.使用struct 将真实数据的长度转为固定的字节数据

    2.发送长度数据

    3.发送真实数据 

    接收端

    1.先收长度数据    字节数固定 

    2.再收真实数据      真实可能很长 需要循环接收

    发送端和接收端必须都处理粘包 才算真正的解决了

    案例: 远程CMD程序

    ```python
    #    ==================================================客户端
    import socket
    from 二_CMD程序 import smallTool
    import struct

    client = socket.socket()
    try:
        client.connect(("127.0.0.1",1688))
        print("链接成功!")
        while True:
            msg = input("请输入要执行指令:").strip()
            if msg == "q": break
            if not msg: continue
            # 发送指令
            # 先发长度
            len_bytes = struct.pack("q",len(msg.encode("utf-8")))
            client.send(len_bytes)
            # 在发指令
            client.send(msg.encode("utf-8"))

            data = smallTool.recv_data(client)
            print(data.decode("GBK"))

        client.close()
    except ConnectionRefusedError as e:
        print("链接服务器失败了!",e)
    except ConnectionResetError as e:
        print("服务器挂了!", e)
        client.close()
    ```

    服务器

    ```python
    import socket
    import subprocess
    import struct
    from 二_CMD程序 import  smallTool

    server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    server.bind(("127.0.0.1",1688))
    server.listen()
    # back

    while True:
        # socket,addr一个元组 客户端的ip和port
        client,addr = server.accept()
        print("客户端链接成功!")
        # 循环收发数据
        while True:
            try:
                cmd = smallTool.recv_data(client)
                if not cmd:
                    break
                print(cmd)

                p = subprocess.Popen(cmd.decode("utf-8"),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
                # 不要先读err错误信息  它会卡主  原因不详  linux不会有问题  tasklist  netstat - ano啥的
                data = p.stdout.read()
                err_data = p.stderr.read()

                len_size = len(data) + len(err_data)
                print("服务器返回了: %s " %  len_size)

                len_bytes = struct.pack("q",len_size)

                # 在发送真实数据前先发送 长度
                client.send(len_bytes)

                # 返回的结果刚好就是二进制
                # 发送真实数据
                client.send(data + err_data)


            except ConnectionResetError as e:
                print("客户端了挂了!",e)
                break
        client.close()

    #server.close()
    ```

     

     

    ##  自定义报头 

    当需要在传输数据时 传呼一些额外参数时就需要自定义报头

    报头本质是一个json  数据  

    具体过程如下:

    发送端

    ​ 1 发送报头长度

    ​ 2  发送报头数据    其中包含了文件长度  和其他任意的额外信息 

    ​ 3  发送文件内容

      

    接收端

    ​ 1.接收报头长度

    ​ 2.接收报头信息

    ​ 3.接收文件内容

    案例:

    服务器

    ```
    import socket
    import os
    import struct
    import json
    """
    客户端接链成功我就给你发个文件过去 
    固定的文件下载

    """

    server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    server.bind(("127.0.0.1",1688))
    server.listen()
    # back

    while True:
        # socket,addr一个元组 客户端的ip和port
        client,addr = server.accept()
        print("客户端链接成功!")
        f = None
        try:

            path = r"F:2.半链接数.mp4"
            file_size = os.path.getsize(path)


            # 我想把文件名发过去
            file_info = {"file_name":"半链接数.mp4","file_size":file_size,"md5":"xxxxxxxxx"}

            json_str = json.dumps(file_info).encode("utf-8")

            # 发送报头长度
            client.send(struct.pack("q",len(json_str)))

            # 发报头
            client.send(json_str)

            # 发文件了
            # 发送文件数据
            f = open(path,"rb")
            # 循环发送文件内容   每次发2048
            while True:
                temp = f.read(2048)
                if not temp:
                    break
                client.send(temp)
            print("文件发送完毕!")

        except Exception as e:
            print("出问题了",e)
        finally:
            if f:f.close()
        client.close()

        # 无论是否抛出异常 文件都要关闭

    #server.close()

    # 用户可以指定要下载什么文件 FTP
    ```

     

    客户端


    """
    客户端输入指令
    服务器接收指令并执行  最后返回执行结果
    """

    import socket
    import struct
    import json

    client = socket.socket()
    try:
        client.connect(("127.0.0.1",1688))
        print("链接成功!")

        # 1.先收报头长度
        head_size = struct.unpack("q",client.recv(8))[0]

        # 2.收报头数据
        head_str = client.recv(head_size).decode("utf-8")
        file_info = json.loads(head_str)
        print("报头数据:",file_info)
        file_size = file_info.get("file_size")
        file_name = file_info.get("file_name")


        # 3.再收文件内容
        # 已接收大小
        recv_size = 0
        buffer_size = 2048
        f = open(file_name,"wb")
        while True:
            if file_size - recv_size >= buffer_size:
                temp = client.recv(buffer_size)

            else:

                temp = client.recv(file_size - recv_size)

            f.write(temp)

            recv_size += len(temp)

            print("已下载:%s%%" % (recv_size / file_size * 100))

            if recv_size == file_size:

                break

        f.close()

    except ConnectionRefusedError as e:

        print("链接服务器失败了!",e)

    ```
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
     
  • 相关阅读:
    9 种数据库中Select Top的使用方法 (只显示数据库的前几条记录)(Oracle、Infomix、DB2、SQL Server、Access、Sybase、MySQL、FoxPro、Sqlite)
    Delphi Treeview用法介绍
    TTreeView、TTreeNodes和TTreeNode
    delphi中TreeView使用
    Delphi 中多线程同步的一些处理方法
    delphi 中几种多线程操作方式
    Delphi 多线程介绍,以及线程类TThread 分析
    Delphi TTcpClient 和 TTcpServer 类 (TBaseSocket、TIpSocket、TCustomIPClient、TTcpClient、TUdpSocket、TRawSocket、TCustomTcpServer、TTcpServer) 介绍
    用NVIDIA Tensor Cores和TensorFlow 2加速医学图像分割
    通过Mellanox ConnectX NIC使用XDP加速
  • 原文地址:https://www.cnblogs.com/llx--20190411/p/10944562.html
Copyright © 2011-2022 走看看