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

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

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

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

TCP

TCP(Transmission Control Protocol)とは、トランスポート層のプロトコルで、コネクション型のデータサービスです。

Q&A

解決済

1回答

2512閲覧

Java : Nagleアルゴリズムで遅延するの?

Rai_Feit

総合スコア7

Java

Javaは、1995年にサン・マイクロシステムズが開発したプログラミング言語です。表記法はC言語に似ていますが、既存のプログラミング言語の短所を踏まえていちから設計されており、最初からオブジェクト指向性を備えてデザインされています。セキュリティ面が強力であることや、ネットワーク環境での利用に向いていることが特徴です。Javaで作られたソフトウェアは基本的にいかなるプラットフォームでも作動します。

TCP

TCP(Transmission Control Protocol)とは、トランスポート層のプロトコルで、コネクション型のデータサービスです。

0グッド

0クリップ

投稿2019/11/20 07:29

編集2019/11/22 10:03

初学者です。不十分な知識で僭越ながら質問させてください。よろしくお願いします。

実現したいこと

Javaのソケット通信において, 勉強と動作確認のため, Nagleアルゴリズムによる遅延をわざと発生させたいです.

現在は1sごとに4byteクライアント側から送信しており, クライアント側送信時刻とサーバ側送信時刻をコンソールで見ています.

小さいデータを送信するとNagleバッファに入り, 送信時刻と受信時刻に200ms程の差異が現れると思うのですが, 現在ほぼ時間の差異はありません.

Nagleアルゴリズムによる遅延はどうすれば発生するのでしょうか? ご教授お願い致します.

該当のソースコード

参照元コード https://oshiete.goo.ne.jp/qa/7317690.html

サーバ側

java

1public class Server { 2 public static ServerSocket ss = null; 3 public static Socket soc = null; 4 private static InputStream is = null; 5 private static OutputStream os = null; 6 7 public static void main(String[] args) { 8 9 try { 10 // サーバソケット生成 11 ss = new ServerSocket(5000); 12 soc = ss.accept(); 13 is = soc.getInputStream(); 14 Thread rcvTh = new ServerRcvThread(is); 15 rcvTh.start(); 16 // 10秒スリープ 17 try { 18 Thread.sleep(10000); 19 } catch (Exception e) { 20 e.printStackTrace(); 21 } 22 // スレッド停止 23 rcvTh.stop(); 24 } catch (IOException e) { 25 e.printStackTrace(); 26 } finally { 27 try { 28 is.close(); 29 soc.close(); 30 ss.close(); 31 } catch (IOException e) { 32 e.printStackTrace(); 33 } 34 } 35 } 36} 37class ServerRcvThread extends Thread { 38 private static InputStream ins = null; 39 40 ServerRcvThread(InputStream is) { 41 this.ins = is; 42 } 43 44 public void run() { 45 DateFormat format = new SimpleDateFormat("yyyy MM dd hh:mm:ss.SSS"); 46 byte rcvData[] = new byte[16]; 47 int size = 0; 48 try { 49 while (true) { 50 // データ読込み 51 size = ins.read(rcvData); 52 System.out.println("size:" + size + "byte" + " 受信時刻 = " + format.format(new Date())); 53 } 54 } catch (IOException e) { 55 e.printStackTrace(); 56 } 57 } 58}

クライアント側

java

1public class Client { 2 private static Socket soc = null; 3 private static OutputStream os = null; 4 private static InputStream is = null; 5 6 public static void main(String[] args) { 7 try { 8 // ソケット生成 9 soc = new Socket("127.0.0.1", 5000); 10 soc.setTcpNoDelay(false); //明示的にNagleアルゴリズムオン 11 os = soc.getOutputStream(); 12 Thread sndTh = new ClientSndThread(os); 13 sndTh.start(); 14 // 10秒スリープ 15 try { 16 Thread.sleep(10000); 17 } catch (Exception e) { 18 e.printStackTrace(); 19 } 20 // スレッド停止 21 sndTh.stop(); 22 } catch (IOException e) { 23 e.printStackTrace(); 24 } finally { 25 try { 26 is.close(); 27 os.close(); 28 soc.close(); 29 } catch (IOException e) { 30 e.printStackTrace(); 31 } 32 } 33 34 } 35} 36 37class ClientSndThread extends Thread { 38 private static OutputStream ous = null; 39 40 ClientSndThread(OutputStream os) { 41 this.ous = os; 42 } 43 44 public void run() { 45 DateFormat format = new SimpleDateFormat("yyyy MM dd hh:mm:ss.SSS"); 46 byte sndData[] = new byte[4]; 47 sndData[0] = 0x04; 48 sndData[1] = 0x03; 49 sndData[2] = 0x02; 50 sndData[3] = 0x01; 51 try { 52 while (true) { 53 // データ書込み 54 ous.write(sndData); 55 ous.flush(); 56 System.out.println("データ送信" + " 送信時刻 = " + format.format(new Date())); 57 // 1秒スリープ 58 try { 59 Thread.sleep(1000); 60 } catch (Exception e) { 61 e.printStackTrace(); 62 } 63 } 64 } catch (IOException e) { 65 e.printStackTrace(); 66 } 67 } 68} 69

コード改修(11.22)

サーバ側

java

1import java.io.IOException; 2import java.io.InputStream; 3import java.io.OutputStream; 4import java.net.ServerSocket; 5import java.net.Socket; 6import java.text.DateFormat; 7import java.text.SimpleDateFormat; 8import java.util.Date; 9 10public class Server 11{ 12 public static ServerSocket ss = null; 13 14 public static Socket soc = null; 15 16 private static InputStream is = null; 17 18 private static OutputStream os = null; 19 20 public static void main(String[] args) 21 { 22 23 try 24 { 25 // サーバソケット生成 26 ss = new ServerSocket(5000); 27 soc = ss.accept(); 28 soc.setTcpNoDelay(true); 29 is = soc.getInputStream(); 30 os = soc.getOutputStream(); 31 32 DateFormat format = new SimpleDateFormat("yyyy MM dd hh:mm:ss.SSS"); 33 byte sndData[] = new byte[4]; 34 sndData[0] = 0x01; 35 sndData[1] = 0x02; 36 sndData[2] = 0x03; 37 sndData[3] = 0x04; 38 39 while (true) 40 { 41 byte rcvData[] = new byte[16]; 42 int size = 0; 43 // データ読込み (受信するまで待つ) 44 for (int tmp = 0; tmp <= 1; tmp++) 45 { 46 size = is.read(rcvData); 47 System.out.println("データ受信。サイズ : " + size + "byte. 送信時刻 = " + format.format(new Date())); 48 if (size == 8) 49 { 50 break; 51 } 52 } 53 // clientからの送信が200ms遅延してる. ACK遅延により返答も200ms遅延 54 // 上記要因を踏まえて3回目以降のトリガー 55 os.write(sndData); 56 os.flush(); 57 System.out.println("データ送信" + " 送信時刻 = " + format.format(new Date())); 58 } 59 } 60 catch (IOException e) 61 { 62 e.printStackTrace(); 63 } 64 finally 65 { 66 try 67 { 68 is.close(); 69 os.close(); 70 soc.close(); 71 ss.close(); 72 } 73 catch (IOException e) 74 { 75 e.printStackTrace(); 76 } 77 } 78 } 79}

クライアント側

java

1import java.io.IOException; 2import java.io.InputStream; 3import java.io.OutputStream; 4import java.net.Socket; 5import java.net.UnknownHostException; 6import java.text.DateFormat; 7import java.text.SimpleDateFormat; 8import java.util.Date; 9 10public class Client1 { 11 12 public static void main(String[] args) { 13 14 Thread client = new ClientThread(); 15 client.start(); 16 try { 17 //Thread.sleep(1000); 18 client.join(); 19 } catch (Exception e) { 20 e.printStackTrace(); 21 } 22 } 23} 24 25class ClientThread extends Thread{ 26 private static Socket soc = null; 27 private static OutputStream os = null; 28 private static InputStream is = null; 29 30 public void run() { 31 try { 32 // ソケット生成 33 soc = new Socket("172.23.56.83", 5000); 34 soc.setTcpNoDelay(false); 35 is = soc.getInputStream(); 36 os = soc.getOutputStream(); 37 38 DateFormat format = new SimpleDateFormat("yyyy MM dd hh:mm:ss.SSS"); 39 byte sndData[] = new byte[4]; 40 sndData[0] = 0x04; 41 sndData[1] = 0x03; 42 sndData[2] = 0x02; 43 sndData[3] = 0x01; 44 45 for (int cnt = 0; cnt <10; cnt++) { 46 // 1回目送信(即送信される) 47 os.write(sndData); 48 os.flush(); 49 System.out.println("データ送信" + " 送信時刻 = " + format.format(new Date())); 50 // 0.01秒スリープ 51 try { 52 Thread.sleep(10); 53 } catch (Exception e) { 54 e.printStackTrace(); 55 } 56 // 2回目送信(1回目のACKが遅延ACKにより200ms遅延) 57 // 加えてNagleにより送信も200ms遅延 58 os.write(sndData); 59 os.flush(); 60 System.out.println("データ送信" + " 送信時刻 = " + format.format(new Date())); 61 62 byte rcvData[] = new byte[16]; 63 int size = 0; 64 // データ読込み 65 size = is.read(rcvData); 66 System.out.println("データ受信。サイズ : " + size + "byte. 送信時刻 = " + format.format(new Date())); 67 } 68 } catch (UnknownHostException e1) { 69 e1.printStackTrace(); 70 } catch (IOException e1) { 71 e1.printStackTrace(); 72 } finally { 73 try { 74 is.close(); 75 os.close(); 76 soc.close(); 77 } catch (IOException e) { 78 e.printStackTrace(); 79 } 80 } 81 } 82}

結果

log

1Nagleオンの場合 2データ受信。サイズ : 4byte. 送信時刻 = 2019 11 22 01:05:20.537 3データ受信。サイズ : 4byte. 送信時刻 = 2019 11 22 01:05:20.584 4データ送信 送信時刻 = 2019 11 22 01:05:20.584 5データ受信。サイズ : 4byte. 送信時刻 = 2019 11 22 01:05:20.584 6データ受信。サイズ : 4byte. 送信時刻 = 2019 11 22 01:05:20.631 7データ送信 送信時刻 = 2019 11 22 01:05:20.631 8データ受信。サイズ : 4byte. 送信時刻 = 2019 11 22 01:05:20.631 9データ受信。サイズ : 4byte. 送信時刻 = 2019 11 22 01:05:20.678 10データ送信 送信時刻 = 2019 11 22 01:05:20.678 11データ受信。サイズ : 4byte. 送信時刻 = 2019 11 22 01:05:20.678 12データ受信。サイズ : 4byte. 送信時刻 = 2019 11 22 01:05:20.724 13...

log

1Nagleオフの場合 2データ受信。サイズ : 8byte. 送信時刻 = 2019 11 22 01:22:21.897 3データ送信 送信時刻 = 2019 11 22 01:22:21.897 4データ受信。サイズ : 4byte. 送信時刻 = 2019 11 22 01:22:21.900 5データ受信。サイズ : 4byte. 送信時刻 = 2019 11 22 01:22:21.901 6データ送信 送信時刻 = 2019 11 22 01:22:21.901 7データ受信。サイズ : 4byte. 送信時刻 = 2019 11 22 01:22:21.901 8データ受信。サイズ : 4byte. 送信時刻 = 2019 11 22 01:22:21.917 9データ送信 送信時刻 = 2019 11 22 01:22:21.917 10...

環境

OS : win10
JDK : 1.8
IDE : eclipse

サーバとクラインアントは同一ホストで行なってました。

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

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

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

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

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

dameo

2019/11/20 09:07

試してないので適当なこと言いますが、同一ホストでもわざわざ遅延させたりするのです? それとも実際に試すときにはIP変えているのでしょうか?
Rai_Feit

2019/11/20 09:21 編集

Nagleがどのように機能して、どのように遅延を発生させるのか知りたいです。 テストコードを実験してる時は、同一ホストで行なっておりました。 同一ホストで通信させる場合、遅延は発生しないのでしょうか
dameo

2019/11/20 09:25

環境も書いてないし、細かいところは知りません。内容そのものは理解して実施しているのでしょうか?
Rai_Feit

2019/11/20 10:01

環境を追記しました。足りなければご教授願います。 調べた上で、MSS以下且つjavaのデフォルトバッファサイズ以下なら遅延が発生すると考えていました。
dameo

2019/11/20 10:10

元々の目的が1バイトを送信するのに必要な通信オーバーヘッドを減らす目的なので、実際には回線上の通信が発生しない同一ホスト間の通信では、これらの遅延を発生させる理由がないと思いますよ。実際に起こるはずの遅延が見られないのであれば、同一ホストではないと考えていいのではないでしょうか? ただし、ロジックが正しければの話ですが…
Rai_Feit

2019/11/20 10:22

なるほどですね。 同一ホストでは、遅延させる意味もないのでOS側?が自動で最適化してくれている可能性に気付きませんでした。ご丁寧にありがとうございます。 回線上での通信を発生させる検証をした上で、報告いたします。
dameo

2019/11/20 12:58

本当は実際にやってみるまで黙っていたかったのですが、こちらの時間の都合もあるので、先に懸念点だけ伝えておきます。 (1)クライアント側で送信間隔を1000msで待っているが、元のコードでは10msで待っている。NagleアルゴリズムではACK未受信の送信パケットがあった場合バッファされるが、1000msもACK未受信のケースなんてあるのだろうか? なお、普通WindowsのTCP遅延ACKのタイムアウトは200msで、LinuxのTCP遅延ACKのタイムアウトは40ms。 (2)最大フレームはどうせ十分長いとはいえ、連続して送ればそれなりに貯まるケースもあり、事前に計算したりしているのかも不安。 ちなみにクライアントは全部送信にしなくても、2回送信して1回受信とかのシーケンスにすれば最大フレームを確実に越えないように実装出来る。 (3)現状送信時刻と受信時刻を比較する想定になっているが、異なるマシンで時間がそこまで正確には一致しないので、同じマシンで送信時刻の間隔か、受信時刻の間隔で遅延を確認すべき。 ご報告お待ちしています。
Rai_Feit

2019/11/21 03:03

懸念点感謝いたします。 知識不足なりに上記懸念点を私の可能な限り考慮したうえで再実験してみました。 コードの変更点は下記で付け焼き刃なので, これから調整するつもりですが, 取り急ぎ報告させていただきます。 (1), (2)クライアント、サーバそれぞれ送受信対応 (1), (2)クライアント側の送信間隔1000ms →10ms, サーバ側送信間隔200ms ACKを200ms間隔で返すことにより送信バッファのクリアかつNagleアルゴリズム発生条件のうちの”ACK未受信なし”を回避するためある程度ACK未受信を溜める (3)受信側の受信間隔と受信サイズで確認 を加えたうえで検証しました。 On size:4byte 受信時刻 = 2019 11 21 11:44:30.674 size:12byte 受信時刻 = 2019 11 21 11:44:30.714 size:16byte 受信時刻 = 2019 11 21 11:44:30.774 size:8byte 受信時刻 = 2019 11 21 11:44:30.821 size:12byte 受信時刻 = 2019 11 21 11:44:30.874 OFF OFF size:8byte 受信時刻 = 2019 11 21 11:42:13.963 size:4byte 受信時刻 = 2019 11 21 11:42:14.000 size:4byte 受信時刻 = 2019 11 21 11:42:14.016 size:4byte 受信時刻 = 2019 11 21 11:42:14.032 size:4byte 受信時刻 = 2019 11 21 11:42:14.047 Nagleバッファに入ってまとめて送られてきているのと少しの遅延を確認できています。 今後追加でコードの編集と確認を行いたいと思います。
dameo

2019/11/21 03:49

「サーバ側送信間隔200ms」がなんなんだろうと気になってしまいますが、とりあえずコードをお待ちしますね。 こちらは急いでるわけではなくて、単にまとまった時間が取れないときがあるので、問答が多くなると回答に時間がかかってしまうことになり、質問者さんの作業進捗に影響すると思い、事前に伝えただけのことです。つまり「急がなくていいので、質問者さんのペースで進めてください」ということです。
Rai_Feit

2019/11/21 10:16

ご配慮感謝いたします。 「「サーバ側送信間隔200ms」がなんなんだろうと気になってしまいます」とご指摘いただいた通り, 実際にコードを編集する中で理解が浅い部分があります。 そこで, この質問タイトルとは関係ないと思い質問をためらっていたのですがご教授ください。 懸念点(2)で「ちなみにクライアントは全部送信にしなくても、2回送信して1回受信とかのシーケンスにすれば最大フレームを確実に越えないように実装出来る。」と仰っていたのは, どういうことでしょうか?受信すればそこにACKが乗ってくるから、送信バッファがクリアされるということでしょうか?
dameo

2019/11/21 11:10

wikipediaからの引用です。 https://ja.wikipedia.org/wiki/Nagle%E3%82%A2%E3%83%AB%E3%82%B4%E3%83%AA%E3%82%BA%E3%83%A0 Nagleアルゴリズムは以下の条件がどれか1つでも成立するまで送信を遅延させる。 ・未送信データが最大セグメントサイズ以上になる ・過去の送信パケットで ACK が未受信の物がなくなる(TCP遅延ACKに注意) ・タイムアウトになる です。今回のプログラムでnagleでバッファリングされるのはクライアント側です。 一回目の送信はこれらの条件を満たさないので必ず送信されます。 二回目の送信からバッファリングされますよね。 なので、一回目の送信パケットに対するACKを受信してしまうか、タイムアウトする(多分200ms)か、バッファリング分の送信量が最大セグメントサイズを越える場合は、出てしまうわけです。遅延を見る際には最大限遅延してほしいので、クライアント側では2回目以降の送信で、タイムアウトする前に最大セグメントサイズを越えたくないわけです。もし、10msごとに送信する場合は、200msでタイムアウトする間に最大19回ほど送信できるわけで、およそ1452バイトだと仮定すれば十分なのですが、一回の通信バイト数と通信環境に依存する部分なので、確実にと考えると3回目以降を送らない方が確実です。では3回目をどうやって待つか?というと、サーバ側からのデータ受信待ちが一番合理的です。 サーバ側でクライアント側からの2回目のデータを受信してから、一度データを送信してあげることにすれば、nagleの条件がタイムアウトで満たされて2回目のデータをクライアント側が送信してくれた後にデータを返すことが出来ます。 すると、 クライアントが2回送信 サーバが1回送信 クライアントが2回送信 サーバが1回送信 ... というシーケンスを繰り返すことが一番合理的なクライアント側のnagleによる遅延確認方法だと思ったということです。 「最大フレーム」はうっかり使ってしまった用語ミスで、「最大セグメントサイズ」が正しいです。最大フレームは逆に長いデータを一気に送りたいときに考慮するものなので。
dameo

2019/11/21 11:22 編集

なお、2番目の条件である、 ・過去の送信パケットで ACK が未受信の物がなくなる(TCP遅延ACKに注意) は、サーバー側のACK送信がどれだけ待たされるか?に依存しています。 この遅延時間がクライアント側のnagleのタイムアウトより短いと、200ms待てないのですが、サーバ側がWindowsであれば、多分200msなのではないかと踏んでいます。数字の部分はネットで調べた値なので、未確認です。
Rai_Feit

2019/11/22 04:22

質問にご丁寧に回答をくださりありがとうございます。 昨日の回答を受けたうえで実験してみたコードと結果を本文に載せました。 Nagleは40~50ms程機能しているように思えますが, 200msとはなりません。 僭越ながら間違いがあればご指摘お願いいたします。 個人的には, サーバー側のACK遅延が40ms程なのではないかと疑っております。
dameo

2019/11/22 04:37

そうですね。そんな感じに見えます。 他の質問でパケットキャプチャも提案されてましたよね? この際鯖側のACKがいつ来てるのか確認されてみてはどうでしょう?
Rai_Feit

2019/11/22 05:22

ありがとうございます。 まだ使い慣れないですが、おそらくこれが証拠になると思います↓ The RTT to ACK the segment was: 0.042699000 seconds
dameo

2019/11/22 05:29

前後もう少し長めに欲しかったけど、キャプチャ開始から43msくらいであれば間違いないですね。 ということは、WindowsでもTCP遅延ACKのタイムアウトは40msなんですね。 あとはお時間のあるときにご自分で回答を書いて頂き、自己解決しちゃいましょう。 おめでとうございます。
Rai_Feit

2019/11/22 05:42

本当に丁寧にお付き合いいただきありがとうございました。 感謝してもしきれません。問答も多くなってしまい申し訳なかったです。 バッジなどあまり興味はないのですが, これ以上お手を煩わせないように後で自分で書いてcloseしておきますね。 ありがとうございました。
dodox86

2019/11/22 08:14

> Nagleは40~50ms程機能しているように思えますが, 200msとはなりません。 200m秒と決まっているわけではなくて、最大200m秒(規格上は500m秒)だからではないでしょうか。そういう話ではなくて?でしょうか。
dodox86

2019/11/22 08:22 編集

回答を一部、書いていたのですが、既に検証をされていて一定の結果を得られていたようでしたので、回答をやめました。大意としては「JavaだとInputStreamやOutputStreamでバッファリングされてしあい、細かくコントロールしづらいので、C言語でsend, readなどのシステムコールを直接使い、setsockoptでTCP_NODELAYを操作して試した方が良いのでは?」と言う程度のものでした。結論は得られたようですので、忘れてください。
dodox86

2019/11/22 08:22

あと、ほぼ終わったところに今更恐縮ですが、タグの「Socket.IO」はNode.jsのものです。
Rai_Feit

2019/11/22 10:24

dodoxさん別の質問でもお世話になりました。次いで考えてくださっていてありがとうございます。当方エンジニア歴一ヶ月のど素人ですかので、正しい情報ならいかなる情報もありがたいのです。Socket.IOはタグから消しました、ご指摘感謝します。 > 200m秒と決まっているわけではなくて、最大200m秒(規格上は500m秒)だからではないでしょうか。そういう話ではなくて?でしょうか。 ご教授頂いたWireSharkで、The RTT to ACK the segment was: 0.042699000 secondsとなっていたのでwin10のACK遅延が40msだということで腹落ちしましたが、初めて利用したので見方が間違っていた可能性もあるかなと思います。 Nagleのタイムアウトが一般的に200msで、環境によっては40msだったりする場合もあるのでしょうか?
dodox86

2019/11/22 11:14

遅延が200msとまではならず、40msと短くて済んだ、ということなのではないかな、と思いました。
dodox86

2019/11/22 11:22 編集

エンジニア歴たった1ヶ月でこういったことに興味と疑問を抱き、かつ、調べられて一定の結果を出されることに感嘆しました。(すごい)
Rai_Feit

2019/11/22 11:47

なるほどそういう見方もあるのですね。確かにソース元が日本語など言語(今回はwikiなど)である以上本来の意味は少なからず矮小化されるため人にとっては解釈の仕方が違ってくるのは当然のことですよね。 個人的には、ack遅延が40msをwiresharkで確認したためそちらが有力ですが、dodoxさんの説も否定できる材料はないのでclient側の送信をキャプチャするなどして追加検証の余地があります。(あいにく直ぐには検証できる環境に無いので、回答自体は前述の通り自己解決して一旦締めさせていただきますね) >(すごい) dodoxさんの足元にも及びません。 今後も何処かで見かけましたら助けてください!
guest

回答1

0

自己解決

端的に。同一ホスト間など通信回線を通さないsocket通信では、アプリケーションより下位層の働きでNagleアルゴリズムは機能しないので通信回線を通す。

投稿2019/11/22 14:59

編集2019/11/22 15:00
Rai_Feit

総合スコア7

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

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

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問