Python中的UDP客户端/服务器套接字

36

我是Python和Socket编程的新手,正在尝试编写回显客户端/服务器Socket。我已经编写了服务器程序,使得30%的数据包丢失。我编写了客户端程序,在一秒钟后超时,因为数据包可能会丢失。然而,每当我运行我的客户端Socket时,输出总是100%的“请求超时”。我认为这是因为我的服务器从未收到消息。我多次检查过我的代码,但无法找到原因。以下是我的服务器和客户端Socket代码。任何帮助将不胜感激。

服务器Socket:

 # We will need the following module to generate randomized lost packets
    import random
    from socket import *

    # Create a UDP socket
    # Notice the use of SOCK_DGRAM for UDP packets
    serverSocket = socket(AF_INET, SOCK_DGRAM)

    # Assign IP address and port number to socket
    serverSocket.bind(('', 12000))

    while True:
        # Generate random number in the range of 0 to 10
        rand = random.randint(0, 10)

        # Receive the client packet along with the address it is coming from
        message, address = serverSocket.recvfrom(1024)

        # Capitalize the message from the client
        message = message.upper()

        # If rand is less is than 4, we consider the packet lost and do notrespond
        if rand < 4:
            continue

        # Otherwise, the server responds
        serverSocket.sendto(message, address) 

客户端套接字:

import time
from socket import *

pings = 1

#Send ping 10 times 
while pings < 11:  

    #Create a UDP socket
    clientSocket = socket(AF_INET, SOCK_DGRAM)

    #Set a timeout value of 1 second
    clientSocket.settimeout(1)

    #Ping to server
    message = 'test'

    addr = ("127.0.0.1", 12000)

    #Send ping
    start = time.time()
    clientSocket.sendto(message, addr)

    #If data is received back from server, print 
    try:
        data, server = clientSocket.recvfrom(1024)
        end = time.time()
        elapsed = end - start
        print data + " " + pings + " "+ elapsed        

    #If data is not received back from server, print it has timed out  
    except timeout:
        print 'REQUEST TIMED OUT'

    pings = pings - 1

4
如果您通过Google搜索了如何在Python中实现UDP套接字,并且想知道为什么某些请求失败,那是有意为之的。请仔细阅读文章。 - rovyko
如果您正在使用AWS AMI,请确保使用的是公共IP而不是私有IP。 - P i
1
一个小建议:为了提高可读性,请避免使用import *。要么明确列出导入的模块或函数,要么只导入基础模块并在代码中使用点符号表示;这样,未来的读者(包括你自己!)将能够快速识别事物的来源。@bsa提供了其他改善代码质量的好建议。 - Ender
2个回答

59

我测试了你的代码,在我的电脑上按预期工作。你的问题可能不是代码的问题,而是防火墙或其他东西阻止了环回接口(127.0.0.1)上的所有数据包。根据你的操作系统,尝试使用像Wireshark这样的数据包监视器进行测试。

此外,以下是一些提高Python风格的建议:

服务器

import random
import socket

server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
server_socket.bind(('', 12000))

while True:
    rand = random.randint(0, 10)
    message, address = server_socket.recvfrom(1024)
    message = message.upper()
    if rand >= 4:
        server_socket.sendto(message, address)

客户

import time
import socket

for pings in range(10):
    client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    client_socket.settimeout(1.0)
    message = b'test'
    addr = ("127.0.0.1", 12000)

    start = time.time()
    client_socket.sendto(message, addr)
    try:
        data, server = client_socket.recvfrom(1024)
        end = time.time()
        elapsed = end - start
        print(f'{data} {pings} {elapsed}')
    except socket.timeout:
        print('REQUEST TIMED OUT')

1
好的,那就解释了一切。我盯着这个东西看了几个小时,还是没搞明白。非常感谢你,也谢谢你的建议。 - user3335367
clientSocket.sendto(message, addr) 这一行,你是否遇到了 TypeError: a bytes-like object is required, not 'str' 的错误? - S Andrew
@SAndrew 我认为在Python2中,字符串对象与字节对象是兼容的,而Python3则明确将两者分开。如果你将message = 'test'这一行改为message = b'test',那么它应该可以正常工作。另外:第三个Python才是最好的Python。 - ocket8888
2
“更具Python风格”你只是删除了多余的换行和注释,并将一个while循环改为for循环。 - Vegard J.
2
还删除了一个 continue,并使用字符串格式化运算符代替字符串连接 :) 你还想改变什么? - bsa
显示剩余2条评论

6
这里有一种使用asyncio的替代方案。
import asyncio
import random

class EchoServerProtocol:
    def connection_made(self, transport):
        self.transport = transport

    def datagram_received(self, data, addr):
        message = data.decode()
        print('Received %r from %s' % (message, addr))
        rand = random.randint(0, 10)
        if rand >= 4:
            print('Send %r to %s' % (message, addr))
            self.transport.sendto(data, addr)
        else:
            print('Send %r to %s' % (message, addr))
            self.transport.sendto(data, addr)


loop = asyncio.get_event_loop()
print("Starting UDP server")

# One protocol instance will be created to serve all client requests
listen = loop.create_datagram_endpoint(
    EchoServerProtocol, local_addr=('127.0.0.1', 12000))
transport, protocol = loop.run_until_complete(listen)

try:
    loop.run_forever()
except KeyboardInterrupt:
    pass

transport.close()
loop.close()

7
请您在代码块中添加几行说明,例如为什么要用这种方式?优缺点是什么?感谢提供示例! - n1k31t4
如果rand >= 4:与else块中的内容相同,那么为什么你需要一开始就使用if else呢? - Chang Zhao

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接