• socket-粘包


    1、什么缓冲区,为什么会有缓冲区?缓冲区是指socket通信时,收发命令时的一个中间存放命令的存储空间因为数据被输出后在处理的时候需要一定的时间,
    为了输入接着输入零时差,就
    需要缓冲了,先预读并处理一部分信息,然后开始输出,在输出的同时进行后面的的处理,然后等缓冲的部分输出完后,另一部分的数据也处理完毕了,
    就可以接着
    输出了,如果没有这个缓冲区,就会很卡输入输出的缓冲区一般是8k

    tcp粘包演示(一):

    先从上面粘包现象中的第一种开始:接收方没有及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包) 
     
    server端代码示例:
     
    cket import *
    import subprocess

    ip_port=('127.0.0.1',8080)
    BUFSIZE=1024

    tcp_socket_server=socket(AF_INET,SOCK_STREAM)
    tcp_socket_server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
    tcp_socket_server.bind(ip_port)
    tcp_socket_server.listen(5)

    while True:
        conn,addr=tcp_socket_server.accept()
        print('客户端>>>',addr)

        while True:
            cmd=conn.recv(BUFSIZE)
            if len(cmd) == 0:break

            res=subprocess.Popen(cmd.decode('gbk'),shell=True,
                             stdout=subprocess.PIPE,
                             stdin=subprocess.PIPE,
                             stderr=subprocess.PIPE)

            stderr=res.stderr.read()
            stdout=res.stdout.read()
            conn.send(stderr)
            conn.send(stdout)
     
    client端代码示例:
     
    ort = ('127.0.0.1',8080)
    size = 1024
    tcp_sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    res = tcp_sk.connect(ip_port)
    while True:
        msg=input('>>: ').strip()
        if len(msg) == 0:continue
        if msg == 'quit':break

        tcp_sk.send(msg.encode('utf-8'))
        act_res=tcp_sk.recv(size)
        print('接收的返回结果长度为>',len(act_res))
        print('std>>>',act_res.decode('gbk')) #windows返回的内容需要用gbk来解码,因为windows系统的默认编码为gbk
     

    tcp粘包演示(二):发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据也很小,会合到一起,产生粘包)

    server端代码示例:(如果两次发送有一定的时间间隔,那么就不会出现这种粘包情况,试着在两次发送的中间加一个time.sleep(1))

    from socket import *
    ip_port=('127.0.0.1',8080)

    tcp_socket_server=socket(AF_INET,SOCK_STREAM)
    tcp_socket_server.bind(ip_port)
    tcp_socket_server.listen(5)
    conn,addr=tcp_socket_server.accept()
    data1=conn.recv(10)
    data2=conn.recv(10)

    print('----->',data1.decode('utf-8'))
    print('----->',data2.decode('utf-8'))

    conn.close()

    client端代码示例:

    import socket
    BUFSIZE=1024
    ip_port=('127.0.0.1',8080)
    s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    # res=s.connect_ex(ip_port)
    res=s.connect(ip_port)
    s.send('hi'.encode('utf-8'))
    s.send('meinv'.encode('utf-8'))

    udp粘包演示:注意:udp是面向包的,所以udp是不存在粘包的

    粘包的原因:主要还是因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的

    粘包的解决方案

    解决方案(一):

     问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端发一个确认消息给发送端,然后发送端再发送过来后面的真实内容,接收端再来一个死循环接收完所有数据。
     
    server端代码
    import socket,subprocess
    ip_port=('127.0.0.1',8080)
    s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

    s.bind(ip_port)
    s.listen(5)

    while True:
        conn,addr=s.accept()
        print('客户端',addr)
        while True:
            msg=conn.recv(1024)
            if not msg:break
            res=subprocess.Popen(msg.decode('utf-8'),shell=True,
                                stdin=subprocess.PIPE,
                             stderr=subprocess.PIPE,
                             stdout=subprocess.PIPE)
            err=res.stderr.read()
            if err:
                ret=err
            else:
                ret=res.stdout.read()
            data_length=len(ret)
            conn.send(str(data_length).encode('utf-8'))
            data=conn.recv(1024).decode('utf-8')
            if data == 'recv_ready':
                conn.sendall(ret)
        conn.close()
     
    client端代码示例
    import socket,time
    s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    res=s.connect_ex(('127.0.0.1',8080))

    while True:
        msg=input('>>: ').strip()
        if len(msg) == 0:continue
        if msg == 'quit':break

        s.send(msg.encode('utf-8'))
        length=int(s.recv(1024).decode('utf-8'))
        s.send('recv_ready'.encode('utf-8'))
        send_size=0
        recv_size=0
        data=b''
        while recv_size < length:
            data+=s.recv(1024)
            recv_size+=len(data)


        print(data.decode('utf-8'))
     
    解决方案(二):
        通过struck模块将需要发送的内容的长度进行打包,打包成一个4字节长度的数据发送到对端,对端只要取出前4个字节,然后对这四个字节的数据进行解包,拿到你要发送的内容的长度,然后通过这个长度来继续接收我们实际要发送的内容。不是很好理解是吧?哈哈,没关系,看下面的解释~~
           为什么要说一下这个模块呢,因为解决方案(一)里面你发现,我每次要先发送一个我的内容的长度,需要接收端接收,并切需要接收端返回一个确认消息,我发送端才能发后面真实的内容,这样是为了保证数据可靠性,也就是接收双方能顺利沟通,但是多了一次发送接收的过程,为了减少这个过程,我们就要使struck来发送你需要发送的数据的长度,来解决上面我们所说的通过发送内容长度来解决粘包的问题
    server端:
    import socket,struct,json
    import subprocess
    phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)

    phone.bind(('127.0.0.1',8080))
    phone.listen(5)

    while True:
        conn,addr=phone.accept()
        while True:
            cmd=conn.recv(1024)
            if not cmd:break
            print('cmd: %s' %cmd)

            res=subprocess.Popen(cmd.decode('utf-8'),
                                 shell=True,
                                 stdout=subprocess.PIPE,
                                 stderr=subprocess.PIPE)
            err=res.stderr.read()
            print(err)
            if err:
                back_msg=err
            else:
                back_msg=res.stdout.read()

            headers={'data_size':len(back_msg)}
            head_json=json.dumps(headers)
            head_json_bytes=bytes(head_json,encoding='utf-8')

            conn.send(struct.pack('i',len(head_json_bytes))) #先发报头的长度
            conn.send(head_json_bytes) #再发报头
            conn.sendall(back_msg) #在发真实的内容

        conn.close()
     
    client端:
    from socket import *
    import struct,json

    ip_port=('127.0.0.1',8080)
    client=socket(AF_INET,SOCK_STREAM)
    client.connect(ip_port)

    while True:
        cmd=input('>>: ')
        if not cmd:continue
        client.send(bytes(cmd,encoding='utf-8'))

        head=client.recv(4)
        head_json_len=struct.unpack('i',head)[0]
        head_json=json.loads(client.recv(head_json_len).decode('utf-8'))
        data_len=head_json['data_size']

        recv_size=0
        recv_data=b''
        while recv_size < data_len:
            recv_data+=client.recv(1024)
            recv_size+=len(recv_data)

        #print(recv_data.decode('utf-8'))
        print(recv_data.decode('gbk')) #windows默认gbk编码
     
    socketserver模块实现并发
    服务端代码示例:
    import socketserver
    class Myserver(socketserver.BaseRequestHandler):
        def handle(self):
            self.data = self.request.recv(1024).strip()
            print("{} wrote:".format(self.client_address[0]))
            print(self.data)
            self.request.sendall(self.data.upper())

    if __name__ == "__main__":
        HOST, PORT = "127.0.0.1", 9999

        # 设置allow_reuse_address允许服务器重用地址
        socketserver.TCPServer.allow_reuse_address = True
        # 创建一个server, 将服务地址绑定到127.0.0.1:9999
        #server = socketserver.TCPServer((HOST, PORT),Myserver)
        server = socketserver.ThreadingTCPServer((HOST, PORT),Myserver)
        # 让server永远运行下去,除非强制停止程序
        server.serve_forever()
     
    客户端代码示例:
    import socket

    HOST, PORT = "127.0.0.1", 9999
    data = "hello"

    # 创建一个socket链接,SOCK_STREAM代表使用TCP协议
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
        sock.connect((HOST, PORT))          # 链接到客户端
        sock.sendall(bytes(data + " ", "utf-8")) # 向服务端发送数据
        received = str(sock.recv(1024), "utf-8")# 从服务端接收数据

    print("Sent:     {}".format(data))
    print("Received: {}".format(received))
     
     
    4、什么是粘包? socket 中造成粘包的原因是什么? 哪些情况会发生粘包现象?
    TCP粘包是指发送方发送的若干包数据包到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾
    (1)发送方原因
    TCP默认会使用Nagle算法。而Nagle算法主要做这两件事1>只有上一个分组得到确认,才会发送下一个分组;2>收集多个小分组,再确认到来时一起发送
    所以是Nagle算法造成了发送方造成了粘包现象
    (2)接收方原因
    TCP接收到分组时,并不会立刻送至应用层处理,或者说,应用层并不会立即处理;实际上,TCP将收到的分组保存至接收缓存里,然后
    应用程序主动从缓存里读收到的分组。这样一来,如果TCP接收分组的速度大于应用程序读分组的速度,多个包就会存至缓存,应用程序
    读时,就会读到多个首尾相接粘到一起的包

    哪些情况会发生粘包现象?
    (1)发送端需要等待缓冲区满才发送出去,造成粘包
    (2)接收方不及时接收缓冲区的包,造成多个包接收

    5、用tcp协议下的socket,写一个简易的文件上传下载的功能,用户需要登陆认证,认证成功后,客户端用户可以选择上传或者是下载,
    上传的时候服务端提前设定好上传文件的路径,将文件上传到对应的路径下,下载文件的时候,服务端将之前设定好的上传路径中的所有
    文件带上序号展示给用户看,用户输入文件序号后下载对应的文件,文件下载到客户端程序的当前路径下就可以了。
     
     
     
     
     
     
     
     
  • 相关阅读:
    UE4智能指针:TUniquePtr
    浅析UE4垃圾回收
    UE4中资源的引用
    ELF文件基础
    【JVM】JVM和Java 体系架构
    【Java多线程】Java线程生命周期、线程同步、线程通信(二)
    【Java多线程】Java多线程基础(一)
    【Java面试题】MySQL索引底层为什么用到B+树
    【算法】B树、B+树详解
    【Kafka】 Kafka的简介与架构(三)
  • 原文地址:https://www.cnblogs.com/DanielYang11/p/10026685.html
Copyright © 2020-2023  润新知