[Python] network programming -- sticking package -- the ultimate version

Let's start with a review of knowledge points:

If you execute the command, you must use the subprocess module we have learned, but note:

res = subprocess.Popen(cmd.decode('utf-8'),shell=True,stderr=subprocess.PIPE,stdout=subprocess.PIPE)

The code of the command result is based on the current system. If it is windows, res.stdout What read() reads is GBK encoded. GBK decoding is required at the receiving end, and the result can only be read once from the pipeline

The following problems arise from the code:

Server – simple version:

import socket

# 1: Buy a mobile phone
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
print(phone)

# 2: Binding mobile card
phone.bind(('127.0.0.1',8081))#0-65535:0-1024 for operating system


# 3: Boot, 5: number of web pages that can be previewed
phone.listen(5)

# 4: Waiting for telephone connection
print('starting...')
conn,client_addr=phone.accept()


# 5: Send a message
data=conn.recv(1024)#1: Unit: bytes 2:1024 represents the maximum reception of 1024 bytes
print('Client data',data)

conn.send(data.upper())

# 6: Hang up
conn.close()

# 7: Shut down
phone.close()

Client – simple version:

import socket

# 1: Buy a mobile phone
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
print(phone)

# 2: Dial
phone.connect((' 127.0.0.1',8081))

# 3: Send and receive messages
phone.send('hello'.encode('UTF-8'))
data =phone.recv(1024)
print(data)

# 4: Shut down
phone.close()

Sticking package:

What is sticky bag?

When you execute the command, you don't know how big the information entered by the user is, but your system is fixed in size at the beginning. The data you enter is small, and you may have got the data you want. At this time, you are happy. It's good to think that I have mastered the foundation of network programming, but don't be happy too early:

At this time, execute a command with a long result, such as top -bn 1. You can still get the result, but if you execute another df -h, you will find that you get not the result of the df command, but part of the result of the previous top command

Because the result of the top command is relatively long, but the client only recv(1024), but the result is longer than 1024. What should I do? I have to temporarily save the confiscated data of the client in the IO buffer of the server and wait for the client to collect it next time. Therefore, when the client calls recv(1024) for the second time, it will first collect the data confiscated last time and then collect the result of the df command.

In case of such problems, some people thought that some students said that it would be better to directly change recv(1024) to 5000 \ 10000 or whatever But my dear, if you do this, you can't solve the actual problem, because you can't know the result data returned by the other party in advance. No matter how big you change it, the result of the other party may be larger than that set by you. In addition, this recv can't be changed arbitrarily. The suggestion of relevant departments should not exceed 8192. No matter how large it is, it will affect the sending and receiving speed and instability

This phenomenon is called sticking package, which means that the two results stick together. It occurs mainly because of the socket buffer

Not only exists in UDP packets

The sending end can send data one K at a time, while the application program at the receiving end can pick up data two K at a time. Of course, it is also possible to pick up 3K or 6K data at a time, or only a few bytes of data at a time. In other words, the data seen by the application program is a whole, or a stream. How many bytes of a message are invisible to the application program. Therefore, TCP protocol is a stream oriented protocol, This is also the reason for the sticking problem. UDP is a message oriented protocol. Each UDP segment is a message. The application must extract data in the unit of message, and cannot extract any byte of data at one time. This is very different from TCP. How do you define messages? It can be considered that the data written / sent by the other party at one time is a message. It should be understood that when the other party sends a message, no matter how the bottom layer is segmented, the TCP protocol layer will sort the data segments constituting the whole message before presenting it in the kernel buffer.

For example, the socket client based on tcp uploads a file to the server. When sending, the file content is sent according to the byte stream section by section. When the receiver sees it, he doesn't know where the byte stream of the file starts and ends

The so-called sticky packet problem is mainly caused by the receiver not knowing the boundaries between messages and how many bytes of data to extract at one time.

Sticking problem - final version

Code implementation:
client

import socket
import struct
import json

phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)

phone.connect(('127.0.0.1',9909))

while True:
    #1. Issue orders
    cmd=input('>>: ').strip() #ls /etc
    if not cmd:continue
    phone.send(cmd.encode('utf-8'))

    #2. Take the result of the command and print it

    #Step 1: receive the length of the header first
    obj=phone.recv(4)
    header_size=struct.unpack('i',obj)[0]

    #Step 2: close the header again
    header_bytes=phone.recv(header_size)

    #Step 3: analyze the description information of the real data from the header
    header_json=header_bytes.decode('utf-8')
    header_dic=json.loads(header_json)
    print(header_dic)
    total_size=header_dic['total_size']

    #Step 4: receive real data
    recv_size=0
    recv_data=b''
    while recv_size < total_size:
        res=phone.recv(1024) #1024 is a pit
        recv_data+=res
        recv_size+=len(res)

    print(recv_data.decode('utf-8'))

phone.close()



Note: if it is a Windows system, the code format of the penultimate line of the client needs to be changed to GBK, because the windows system is incompatible, otherwise it will be wrong. If it is a Mac system, it does not need to be changed for other systems

Server:

import socket
import subprocess
import struct
import json


phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.bind(('127.0.0.1',9908))

phone.listen(5)

print('starting...')
while True:
    conn,client_addr=phone.accept()
    print(client_addr)

    while True:#Communication cycle
        try:
            # 1: Receive orders
            cmd=conn.recv(8096)
            if not cmd:break#Suitable for Linux operating system

            # 2: Execute the order and get the result
            obj=subprocess.Popen(cmd.decode('utf-8'), shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE)

            stdout=obj.stdout.read()
            stderr=obj.stderr.read()


            # 3: Return the result of the command to the client
            # Step 1: make a fixed length header
            header_dic={
                'filename':'a.txt',
                'md5':'xxxdxxxx',
                'total_size':len(stdout)+len(stderr)
            }

            header_json=json.dumps(header_dic)
            header_bytes=header_json.encode('utf-8')

            # Step 2: first send the length of the header
            conn.send(struct.pack('i',len(header_bytes)))

            # Step 3: resend the message
            conn.send(header_bytes)

            # Step 4: send real data again
            conn.send(stdout)
            conn.send(stderr)


        except  ConnectionResetError:
            break
    conn.close()


phone.close()


Tags: Python network socket udp Network Communications

Posted by Ne.OnZ on Wed, 30 Mar 2022 21:44:46 +0300