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()