pythonメニュー

UDP通信の確認

python の socketクラスのTCP通信を利用して、文字列をUDP通信相手に送るプログラムと 受信プログラムを紹介します。(MicroPythonでも動作します。)

STEP1

受信プログラムを示します。受信したバイト列を文字列に変換して、表示する繰り返しです。
import socket # udp_receive.py

UDP_IP = "192.168.0.110"
UDP_PORT = 3001

sock=socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
local_addr=(UDP_IP, UDP_PORT)
sock.bind(local_addr) 
print("start udp receive")
SIZE = 128
while True:
    msg, client_addr = sock.recvfrom(SIZE)
    print(len(msg), "byte受信", end="")
    s=msg.decode()
    print(s, end="")
    if s.startswith("Quit") : break

sock.close()


送信プログラムを示します。キー入力と送信を繰り返す
import socket # udp_send.py

UDP_IP = "192.168.0.110"
UDP_PORT = 3001

sock=socket.socket(socket.AF_INET, type=socket.SOCK_DGRAM)
print("ソケット生成")

SIZE = 128

sockSend=socket.socket(socket.AF_INET, type=socket.SOCK_DGRAM)

send_addr=(UDP_IP,UDP_PORT )#IPアドレスとポート番号
while True:
    cmd=input("送信文字列/Quit>>")
    cmd += "\n"
    send_len=sockSend.sendto(cmd.encode('utf-8'), send_addr)
    print(cmd, send_len, "byte 送信しました")
    if cmd[0:4] == "Quit": break

sock.close()


STEP2

上記プログラムを、一つのプログラムにまとめました。
import socket # pc_udp
import _thread

UDP_IP = "192.168.0.110"
UDP_PORT = 3001

sock=socket.socket(socket.AF_INET, type=socket.SOCK_DGRAM)
print("ソケット生成")
sock.bind( (UDP_IP , UDP_PORT) )
SIZE = 32

def receive_loop():
    print("受信ループ開始")
    while True:
        msg,client_addr= sock.recvfrom(SIZE) #受信
        print("受信情報:",type(msg), client_addr)
        print(msg.decode(encoding='utf-8'), "を受信しました。")

thread_id = _thread.start_new_thread( receive_loop, ())
import time
time.sleep(1)

sockSend=socket.socket(socket.AF_INET, type=socket.SOCK_DGRAM)

sever_addr=(UDP_IP,UDP_PORT )#IPアドレスとポート番号
while True:
    s=input("W/A/X/D/S/B/QUIT/Q==>").upper()
    if s.startswith("W") :
        cmd="Forward\r\n"
    elif s.startswith("A") :
        cmd="Left\r\n"
    elif s.startswith("X") :
        cmd="Back\r\n"
    elif s.startswith("D") :
        cmd="Right\r\n"
    elif s.startswith("QUIT") :
        cmd="ServerStop\r\n"
    elif s.startswith("Q") :
        break
    else:
        cmd="Beep\r\n"
    #
    send_len=sockSend.sendto(cmd.encode('utf-8'), sever_addr)
    print(cmd, send_len, "byte 送信しました")

sock.close()

マルチキャスト

送信コード

# Python のマルチキャスト送信の簡単なコード例
import socket

MCAST_GRP = "239.192.0.0"
MCAST_PORT = 59001

while True:
    if input("Enter で送信 quitで終了>") == "quit" : break
    sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    message = "192.168.0.110,59002" # 送信文字列
    sock.sendto(message.encode('utf-8'), (MCAST_GRP, MCAST_PORT))
    print(f"'{message}' を {MCAST_GRP}:{MCAST_PORT} に送信しました。")
    sock.close()

受信コード

上記送信に対するの受信例
import socket
import struct

def receiveIpMulticast(multicast_group, portNo): #マルチキャスト
    server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # ソケットの作成
    server_socket.bind(('', portNo)) # ソケットにアドレスをバインド

    # マルチキャストグループに参加
    # group = ipv4_inet_pton(multicast_group) # 引数のIPアドレス文字列に対応する4byteバイナリ取得(Micropythonの場合)
    group = socket.inet_aton(multicast_group)

    mreq = struct.pack('4sL', group, 0)
    server_socket.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)
    # 受信
    data, addr = server_socket.recvfrom(1024)  # データとアドレスを受信 (最大で1024 バイトのデータを受信)
    print(f"受信: {data.decode('utf-8')} from {addr}") 
    # 上記結果例:「受信: 192.168.0.110,59002 from ('192.168.0.110', 51201)」戻り値addrで送信側のIPアドレスが得られる
    return data

def receiveIpMulticast_new(multicast_group, portNo): #マルチキャスト
    wlan = network.WLAN(network.STA_IF)
    local_ip = ipv4_inet_pton(wlan.ifconfig()[0])
    print(f"local_ip:{local_ip}")
    group = ipv4_inet_pton(multicast_group)

    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.bind(('', portNo))

    mreq = struct.pack('4s4s', group, local_ip)
    s.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)

    print(f"Multicast-----{multicast_group}:{portNo}-----Wait !!!")

    #s.settimeout(5)  # optional
    try:
        data, addr = s.recvfrom(1024)
        print(f"Multicast-----receive data: {data.decode()} from {addr}")
        s.close()
        return data
    except OSError as e:
        print("Multicast receive failed:", e)
        s.close()
        return None

ip_port_bin = receiveIpMulticast("239.192.0.0",  59001) # 接続サーバIPアドレス受信
ip_port_a = ip_port_bin.decode('utf-8').split(',')
print(f"受信データ:{ip_port_bin.decode('utf-8')}")
ip=ip_port_a[0]
portnumber=int(ip_port_a[1])
print(f"Multicast Recive IP:PORT:{ip}:{portnumber}")



Raspberry Pi Pico WのMicroPython で検証したネットワークプログラム

STA(ステーション)モードでの検討です。 起動時に,既存のWifiアクセスポイントに接続し、IPアドレスを得ます。
そのIPアドレスをブロードキャストで配信します。
クライアントPCから、UDPでそのPCのアドレスとポートを得ます。
取得したPCのアドレスに、UDPユニキャストでテスト用データを送り続けます。
import socket
import network
import struct
from machine import Pin
import utime
import gc

SSID='〇〇'# Wifi用
PW = '〇〇〇'# 上記に接続するためのパスフレーズ

def wifi__connect():# STA(ステーション)モードで既存のWi-Fiに接続して、確定したらリターン
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    wlan.connect(SSID, PW)
    while wlan.isconnected() == False:
        print('Connecting to Wi-Fi AP')
        utime.sleep(1)
    #
    wlan_status = wlan.ifconfig()
    return wlan_status

target_pin = Pin(14,Pin.OUT) # 使用LED(GPIO14接続)利用の準備
onboard_led = Pin( "LED", Pin.OUT )

target_pin.high()# 外付けLED点灯
wlan_status = wifi__connect()# Wifi接続
print( f'''Connected!
    Pico IP Address:{wlan_status[0]} Netmask:{wlan_status[1]}
    Default Gateway:{wlan_status[2]} Name Server: {wlan_status[3]}''' ) # Wifi接続情報表示
utime.sleep(1)
target_pin.low()# 外付けLED消灯

#ユニキャスト初期化
udp_portNo=59154
udp_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udp_sock.bind(('0.0.0.0', udp_portNo))
udp_sock.setblocking(False)  # ソケットをノンブロッキングモードに設定
print(f"UDP 受信待機中 (ポート {udp_portNo})...")

# ブロードキャスト送信の準備
BROADCAST_PORT = 59001 
MESSAGE = f"{wlan_status[0]},{udp_portNo}"
MESSAGE = MESSAGE.encode('utf-8')
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)# ブロードキャストを有効にする 

# 自身picoWのIPアドレスをブロードキャストで配信をUDP受信まで繰り返す
count=0
while True:
    sock.sendto(MESSAGE, ('255.255.255.255', BROADCAST_PORT))
    print(f"[{count}] BROADCAST Sent: {MESSAGE}")
    try:
        data, udp_addr = udp_sock.recvfrom(1024) # UDP ユニキャスト受信
        print(f"受信: {data.decode('utf-8')} from {udp_addr}")
        break  # UDP受信でループ抜ける
    except OSError:# 接続なしの時はここに来る
        print("まだ接続なし。処理続行。")
    utime.sleep(2)
    count+=1

print(f"送信先アドレス:{udp_addr}が確定")
udp_sock.setblocking(True)  # ソケットをブロッキングモードに戻す
count=0
while True:
    udp_sock.sendto(b'\x01\x02\x03\x04\x05\x06\x07', udp_addr)  # テスト用7バイト送信(ここでOSError: [Errno 113] EHOSTUNREACH)
    utime.sleep(0.01)
    count+=1
    #gc.collect()
    print(f"count:{count}") # この行をコメント化すると、エラーが少し減る

Raspberry PI pico WをSTAモードにおいて、MicroPythonによるUDP送信で、 時々発生する[Errno 103]エラーで、安定動作が得られません。
(APモードであれば安定して動作が確認できているのですが・・)
UDP通信は送信先が存在しなくても送信できるので、その状態での送信処理を行うと、送信の繰り返しが速くなり、
しかもエラーが起きない結果が得られました。
また途中で受信プログラムを起動すると送信の繰り返しが遅くなる結果が得られています。
受信プログラムをPythonから次のUnit用コードに変更すると、エラーの頻度が低下したように感じます。

以上の実験で、エラーが起きることは避けられないと判断しました。
そこで、 エラーが起きたらクロー時してソケットを作り直し、送信を続けられるように 上記コードの063行〜070行をを次のコードに変更しました。
count=0
while True:
    try:
        udp_sock.sendto(b'\x01\x02\x03\x04\x05\x06\x07', udp_addr)  # テスト用の7バイト送信
    except OSError as e:
        udp_sock.close() # 上記の送信で OSError: [Errno 113] があったら、ソケットを閉じる
        print(e)
        #gc.collect() # この2行は影響しない実験結果が得られました。
        #utime.sleep(2)
        udp_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # ソケットを作り直す。
        udp_sock.bind(('0.0.0.0', udp_portNo))
    utime.sleep(0.01) # 送信間隔
    count+=1
    print(f"count:{count}") # 送信回数の表示

以上の変更で140000000回以上の送信しましたが、止まることなく動作しました。

上記と通信するPC側のコード(sensor2_client.py)を下記に示す。 下記をUnity用にしたC#コード

import socket
import struct
import time

def receiveBroadcast(portNo): #ブロードキャスト受信関数
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    s.bind(('', portNo))
    print("Waiting for broadcast...")
    data, addr = s.recvfrom(1024)
    print(f"Received: {data} from {addr}")
    s.close()
    return data

ip_port_bin = receiveBroadcast(59001) # 接続サーバIPアドレスとポート番号の受信
print(f"受信データ:{ip_port_bin}")
rec_data=ip_port_bin.decode('utf-8')
ip_port_a = rec_data.split(',')
udp_ip=ip_port_a[0]
udp_portNo=int(ip_port_a[1])

hostname=socket.gethostname()
locat_ip = socket.gethostbyname(hostname)
locat_ip='192.168.0.110'
print(f"このPCのIPアドレス:{locat_ip}")
send_message=f"{locat_ip},{udp_portNo}"
send_message=send_message.encode('utf-8')

#ユニキャスト
udp_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udp_sock.bind(('0.0.0.0', udp_portNo))
udp_sock.setblocking(False)  # ソケットをノンブロッキングモードに設定
print(f"UDP 受信待機中 (ポート {udp_portNo})...")

# 自身の情報を送信して、受信できたら送信を止めます。
while True:
    print(f"sendto( {send_message},{(udp_ip, udp_portNo)} )")
    time.sleep(1)
    udp_sock.sendto(send_message, (udp_ip, udp_portNo))
    try:
        # 返信の受信を試みる(ノンブロッキング)
        data, addr = udp_sock.recvfrom(1024) # 1024バイトまで受信
        print(f"受信データ:{data}")
        break
    except OSError as e:
        pass # 受信なし

udp_sock.setblocking(True)  # ソケットをブロッキングモードに戻す
count=1
while True:
    data, addr = udp_sock.recvfrom(1024) # 1024バイトまで受信
    print(f"{count}回目の受信データ:{data}")
    count+=1

udp_sock.close()