我正在研究套接字编程。这里,服务器向连接到服务器的机器发送随机字节数。这个随机数模拟服务器也从其他地方接收数据,数据长度可以变化很大,我们不确定有多少。
我在同一台机器上运行服务器和客户端。我期待没有数据丢失,但令我惊讶的是,我可以看到数据丢失也发生在这里。我一定是在我的代码中做错了什么。我已经试着找过了,但没有发现任何可疑的东西。我只能猜测数据的长度可能是一个问题,但我仍然不确定。
我首先从服务器发送消息的长度到客户端,然后发送实际的消息,以便我可以确定我收到了完整的消息。
我连接到服务器100次,很多次数据丢失。
下面是我的服务器代码:
import socket
import struct
import random as rand
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# bind the socket to a specific address and port
server_address = ('localhost', 12345)
sock.bind(server_address)
sock.listen(1)
while True:
message = "12345" * rand.randint(100000, 200000)
connection, client_address = sock.accept()
message_in_bytes=message.encode()
length = len(message_in_bytes)
print(client_address, "connected and message length is ",length)
length_bytes = struct.pack("<I", length)
connection.send(length_bytes + message_in_bytes)
connection.close()
下面是客户端代码:
import socket
import struct
def connect_server():
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_address = ('localhost', 12345)
sock.connect(server_address)
message_length_bytes=sock.recv(4)
length_of_message = struct.unpack("<I",message_length_bytes)[0]
whole_message=sock.recv(length_of_message)
sock.close()
if length_of_message == len(whole_message):
return "success"
else:
return "failed"
stats={'success':0,'failed':0}
for _ in range(100):
stats[connect_server()] +=1
print(stats)
send()
不能保证发送所有的数据,这受到网络堆栈中缓冲区实现的限制。recv(n)
也不能保证返回n字节。在这两种情况下,检查返回值并发送/接收剩余的数据。
sendall()
是另一种选择,它将循环并发送所提供的所有数据。
对于接收,缓冲接收到的数据,直到接收到完整的消息。socket.makefile()
是一个函数,它将套接字包装在一个类似文件的对象中,其中像read(n)
这样的I/O操作将返回n字节,除非套接字关闭,readline()
将读取直到换行符。
的例子:
server.py
import socket
import struct
import random as rand
sock = socket.socket()
server_address = ('localhost', 12345)
sock.bind(server_address)
sock.listen()
while True:
connection, client_address = sock.accept()
# closes socket when with is exited
with connection:
message = b'12345' * rand.randint(100000, 200000)
length = len(message)
print(f'Sending {length}-byte message...')
length_bytes = struct.pack('<I', length)
connection.sendall(length_bytes + message) # Use sendall
client.py
import socket
import struct
def connect_server():
with socket.socket() as sock:
server_address = ('localhost', 12345)
sock.connect(server_address)
with sock.makefile('rb') as rfile: # wrap in file-like object
message_length_bytes = rfile.read(4)
length_of_message = struct.unpack('<I', message_length_bytes)[0]
whole_message = rfile.read(length_of_message)
if length_of_message == len(whole_message):
return 'success'
else:
return 'failed'
stats = {'success': 0, 'failed': 0}
for _ in range(100):
stats[connect_server()] += 1
print(stats)
输出(服务器):
Sending 604035-byte message...
Sending 949255-byte message...
Sending 838075-byte message...
...
Sending 756820-byte message...
Sending 912215-byte message...
Sending 673175-byte message...
输出(客户端):
{'success': 100, 'failed': 0}
问题出在发送的长度上。您的消息长度最多可达1 000 000字节。它将超过内核缓冲区的长度。
在这种情况下,send()返回实际传输到缓冲区的长度,您必须为额外的部分重新执行send()。
我在我的机器上做了一个测试。消息长度为542610,但send(0)返回524288 (512KiB)。
你必须循环send(),直到所有的东西都被传输到内核缓冲区。
rand = rand.randint( 100000,200000)
length_bytes = struct.pack("<I", rand * 5)
whole_message = length_bytes + ('12345' * rand).encode()
actual_length = 0
while actual_length < len( whole_message:
nb = send( whole_message[actual_length:])
actual_length += nb
'''
Note: the length of kernel buffer may be different with your distrib.