服务器未从客户端接收UDP消息(Python到MATLAB)

rhfm7lfc  于 2023-10-23  发布在  Matlab
关注(0)|答案(2)|浏览(187)

我正在尝试连接到客户端计算机,以便使用UDP从服务器计算机发送整数值。问题的出现(我相信)是由于服务器计算机在Python中发送和侦听UDP通信,而客户端从MATLAB脚本接收和发送消息。从理论上讲,这种安排应该没有关系,因为UDP通信根本不应该受到编码语言的影响。
服务器端代码(Python):

import socket

commandVal = 0 #Message to be sent

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.sendto(np.uint8(commandVal), (hostIP, portNum)) #hostIP and portNum are defined separately

sock.connect((hostIP, portNum))
while True:
    data, addr = sock.recvfrom(1024)
    print("received message: %s" % data)

客户端代码(MATLAB):

packetlength = 50
waitdur = 15000

mssgA = judp('receive',port,packetlength,waitdur);
if mssgA(1) == 0
   judp('send',port, host,int8('error'))
else
   judp('send',port, host,int8('success'))

我知道端口和IP地址定义正确,因为如果我使用基于MATLAB的judp函数从服务器端进行通信,我就可以发送和接收消息。
当使用Python代码时,消息被发送到客户端,但没有收到“错误”或“成功”消息。这里有什么问题吗?
我尝试过更改防火墙设置,并浏览judpsocket的文档。我还没找到解决办法

vaqhlq81

vaqhlq811#

我有点困惑,在你的代码中谁是客户端,谁是服务器。看起来你的“服务器”正在向“客户端”发送命令/请求,这似乎是向后的?
话虽如此,我认为你的主要问题来自这样一个事实,即你从来没有将你的“服务器”套接字绑定到一个特定的地址,这意味着你的“客户端”的消息不被识别为寻址到“服务器”。
此外,您在MATLAB脚本中使用相同的端口进行发送和接收,这可能是一个错字,也可能是故意的,恐怕我不能说没有更多关于您代码其余部分的信息。

server.py

import socket

client_ip = '127.0.0.1' # using localhost for this example
client_port = 10001

server_ip = '127.0.0.1' # using localhost for this example
server_port = 10002

commandVal = 0  # Message to be sent
waitdur = 0.1 # wait for messages this many seconds, then break to
#   handle KeyboardInterrupts. note: don't do this in a real project.
#   it's a waste of CPU resources. for this demo, it's OK though, I guess.

# use a context manager ("with-statement") to make sure the socket is properly
#   closed and released on exit.
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as sock:
    # set a timeout so we can interrupt the program with CTRL+C later
    sock.settimeout(waitdur)

    print(f"binding address to server: {(server_ip, server_port)}")
    sock.bind((server_ip, server_port))

    print(f"connecting to 'client' at {(client_ip, client_port)}")
    sock.connect((client_ip, client_port))

    print("sending command value to 'client'")
    sock.send(int.to_bytes(commandVal, length=1, signed=False)) # check endianness if there's an issue with the value being sent. for a single byte this is irrelevant though

    print("listening for response")
    while True:
        try:
            data, addr = sock.recvfrom(1024)
            print(f"received '{data}' from {addr}")
            # note: since we already know the address we're receiving from (it's
            #   the one we connected to previously), the above is equivalent
            #   to the following:
            # data = sock.recv(1024)
            # print(f"received '{data}' from {(client_ip, client_port)}")

        except socket.timeout:
            pass

客户端.m

client_ip = '127.0.0.1'
client_port = 10001

server_ip = '127.0.0.1'
server_port = 10002

packetlength = 50
waitdur = 15000

mssgA = judp('receive', client_port, packetlength, waitdur);
if mssgA(1) == 0
   judp('send', server_port, server_ip, int8('error'))
else
   judp('send', server_port, server_ip, int8('success'))
r6hnlfcb

r6hnlfcb2#

TL;DR

在服务器上使用套接字的bind方法,以便将其绑定到特定的端口号。通常建议仅绑定到服务器预期通过其进行通信的特定IP地址(或接口),但如果合适,可以使用通配符(所有接口-0.0.0.0)。
确保在发出任何传出流量之前绑定套接字,如下所示:

import socket

commandVal = 0 # Message to be sent

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind("0.0.0.0", server_port)

sock.sendto(...)

概述

通常在客户端-服务器架构中,客户端发起与服务器的通信(就像有人问问题一样),服务器相应地回复(通常是问题的答案)。这对于UDP和TCP都是正确的(例如DHCP、NTP、DNS等)。一些服务器可以发起与下级机器的通信(例如在配置管理中),但是可以认为这些机器实际上正在运行“服务器”(代理)以回复客户端(CM服务器)的查询。正如@Hoodlum所提到的,让你的“服务器”(Python代码)与“客户端”(MATLAB)联系是相当混乱的,但这是一个不同的主题(如果有的话)。

技术支持

在客户端-服务器架构中,服务器将在特定端口上监听传入的客户端请求(例如,HTTP的端口号为80,HTTPS的端口号为443,SMTP的端口号为25,等等),而客户端通常会分配一个伪随机的源端口号,它们期望接收服务器的应答(通常有些顺序,关键是这些端口号不能被依赖)。这就是问题中的服务器和客户端之间开始出现问题的地方:
在服务器端,sendto可以正常工作。如上所述,UDP数据报到达MATLAB客户端。当客户端试图向port发送响应时,问题就开始了,而服务器并没有监听。由于服务器的套接字没有绑定到特定的端口,因此传出消息被赋予了一个伪随机(顺序的东西)端口号。客户端被配置为将消息发送到Python应用程序(可能)没有监听的特定端口号,并且该数据报将无法到达其期望的目的地。

解决方案

[免责声明-我没有MATLAB许可证,所以我尽可能地将客户端代码移植到Python。我建议的一些解决方案需要修改客户端的代码,我不确定MATLAB是否可以做到这一点。
这是作为一个摘要提供的运行示例。代码本身将在下面发布。

1.绑定服务端socket

==== TERMINAL 1 ====
$ ./example.py ported_client
Client received message: 'data=(b'\x00', ('127.0.0.1', 10001))', 'data_int=0'
Client has Not received The Answer to Life; data_int=0
$

==== TERMINAL 2 ====
$ ./example.py bound_server
Server received message: 'b'+''
Server formatted data: npint_data=43, addr=('127.0.0.1', 10002)

2.绑定连接客户端

请注意,连接后,应使用send/recv而不是sendto/recvfrom

==== TERMINAL 1 ====
$ ./example.py ported_client
Client received message: 'data=(b'\x00', ('127.0.0.1', 10001))', 'data_int=0'
Client has Not received The Answer to Life; data_int=0
$

==== TERMINAL 2 ====
$ ./example.py connected_server
Server received message: 'b'+''
Server formatted data: npint_data=43

3.让客户端知道服务器的端口(需要修改客户端)

==== TERMINAL 1 ====
$ ./example.py adaptive_ported_client
Client received message: 'data=(b'\x00', ('127.0.0.1', 58264))', 'data_int=0'
Client has Not received The Answer to Life; data_int=0
$

==== TERMINAL 2 ====
$ ./example.py original_server
Server received message: 'b'+''
Server formatted data: npint_data=43, addr=('127.0.0.1', 10002)

4.实现客户端-服务器,其中服务器不发起通信(需要更改客户端)

这还有一个额外的好处,即在服务器保持运行的同时,可以多次触发客户机

==== TERMINAL 1 ====
$ ./example.py simple_client
Client: 7 is NOT The Answer to Life; data_int=0
Client: 42 IS The Answer to Life; data_int=1
$
$ ./example.py simple_client
Client: 7 is NOT The Answer to Life; data_int=0
Client: 42 IS The Answer to Life; data_int=1
$

==== TERMINAL 2 ====
$ ./example.py simple_server
Server formatted data: npint_data=7, addr=('127.0.0.1', 61549)
Server formatted data: npint_data=42, addr=('127.0.0.1', 61549)
Server formatted data: npint_data=7, addr=('127.0.0.1', 49335)
Server formatted data: npint_data=42, addr=('127.0.0.1', 49335)

代码

首先,让我们展示问题是否会重现:

==== TERMINAL 1 ====
$ ./example.py ported_client
Client received message: 'data=(b'\x00', ('127.0.0.1', 49478))', 'data_int=0'
Client has Not received The Answer to Life; data_int=0
$

==== TERMINAL 2 ====
$ ./example.py original_server

[No other output]

下面是一些代码(example.py的完整内容):

#!/usr/bin/env python

import sys
import socket
import argparse

import numpy as np

BUFSIZE = 50            # While the Python code sets it at 1024, MATLAB's sets it at 50. Both should accomodate for a uint8
SRV_COMMAND_VAL = 0     # replacing `commandVal`. You know, for testing.

server_host = "127.0.0.1"
server_port = 10001
client_host = server_host
client_port = server_port + 1

def ported_client(adaptive = False):
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.settimeout(15)     # Should match MATLAB's `waitdur`

    sock.bind((client_host, client_port))
    data = sock.recvfrom(BUFSIZE)
    data_int = np.frombuffer(data[0], dtype=np.uint8)[0]
    _server_port = data[1][1] if adaptive else server_port
    print(f"Client received message: '{data=}', '{data_int=}'")

    # I don't know what int8('error') or int8('success') return in MATLAB, but let's try returning THE answer:
    if data_int:    # != 0
        sock.sendto(np.uint8(42), (server_host, _server_port))
    else:           # == 0, is an empty sequence, False, None, ...
        # The MATLAB code seems to return `int8('error')` if data_int == 0, so let's Not return THE answer:
        sock.sendto(np.uint8(43), (server_host, _server_port))
    print(f"Client has {'' if data_int else 'Not '}received The Answer to Life; {data_int=}")

def adaptive_ported_client():
    return ported_client(adaptive=True)

def _srv_recv(sock):
    while True:
        data, addr = sock.recvfrom(BUFSIZE)
        print("Server received message: '%s'" % data)
        npint_data = np.frombuffer(data, dtype=np.uint8)[0]
        print(f"Server formatted data: {npint_data=}, {addr=}")

def original_server():
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.sendto(np.uint8(SRV_COMMAND_VAL), (client_host, client_port))

    sock.connect((client_host, client_port))
    _srv_recv(sock)

def bound_server():
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    # `socket.bind()` must happen before `socket.sendto()`, otherwise an `OSError: [Errno 22] Invalid argument` will be raised;
    # I assume this has something to do with port assignments during `sendto`, but that's only a speculation on my end.
    sock.bind((server_host, server_port))
    sock.sendto(np.uint8(SRV_COMMAND_VAL), (client_host, client_port))

    _srv_recv(sock)

def connected_server():
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.bind((server_host, server_port))

    # When connecting a socket to a specific remote host, use `send` and `recv` without the to/from suffixes
    sock.connect((client_host, client_port))
    sock.send(np.uint8(SRV_COMMAND_VAL))

    while True:
        data = sock.recv(BUFSIZE)
        print("Server received message: '%s'" % data)
        npint_data = np.frombuffer(data, dtype=np.uint8)[0]
        print(f"Server formatted data: {npint_data=}")

def simple_server():
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.bind((server_host, server_port))

    while True:
        data, addr = sock.recvfrom(BUFSIZE)
        npint_data = np.frombuffer(data, dtype=np.uint8)[0]
        print(f"Server formatted data: {npint_data=}, {addr=}")
        is_the_answer = npint_data == 42
        sock.sendto(np.uint8(is_the_answer), addr)

def simple_client():
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    sock.settimeout(15)     # Should match MATLAB's `waitdur`

    for the_answer_is in (7, 42):
        sock.sendto(np.uint8(the_answer_is), (server_host, server_port))
        data = sock.recvfrom(BUFSIZE)
        data_int = np.frombuffer(data[0], dtype=np.uint8)[0]
        print(f"Client: {the_answer_is} {'IS' if data_int else 'is NOT'} The Answer to Life; {data_int=}")

def parse_args():
    parser = argparse.ArgumentParser()
    parser.add_argument("run_type", choices=("ported_client", "adaptive_ported_client", "original_server", "bound_server", "connected_server", "simple_server", "simple_client"))
    return parser.parse_args()

def main(run_type):
    return globals()[run_type]()

if __name__ == "__main__":
    args = parse_args()
    sys.exit(main(args.run_type))

总结(终于...)

喜欢Hoodlumsolution,有一些保留。它很简单,并处理socket.close()(通过使用with上下文)。基本上,这是我的帖子的解决方案#2,只是更短,更可读。
尽管如此,我认为这是值得写的,可能会阐明这个问题的原因(或美化UDP,IDK)。

相关问题