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

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

新規登録して質問してみよう
ただいま回答率
85.48%
C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

UDP

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

非同期処理

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

Q&A

解決済

1回答

2073閲覧

C# UDP受信処理を非同期で行いたい

SparklingLemon

総合スコア10

C#

C#はマルチパラダイムプログラミング言語の1つで、命令形・宣言型・関数型・ジェネリック型・コンポーネント指向・オブジェクティブ指向のプログラミング開発すべてに対応しています。

UDP

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

非同期処理

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

0グッド

1クリップ

投稿2022/12/15 04:00

編集2022/12/15 06:29

前提

C# .NetFrameWork4.6.2で開発を行っております。

UDP送信クラス(同期処理)と受信クラス(非同期処理)をそれぞれ作成したところ、
UDPの送受信が正しく動作する時と動作しない時があり原因がわからず悩んでおります。

UDP送信側はサーバに配置されるコンソールアプリでUDP受信側はクライアントPCに配置されるWindowsFormsアプリを想定しています。
受信側は画面を伴ったアプリの為、画面のロックを回避したいと考えており、
受信待ちの処理と受信後に行う処理は非同期で行う想定で実装しております。

wiresharkを使用してパケットを確認したところ動作しない時はUDP送信は行われているため
受信側の実装に不備があるものと思われます。

知識不足により見よう見まねで作成しているため非同期処理の部分がうまく出来ていないのではないかと
考えておりますが、実装不備な箇所をご指摘いただけないでしょうか。

実現したいこと

UDP受信が行われない事があるため、
ソフト面・ハード面のどちらが原因であるか突き止め、
ソフトが原因であれば対応を行いたい。

該当のソースコード

送信側クラス

C#

1 public class SendHelper : IDisposable 2 { 3 UdpClient client { get; set; } 4 5 public SendHelper(int port) 6 { 7 client = new UdpClient(port); 8 } 9 10 public void SendMessage(string message, string destinationAddress, int destinationPort) 11 { 12 client.Connect(new IPEndPoint(IPAddress.Parse(destinationAddress), destinationPort)); 13 var enc = Encoding.GetEncoding("utf-8"); 14 var s = Encoding.UTF8.GetBytes(message); 15 client.Send(s, enc.GetByteCount(message)); 16 } 17 18 public async void SendMessageAsync(string message, string destinationAddress, int destinationPort) 19 { 20 client.Connect(new IPEndPoint(IPAddress.Parse(destinationAddress), destinationPort)); 21 await client.SendAsync(Encoding.UTF8.GetBytes(message), message.Length); 22 } 23 24 ~SendHelper() 25 { 26 Dispose(); 27 } 28 29 public void Dispose() 30 { 31 if (client != null) 32 { 33 client.Close(); 34 client = null; 35 } 36 } 37 }

受信側クラス

C#

1 public class RecieveHelper : IDisposable 2 { 3 UdpClient client { get; set; } 4 5 public RecieveHelper(string ipAddress, int port) 6 { 7 var address = IPAddress.Parse(ipAddress); 8 client = new UdpClient(new IPEndPoint(address, port)); 9 } 10 11 ~RecieveHelper() 12 { 13 Dispose(); 14 } 15 16 public void Dispose() 17 { 18 if (client != null) 19 { 20 // 排他制御 21 lock (this) 22 { 23 client.Close(); 24 client = null; 25 } 26 } 27 } 28 29 public delegate void OnRecieveAction(string s); 30 31 public async void ListenStart(OnRecieveAction action) 32 { 33 while (true) 34 { 35 Task<UdpReceiveResult> receiveTask; 36 // 排他制御 37 lock (this) 38 { 39 if (client == null) 40 { 41 // Disposeされたのでループを抜ける 42 break; 43 } 44 // ヌルポ回避 ←上記if文と下記コードが無い場合、Dispose()で破棄された時に例外が発生 45 receiveTask = client.ReceiveAsync(); 46 } 47 // 非同期で受信待機 48 var result = await receiveTask; 49 50 var data = Encoding.UTF8.GetString(result.Buffer); 51 await Task.Run(() => { 52 action(data); 53 }); 54 } 55 } 56 }

受信側クラスを使用しているコード

C#

1RecieveHelper = new RecieveHelper(AppConfig.IPAddress, AppConfig.Port); 2 3RecieveHelper.ListenStart(str => 4{ 5 try 6 { 7 //受信後の処理 8 } 9 catch (Exception e) 10 { 11 // エラー処理 12 } 13});

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

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

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

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

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

退会済みユーザー

退会済みユーザー

2022/12/15 04:12

例外はスローされてないのですか? スローされてるけど、 > public async void ListenStart では catch できないということでは? void を Task に変えたらどうなりますか?
kikukiku

2022/12/15 04:16

ログを入れるなりして、どこまで正常に動作しているかを絞りこみましょう。 また、どんなときに発生するかわかっていますか? わかっているなら、それも記述しましょう。 例えば、PCがスリープしているときにはアプリも停止しているので 受信しないとうことはないでしょうか?
dodox86

2022/12/15 04:22

> UDP受信が行われない事があるため、 必ず受信されるよう 現状のコードにも問題がありそうではありますが、そもそもUDPで常に確実な受信を求めるのも要求仕様としていかがなものかと。
kikukiku

2022/12/15 04:57

>そもそもUDPで常に確実な受信を求めるのも要求仕様としていかがなものかと。 確かにそうですね。 UDPはプロトコル内に再送機能はないので。。。
退会済みユーザー

退会済みユーザー

2022/12/15 05:10

それから while (true) という無限ループでまわすのはやめることを考えましょう。そういうのが良い結果になることはないはず。参考にした記事にそういうのがあったらみない方が良いと思います。
SparklingLemon

2022/12/15 06:05 編集

>では catch できないということでは? void を Task に変えたらどうなりますか? Task に変えると再現しなくなっているのですが、元々数十分に1度発生するかしないかといった具合だったので直っているのかわからないです。 試しにVoidに戻したところ再発してはいます。 >例外はスローされてないのですか? スローされてるけど、 との事でしたのでTask に変えただけでは例外が発生するものと思っていましたが、 Catchには入ってきていない状態です。 また、記載したコードが本実装と異なることを危惧し、記載したコードにより新プロジェクトを作成してみましたが例外が発生することがなかったのですが、環境の違いでしょうか? >ログを入れるなりして、どこまで正常に動作しているかを絞りこみましょう。 ログ出力する箇所を増やしてみましたが上記の通り、Taskに変更したところ再発しなくなり よくわからない状況が続いています。 >また、どんなときに発生するかわかっていますか? 条件は全くわかっていない状況です。 サーバ側はもちろん常時稼働しており一定周期で送信を繰り返しており、 クライアント側もPCは起動しっぱなしで受信をし続けています。 (受信失敗した後も稼働は続けており、失敗後に成功する時もあります。) >現状のコードにも問題がありそうではありますが、そもそもUDPで常に確実な受信を求めるのも要求仕>様とし>ていかがなものかと。 仰る通りです。 稼働環境が有線かつサーバ・クライアントが物理的に同じ建物内に存在するため パケット欠けが起こるにしても頻度は低いものと想定しているのですが、 頻発するものなのでしょうか(環境によると思いますので一般的にはどうなのかと疑問に思っております) >UDPはプロトコル内に再送機能はないので。。。 仰る通りです。 >それから while (true) という無限ループでまわすのはやめることを考えましょう。そういうのが良い結>果にな>ることはないはず。参考にした記事にそういうのがあったらみない方が良いと思います。 そうなんですね。 クライアント側は受信し続けたいのですが、どういった実装が一般的であるかご教示願う事はできますでしょうか。 いまいちどうするべきか想像がついておらず、申し訳ないです。 コードをかいて頂かずとも、「~クラスの~メソッドを使えばいい」などのアドバイスを頂ければ幸いです。
KOZ6.0

2022/12/15 06:07

送信側クラスには、あれ?と思うコードがあります。 ・SendMessage var enc = Encoding.GetEncoding("utf-8"); var s = Encoding.UTF8.GetBytes(message); client.Send(s, enc.GetByteCount(message)); ↓ var s = Encoding.UTF8.GetBytes(message); client.Send(s, s.Length); ・SendMessageAsync await client.SendAsync(Encoding.UTF8.GetBytes(message), message.Length); ↓ var s = Encoding.UTF8.GetBytes(message); await client.SendAsync(s, s.Length); ですが、ASCII コードしか送信しないのであれば問題は無いと思われます。 2バイトコードとか送信していますか? 受信側は // 非同期で受信待機 var result = await receiveTask; RecieveHelper を Dispose すると、ObjectDisposedException が発生する可能性があるので、catch しておいたほうが良さそうです。 サンプル作って実行してみましたが、特にデータが抜けることもなく受信できています。
dodox86

2022/12/15 06:08

> >現状のコードにも問題がありそうではありますが、そもそもUDPで常に確実な受信を求めるのも要求仕>様としていかがなものかと。 > 仰る通りです。 > 稼働環境が有線かつサーバ・クライアントが物理的に同じ建物内に存在するため > パケット欠けが起こるにしても頻度は低いものと想定しているのですが、 > 頻発するものなのでしょうか(環境によると思いますので一般的にはどうなのかと疑問に思っております) 回線品質が高く、サーバー、クライアント共にしっかり作られていれば損失は少ないでしょうけれども、 一般的に、送信の頻度が高くても扱うデータとして欠落があっても困らないものをUDPにします。ブロードキャストとか、センサーの送りっ放しデータとか。 順序性があったり、途中で欠落するとシステムとしてその後の処理が破綻するようなものではUDPは普通、採用しません。UDPを使いつつも、クライアントからの要求に応じて必要なデータを再送可能なシステムであればその限りでは無いと思いますけれども。
kikukiku

2022/12/15 06:14

頻度が低いようなので、単に、UDPパケットがロスしているのではないでしょうか? だから例外が発生していないのではないか? まずは元のソースに戻して、例外をすべてキャッチし、ログを出すようにして 例外が発生しているかを確認したいですね。 例外が発生したらその内容を見たい。 例外が発生していなければ、パケットロスではないでしょうか。
kikukiku

2022/12/15 06:17

パケットロスは、UDP送信したタイミングで、別パケットがLAN上を占有していた場合でも 発生すると思います。LAN上は同時に1パケットしか情報を流せません。
dodox86

2022/12/15 06:21

私のコメントで言い方を変えると、「多少のロスがあってもそれは既知のこととしてシステムとしてあまり困らないけれども、現状、最大限に受信できるようにしたい」ことでのご質問であればそれはそれで良いと思います。コメントがたくさんあっても困ってしまうでしょうから、この辺で。
kikukiku

2022/12/15 06:21

パケットロスかどうか確実に判定したいのであるならば、 ・アプリで受信時の時間を記録 ・アプリで送信時の時間を記録 ・受信側PC内のパケットキャプチャを取得 上記3つのログを突き合せれば、パケットロスがわかると思います。
SparklingLemon

2022/12/15 06:27

KOZ6.0さん >2バイトコードとか送信していますか? はい、2バイト文字の送信をすること自体の是非はともかく、2バイト文字の送信を行っております。 >RecieveHelper を Dispose すると、ObjectDisposedException が発生する可能性があるので、catch >しておいたほうが良さそうです。 ご指摘ありがとうございます。 Catchを追加しておきます。 >サンプル作って実行してみましたが、特にデータが抜けることもなく受信できています。 わざわざ確認して頂きありがとうございます。 dodox86さん >順序性があったり、途中で欠落するとシステムとしてその後の処理が破綻するようなものではUDPは >普通、採用しません。UDPを使いつつも、クライアントからの要求に応じて必要なデータを再送可能な >システムであればその限りでは無いと思いますけれども。 ポーリング処理のような送信成功したかどうか判断するような実装は存在せず、 再送可能な作りとはなっていないため、仰る通りUDP通信なので 「必ず受信されるよう改善を行いたい。」という実現目標は不可能であると思いますので修正いたしました。 kikukikuさん >まずは元のソースに戻して、例外をすべてキャッチし、ログを出すようにして >例外が発生しているかを確認したいですね。 はい。受信失敗時に例外が発生していないか再度試してみようと思います。 (相談前のコードでは例外が発生していませんでしたが、Catch出来ていない可能性のご指摘を受けてCatch処理を追加しましたので例外をつかむことが出来るかもしれません) >パケットロスは、UDP送信したタイミングで、別パケットがLAN上を占有していた場合でも >発生すると思います。LAN上は同時に1パケットしか情報を流せません。 そうなんですね。 知らない知識でしたが、LAN上は同時に1パケットしか情報を流せないということであれば 割と頻繁にパケットロスが起きそうではありますね。
kikukiku

2022/12/15 06:34

>そうなんですね。 >知らない知識でしたが、LAN上は同時に1パケットしか情報を流せないということであれば >割と頻繁にパケットロスが起きそうではありますね。 同じHUB内(スイッチングHUB)の通信であれば、 1対1の通信になるので、ロスはないと思います。 ※LAN構成によっても変わってきますね。
SparklingLemon

2022/12/15 06:39

kikukikuさん >パケットロスかどうか確実に判定したいのであるならば、 >・アプリで受信時の時間を記録 >・アプリで送信時の時間を記録 >・受信側PC内のパケットキャプチャを取得 >上記3つのログを突き合せれば、パケットロスがわかると思います。 アプリ内のログ出力箇所を増やして確認したところ、送信側も受信側も問題なさそうに見えています。 クライアントが受信開始 ⇒ サーバが送信 ⇒ クライアントが受信時の処理開始・終了 ⇒ クライアントが受信開始 ⇒ サーバが送信(これが受信出来ていない) ⇒ サーバが送信 ⇒ クライアントが受信時の処理開始・終了 ⇒ クライアントが受信開始 上記のようなログになっています。 wiresharkにより送信側のパケット解析を行った結果は上記と同様でした。 受信側PC内のパケットキャプチャ取得方法がよくわかっていないので調べてみたいと思います。
kikukiku

2022/12/15 06:43

>知らない知識でしたが、LAN上は同時に1パケットしか情報を流せないということであれば >割と頻繁にパケットロスが起きそうではありますね。 誤解のないようにもう少しコメントします。 ルータやHUB内にもバッファをもっているので、これがあるために 頻繁にパケットロスが起きないようにはなっています。
SparklingLemon

2022/12/15 07:36

kikukikuさん 色々と教えて頂きありがとうございます。
KOZ6.0

2022/12/15 07:49

>はい、2バイト文字の送信をすること自体の是非はともかく、2バイト文字の送信を行っております。 2バイト文字を送信するのは問題ありません。 SendMessageAsync は送信長が間違っているので修正しましょう。 でもやはり UDP ではデータ抜けは発生しますのであきらめるしかないと思います。
tmp

2022/12/18 01:29

もし、再送処理をいれるなら返事さえもパケットロスをすることを考えないと問題が発生するケースもあります。 私が見たケースでは、パケットロスで再送はするようにつくられていたのですが、受け取った返事をもらう、一定時間に返事がこなかったら再送という単純実装でした 一見行けそうにもみえますが、パケットの返事さえもパケットロスをするってことを考えてなかったので パケットロスしなかったパケットを再送してしまい、たまにデータが倍になってました。
SparklingLemon

2022/12/19 06:11

KOZ6.0さん >SendMessageAsync は送信長が間違っているので修正しましょう。 ありがとうございます。修正いたします。 tmpさん やはりUDP通信では送受信の確実性は担保できないという事なのですね。 「Void⇒Task」に変更した状態の実行ファイルで3日ほど稼働状態を維持して受信失敗が発生するか試してみました。 ログと受信後の後処理で行われるDBの登録結果を確認したところ、受信が失敗した痕跡は見られないため再現しなかったものと思われます。 知識不足のためコード変更により解決したものなのか、ハード的なトラブルにより起きていたものなのか判断がつかないのですが、いつまでも回答を受け付けている状態にするのも問題があると思いますので、 勝手ながら自己解決という形でクローズさせて頂こうと思います。 後日何か分りましたら追記させて頂こうと思います。
退会済みユーザー

退会済みユーザー

2022/12/19 06:31

> 「Void⇒Task」に変更した状態の実行ファイルで3日ほど稼働状態を維持して受信失敗が発生するか試してみました。ログと受信後の後処理で行われるDBの登録結果を確認したところ、受信が失敗した痕跡は見られないため再現しなかったものと思われます。 それはたまたま問題が発生しなかったというだけでしょう。 非同期プログラミングのベスト プラクティス https://learn.microsoft.com/ja-jp/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming "async void メソッドからスローされた例外は本質的にはキャッチできない" "上から下に (または下から上に) 非同期コードが他の非同期コードを呼び出す、または呼び出される場合に最もうまく機能する"
guest

回答1

0

自己解決

「質問へのコメント」のやりとりの通り、これといった確証をもった解決となったわけではないですが、
UDP受信が失敗する(受信されない)事象が再現することがなくなったため、
いつまでも回答を受け付けている状態にするのも問題があると思いますので、
解決扱いとさせて頂こうと思います。

行った対応
①関数の「Void ⇒ Taskに変更」
②Lanケーブルの交換
③ファイアウォールの受信規則・送信規則の再設定
④サーバ・クライアントの再起動
⑤ルータの交換

後日何かわかることがあれば、追記させていただきます。

投稿2022/12/19 06:21

編集2022/12/19 06:22
SparklingLemon

総合スコア10

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問