質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.53%
Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

UDP

UDP(User Datagram Protocol)とは、トランスポート層のプロトコルであり、コネクション型のデータサービスです。IPネットワーク上の別のホストにコンピュータのアプリケーションがメッセージを送ることができ、転送チャンネルやデータ経路を設定する必要はありません。TCPに比べて高速であるが、信頼性が薄いという特徴があります。

非同期処理

非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。

Q&A

解決済

1回答

2073閲覧

Python: asyncioを用いて,複数クライアントからUDPデータを同時に受け取りたい

Yochyee

総合スコア2

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

UDP

UDP(User Datagram Protocol)とは、トランスポート層のプロトコルであり、コネクション型のデータサービスです。IPネットワーク上の別のホストにコンピュータのアプリケーションがメッセージを送ることができ、転送チャンネルやデータ経路を設定する必要はありません。TCPに比べて高速であるが、信頼性が薄いという特徴があります。

非同期処理

非同期処理とは一部のコードを別々のスレッドで実行させる手法です。アプリケーションのパフォーマンスを向上させる目的でこの手法を用います。

0グッド

0クリップ

投稿2022/12/21 00:33

編集2022/12/22 18:58

前提

python3.9を用いて,UDP/TCPによるソケット通信を行っています.サーバは,複数のクライアントからUDPでデータを受け取り,そのデータを加工し,TCPでクライアントに返送します.

実現したいこと

複数のクライアントからのUDPのデータを一台のサーバで並列(非同期)で捌きたいと考えています.
その際に,asyncioでタスクを複数作り,同時にUDPデータをrecvfrom()したいと考えています

発生している問題・エラーメッセージ

1つ目のクライアントからのデータは正常に受け取れますが,2つめのクライアントからのデータは受け取れず,recvfromがそのままタイムアウトしてしまいます.

ちなみに,asyncioは並列処理ではなく,シングルスレッドで動作していることは理解していますが,全く知見はありません.

該当のソースコード

10つのクライアントからUDPを受信しようと考えているため,def main()内のfor処理を10回繰り返し,10個のタスクを生成しています.

※主にソケット生成/受信/送信部分を見てもらえればよいと思います.

async.py

1import socket 2import asyncio 3 4 5M_Group = "239.1.2.3" 6LOCALHOST = socket.gethostbyname(socket.gethostname()) 7PORT = 1234 8 9 10FILE_NAME = 'send.txt' 11 12# UDPソケットの設定 13udp_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 14udp_sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF, socket.inet_aton(LOCALHOST)) 15udp_sock.setsockopt( 16 socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, 17 socket.inet_aton(M_Group) + socket.inet_aton(LOCALHOST) 18) 19# 自分の送ったマルチキャストパケットを自身で受け取らない 20udp_sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_LOOP, 0) 21udp_sock.bind(('', PORT)) 22udp_sock.settimeout(5) 23 24# udp_sock.connect((M_Group, PORT)) 25 26# TCPソケットの設定 27tcp_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 28 29 30file_data = "" 31len_data = 0 32 33with open(FILE_NAME, 'r') as f: 34 file_data = f.read() 35 len_data = len(file_data) 36 37 38async def recv_loss_packet(): 39 40 print("time to wait for response...") 41 try: 42 res_data, (src_ip, src_port) = udp_sock.recvfrom(1024) 43 44 print(f'src_ip: {src_ip}, src_port: {src_port}') 45 46 47 # packet loss seq split 48 recv_seq_ary = res_data.decode().split(",") 49 recv_seq_ary.pop(-1) 50 51 recv_seq_ary = list(map(int, recv_seq_ary)) 52 print(recv_seq_ary) 53 54 # packet loss data resend 55 tcp_sock.connect((src_ip, src_port)) 56 57 for i in recv_seq_ary: 58 chunk = file_data[i*10:i*10+10] 59 chunk = str(i) + ":" + chunk 60 print(chunk) 61 62 tcp_sock.send(chunk.encode()) 63 64 except: 65 print(f"Timeout!") 66 67 68async def main(): 69 count = 0 70 71 # データを指定バイトごとに分割 72 chunks = [file_data[i:i+10] for i in range(0, len_data, 10)] 73 74 for chunk in chunks: 75 count+=1 76 chunk = str(count) + ":" + chunk 77 print(chunk) 78 79 udp_sock.sendto(chunk.encode(), (M_Group, PORT)) 80 time.sleep(0.05) 81 82 for i in range(10): 83 asyncio.create_task(recv_loss_packet()) 84 85 await asyncio.sleep(10) 86 87asyncio.run(main()) 88

試したこと

"発生している問題"でも述べましたが,2つのクライアントで試してみたところ,1つめのクライアントからは受け取れて,2つめのクライアントからは,データを受け取れませんでした.

並列処理ではないため,2つのクライアントからの送信を,十分な間隔(2秒)を空けて送信しましたが結果は変わりませんでした.

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

can110

2022/12/21 02:40

ブロッキングソケットを利用しているのではないか?など詳細が不明なので ソケット生成なども含めたコード部分、できれば現象が再現する最小限のコード全体を提示すると回答得られやすくなるかと思います。
bsdfan

2022/12/21 02:42

udpで受けているのに、tcpで返そうとしていませんか? そこで止まっていないでしょうか。
Yochyee

2022/12/21 02:54 編集

すいません.udpで受け取ったデータを加工をしてTCPで返信しています. 止まっているのは,udpのrecvfromの部分です.
matukeso

2022/12/21 03:21

udp_sockをnonblockにした上、recv_loss_packetはasyncioのeventloopのadd_readerからのコールバックで呼ばれる必要があるでしょう。
Yochyee

2022/12/21 04:16 編集

@matukeso どこでnonblockされているでしょうか?settimeoutの部分でしょうか? settimeoutでコントロールが戻ってくるのに,わざわざadd_readerのコールバックを呼ぶ必要がないということですか?
matukeso

2022/12/21 04:52

nonblockにしてないから、すべき、と言いました。settimeoutつけても、recfronはtimeoutになるまでpythonに制御が戻らないから意味がなくて、、add_readerで読めるタイミングを通知してもらう必要があるんでしょ。
Yochyee

2022/12/21 06:05

@matukeso ご提言感謝します!参考にさせていただきます。
guest

回答1

0

自己解決

udpのsocketをnon blockingにし,add_readerでイベントを登録することで実装できました.
ちなみに私の環境であるwindowsでは,asyncio.get_event_loop()が使えないようなので代替記法で書いています.

本質問では,UDPで受信した後,TCPで返送するプログラムでしたがTCP部分が正常に動かなかったため.TCPの部分は削除しました.

async.py

1M_Group = "239.1.2.3" 2LOCALHOST = socket.gethostbyname(socket.gethostname()) 3PORT = 1234 4 5# UDPソケットの設定 6udp_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) 7udp_sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_IF, socket.inet_aton(LOCALHOST)) 8udp_sock.setsockopt( 9 socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, 10 socket.inet_aton(M_Group) + socket.inet_aton(LOCALHOST) 11) 12# 自分の送ったマルチキャストパケットを自身で受け取らない 13udp_sock.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_LOOP, 0) 14udp_sock.bind(('', PORT)) 15 16# ノンブロッキングに指定 17udp_sock.setblocking(False) 18 19 20FILE_NAME = 'send.txt' 21file_data = "" 22len_data = 0 23 24with open(FILE_NAME, 'r') as f: 25 file_data = f.read() 26 len_data = len(file_data) 27 28 29def recv_loss_packet(): 30 31 print("time to wait for response...") 32 try: 33 res_data, (src_ip, src_port) = udp_sock.recvfrom(1024) 34 35 print(f'src_ip: {src_ip}, src_port: {src_port}') 36 37 except: 38 print(f"Timeout!") 39 40 41count = 0 42 43# データを指定バイトごとに分割 44chunks = [file_data[i:i+10] for i in range(0, len_data, 10)] 45 46for chunk in chunks: 47 count+=1 48 chunk = str(count) + ":" + chunk 49 print(chunk) 50 51 udp_sock.sendto(chunk.encode(), (M_Group, PORT)) 52 time.sleep(0.05) 53 54 55# イベントループの取得 & セット 56# 以下3行は,windows以外なら,asyncio.get_event_loop() のみで良い 57selector = selectors.SelectSelector() 58loop = asyncio.SelectorEventLoop(selector) 59asyncio.set_event_loop(loop) 60 61# udp_sockのファイルディスクリプタが空いたら,紐づけられた関数を実行 62loop.add_reader(udp_sock.fileno(), recv_loss_packet) 63 64# ループを回す 65try: 66 loop.run_forever() 67finally: 68 loop.close()

投稿2022/12/22 09:43

編集2022/12/22 09:58
Yochyee

総合スコア2

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.53%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問