このページの本文へ

Goならわかるシステムプログラミング 第8回

UDPソケットをGoで叩く

2016年12月28日 22時00分更新

文● 渋川よしき、編集● 鹿野桂一郎

  • この記事をはてなブックマークに追加
  • 本文印刷

前回と前々回の記事では、Go言語によるTCPソケットの通信例を紹介してきました。 今回は、ネットワークの解説でTCPと一緒に紹介されることが多いUDPのソケットをGo言語で触ってみます。

今回の記事の概要部分は佐藤貴彦氏、若山史郎氏、小泉守義氏にアドバイスをいろいろもらいました。 ありがとうございます。

UDPが使われる場面は昔と今で変わってきている

UDPはTCPと同じトランスポート層プロトコルですが、TCPと違ってコネクションレスであり、誰とつながっているかは管理しません。 プロトコルとしてデータロスの検知をすることも、通信速度の制限をすることもなく、一方的にデータを送りつけるのに使われます。 その際にはパケットの到着順序も管理しません。

TCPとくらべて機能が少なくシンプルですが、そのかわりに複数のコンピュータに同時にメッセージを送ることが可能なマルチキャストとブロードキャストをサポートしています。 これはTCPにはない機能です。

TCPには通信制御のための機能が備わっている

UDPでは一方的にデータを送り付ける(各アプリケーションへのデータの振り分けといったトランスポート層としての役割は果たす)

具体的なアプリケーションとしては、ドメイン名からIPアドレスを取得するDNSの通信、時計合わせのためのプロトコルのNTPがUDPを利用しています。 さまざまなストリーミング動画・音声プロトコルも、トランスポート層でUDPを利用するものが多い分野です。 たとえば、最近話題になることが多いブラウザ上で行うP2Pのための動画・音声通信プロトコルWebRTCでは、主にUDPを使います。

かつては、VPNなどの仮想ネットワークの土台としてもUDPが使われていました。 仮想ネットワークでは、そこで張られたTCPコネクションがエラー訂正や順番の制御を行うため、その土台としてTCPを使うとTCP over TCPになってしまい無駄が多いから、というのがその理由でした。

「UDPは高速」と言われているので、独自プロトコルを開発するときにUDPが土台として選ばれることもあります。 伝送ロスがあまりないことが期待できる構内LAN専用の高速プロトコルなどが作られることも多かったそうです。

しかし現在では以上のような使い分けが常に正しいとは言い切れません。

現在は、セキュリティ上の理由から、VPN接続でも暗号化のためにTLSを経由するSSL-VPNが使われることが増えています。 SSL-VPNにも3通りの方式がありますが、その中にはパケットをHTTPS上にくるんで送信するものがあります。 この場合には、上で使うプロトコルがTCPの場合、どうしてもTCP over TCPになります。

独自プロトコルを開発する場合も、土台としてUDPを使うということは、通信環境が劣悪な状態での信頼性とか、ネットワークに負荷をかけすぎて他の通信の邪魔をしないか(フェアネス)とか、そういった点について自分たちで作りこみが必要になるということです。 そのような「安定したプロトコル」を作るには多くの労力がかかります。

Googleでは独自にQUICというトランスポート層のプロトコルを開発していますが、彼らのようにネットワーク知り尽くした人たちがきちんと設計して大規模なフィールドテストができる状態でないならば、そもそも独自プロトコルを使わないほうが得策といえます。 TCPプロトコルの制御もバージョンがあがり、輻輳制御は高性能になっています1

UDPが高速と言われている理由は、コネクション接続時に時間がかからないからです。 TCPでは1.5RTTの時間がかかりますが、UDPでは接続の時間は不要なので、短時間で完了するメッセージを大量に送受信する場合はメリットが大きいでしょう。 一方で、一度の通信で1パケットに収まらないような大きめのデータをやり取りする場合、自前でエラー処理も含めて実装するのであればそこまで差はないでしょう。 UDPが高速かどうかは通信するアプリケーションの特性に左右されます。

現在では、一部を除いて、アプリケーションレイヤーで使われるプロトコルの多くがTCPを土台にしています。 主にUDPを使っているDNSも、512バイトを超えるレスポンスの場合にはTCPにフォールバックする仕組みがあったりします。WebRTCも、UDPだけではなくTCPが使えるようになっています。 TCPがやってくれるような再送処理を実装するには、通信経路が混雑してきたときの対処法、パケット到着順の制御など、さまざまな機能が必要です。 それらをアプリケーションごとに実装するのは大変ですし、TCPなら使える環境にも差がないため、特別な理由がない限りはTCPが選択されることが多くなっています。

アプリケーション開発という視点で見れば、「ロスしても良い、マルチキャストが必要、ハンドシェイクの手間すら惜しいなど、いくつかの特別な条件に合致する場合以外はTCP」という選択でよいでしょう。

UDPとTCPのそれぞれの特徴をまとめておきます。 いくつかの項目の詳細については本記事の最後でくわしく紹介します。

UDPとTCPの処理の流れの違い

UDPの特徴とTCPの違いを言葉で説明してきましたが、 「UDPはシンプルで早い」だけでは単なる感想と大差がないので、具体的にUDPとTCPとではなにが違うのかをGo言語のコードを通して見ていきましょう。

UDPによる接続の流れを図示すると次のようになります。

サーバ側の実装例

まずはサーバ側から見ていきましょう。

TCPソケットの場合、接続を受け付けるサーバはnet.Listen()関数を呼び、返ってきたnet.Listenerインタフェースでクライアントが接続してくるのを待っていました。 クライアントが接続してきたら、net.ListenerインタフェースのAccept()メソッドを呼び、お互いにデータを送受信するためのnet.Connインタフェースのオブジェクトを得ます。

これに対し、UDPの接続でサーバが使う関数はnet.ListenPacket()です。 net.ListenPacket()を呼ぶと、net.Listen()のような「クライアントを待つ」インタフェースではなく、データ送受信のためのnet.PacketConnというインタフェースが即座に返されます。 このnet.PacketConnオブジェクトもio.Readerインタフェースを実装しているため、圧縮やファイル入出力などの高度なAPIと簡単に接続できます。

実際にサーバ側のコードを見てみましょう。接続後の処理も一緒に示してあります。 TCPの通信例と比べるとステップ数が減っていて、そのぶんだけシンプルになっていることがわかります。

package main
import (
    "fmt"
    "net"
)
func main() {
  fmt.Println("Server is running at localhost:8888")
  conn, err := net.ListenPacket("udp", "localhost:8888")
  if err != nil {
    panic(err)
  }
  defer conn.Close()
  buffer := make([]byte, 1500)
  for {
    length, remoteAddress, err := conn.ReadFrom(buffer)
    if err != nil {
      panic(err)
    }
    fmt.Printf("Received from %v: %v\n",
               remoteAddress, string(buffer[:length]))
    _, err = conn.WriteTo([]byte("Hello from Server"),
                          remoteAddress)
    if err != nil {
      panic(err)
    }
  }
}

接続後の処理で注目すべきポイントはconn.ReadFrom()です。 ReadFrom()メソッドを使うと、通信内容を読み込むと同時に、接続してきた相手のアドレス情報が受け取れます。

net.PacketConnは、サーバ側でクライアントを知らない状態で開かれるソケットなので、 このインタフェースを使ってサーバから先にメッセージを送ることはできません。 サーバには、クライアントからリクエストがあったときに、初めてクライアントのアドレスがわかります。 通信内容だけを取得し、通信の送信元を識別しないRead()メソッドを使ってしまうと、通信相手に通信を送り返す必要があるときに対処できなくなってしまいます。

ReadFrom()では、TCPのときに紹介した「データの終了を探りながら受信」といった高度な読み込みはできません。そのため、データサイズが決まらないデータに対しては、フレームサイズ分のバッファや、期待されるデータの最大サイズ分のバッファを作り、そこにデータをまとめて読み込むことになります。 あるいは、バイナリ形式のデータにしてヘッダにデータ長などを格納しておき、そこまで先読みしてから必要なバッファを確保して読み込む、といったコードになるでしょう。 上記のサンプルでは前者の実装になっています。

データの境界まで読み込みが完了し、データが入ったバイト配列([]byte)が手元に用意できたら、bytes.Readerbufio.Readerが使えます。

ReadFrom()で取得したアドレスに対しては、net.PacketConnインタフェースのWriteTo()メソッドを使ってデータを返送することができます。

クライアント側の実装例

次はクライアント側です。

package main
import (
    "fmt"
    "net"
)
func main() {
    conn, err := net.Dial("udp4", "localhost:8888")
    if err != nil {
        panic(err)
    }
    defer conn.Close()
    fmt.Println("Sending to server")
    _, err = conn.Write([]byte("Hello from Client"))
    if err != nil {
        panic(err)
    }
    fmt.Println("Receiving from server")
    buffer := make([]byte, 1500)
    length, err := conn.Read(buffer)
    if err != nil {
        panic(err)
    }
    fmt.Printf("Received: %s\n", string(buffer[:length]))
}

こちらも前回紹介したTCPのコードと比べるとシンプルになっています。 クライアントでは相手がわかった上でDial()するので、TCPの場合と同じようにio.Readerio.Writerインタフェースのまま使うこともできます。 しかし、サーバ側でフレームサイズやバイナリ形式に従った読み込みをする関係で、実際にはサーバ側と同じようなコードにすることが多いと思いでしょう。

ここに掲載したクライアント側のコードでは、net.Dial()"udp4"というプロトコル名を渡しています。 本来はサーバ側のコードと同じように"udp"で大丈夫なはずですが、 筆者の環境(macOS)では"udp4"を指定しないとエラーになりました。 もう少し調べてエラー報告等をしようと思います。

コラム: 抽象インタフェースと具象実装

UDPの通信サンプルをネットで調べると、ほとんどのコードではnet.Listen()net.Dial()ではなく、net.ListenUDP()net.DialUDP()という関数を使っています。 結論からいうと、上記の例ではどちらを使ってもほぼ変わりはありません。

しかし、これから説明するようなUDPのマルチキャストや、TCPのKeepAliveなど、それぞれのプロトコル固有の操作が必要な場合には、それぞれの型の関数(net.TCPConnnet.UDPConnなど)を明示的に使う必要があります。

udp, ok := conn.(*net.UDPConn)
if ok {
    // UDP固有の処理
}

上記の例で使っているnet.Listen()net.ListenPacket()net.Dial()は、プロトコルの種類を文字列で指定するだけで具体的なインタフェースを隠して通信を抽象的に書くためのインタフェースです。

明示的な実装が必要な場合は、最初からnet.ListenUDP()net.ListenTCP()などの関数を使って通信してもいいし、net.Connnet.PacketConnインタフェースから具象型にキャストする方法もあります。

UDPのマルチキャストの実装例

マルチキャストは、リクエスト側の負担を増やすことなく多くのクライアントに同時にデータを送信できる仕組みです。 マルチキャストはUDPならではなので、次はGo言語でマルチキャストサーバとクライアントを作ってみましょう。

その前に、まずはマルチキャストについて簡単に説明します。 マルチキャストでは使える宛先IPアドレスがあらかじめ決められていて、ある送信元から同じマルチキャストアドレスに属するコンピュータに対してデータを配信できます。 送信元とマルチキャストアドレスの組み合わせをグループといい、同じグループであれば、受信するコンピュータが100台でも送信側の負担は1台ぶんです。 IPv4については、先頭4ビットが1110のアドレス(224.0.0.0 ~ 239.255.255.255)がマルチキャスト用として予約されています2。 IPv6については、先頭8ビットが11111111のアドレスがマルチキャスト用アドレスです。 IPv4では224.0.0.0 ~ 224.0.0.255の範囲がローカル用として予約されているので、このアドレスはテストなどで使えます。

サーバ側の実装例

それではGoでマルチキャストを利用するコードを見てみましょう。 例題として117の時報のようなサービスを実装しました。 時間合わせ用のプロトコルとしては、NTPという歴史あるしっかりしたものがあり、やはりUDPが利用されています。 ここで紹介するのは、あくまでも例題ということで、電話による時報を聞く場合と同様に遅延による誤差などは気にしない簡易なものです。

package main
import (
  "fmt"
  "net"
  "time"
)
func main() {
  fmt.Println("Start tick server at 224.0.0.1:9999")
  conn, err := net.Dial("udp", "224.0.0.1:9999")
  if err != nil {
    panic(err)
  }
  defer conn.Close()
  start := time.Now()
  wait := 10*time.Second - 
          time.Nanosecond*time.Duration(
            start.UnixNano()%(10*1000*1000*1000))
  time.Sleep(wait)
  ticker := time.Tick(10 * time.Second)
  for now := range ticker {
    conn.Write([]byte(now.String()))
    fmt.Println("Tick: ", now.String())
  }
}

UDPのマルチキャストでは、サービスを受ける側(クライアント)がソケットをオープンして待ち受け、そこにサービス提供者(サーバ)がデータを送信します。 よく考えると、このフローはTCPを利用する場合とは逆の関係です。 TCPを利用する通常のネットワークのサービスでは、まずサーバが起動してからクライアントを待ち受けていて、リクエストがきたらレスポンスを返します。 そのため、上記はマルチキャストにおけるサーバ側のコードなのですが、構成としては前回のTCPの例におけるクライアントコードと同じです。

時報サービスの実装なので、このサーバ側のコードには時間を扱う処理が出てきます。 time.Sleep()に渡す値を作る部分が少し複雑に見えますが、10秒単位の端数を取り出してきているだけです。 time.Durationは、実体はintですが、Go言語では暗黙に型変換されないので明示的にキャストが必要になります。 あとは、決まった時間間隔で定期的にfor文を回すためにtime.Tick()を使っています。

クライアント側の実装例

次はクライアント側のコードです。 こちらはソケットを開いて、サーバが10秒に一回送信するパケットを待って表示します。

package main
import (
    "fmt"
    "net"
)
func main() {
    fmt.Println("Listen tick server at 224.0.0.1:9999")
    address, err := net.ResolveUDPAddr("udp", "224.0.0.1:9999")
    if err != nil {
        panic(err)
    }
    listener, err := net.ListenMulticastUDP("udp", nil, address)
    defer listener.Close()
    buffer := make([]byte, 1500)
    for {
        length, remoteAddress, err := listener.ReadFromUDP(buffer)
        if err != nil {
            panic(err)
        }
        fmt.Printf("Server %v\n", remoteAddress)
        fmt.Printf("Now    %s\n", string(buffer[:length]))
    }
}

クライアントコードは、構成としてはTCPの例におけるサーバと同じです。 しかし使っている関数はTCPのときに使ったnet.Listen()ではなく UDPによるマルチキャスト専用のnet.ListenMulticastUDP()という関数です。 この関数を使う場合は、アドレスをあらかじめnet.ResolveUDPAddr()関数でパースする必要があります。

ReadFromUDP()メソッドは、レスポンスで返ってくるサーバのアドレスの型がUDP専用のnet.UDPAddr型であること以外、ふつうのUDPクライアントで使ったReadFrom()とほぼ同じです。

実行例

サーバ側の実行ログは次の通りです。

$ go run server_multicast.go ⏎
[master]
Start tick server at 224.0.0.1:9999
Tick:  2016-12-19 00:59:20.007763991 +0900 JST
Tick:  2016-12-19 00:59:30.00522309 +0900 JST
Tick:  2016-12-19 00:59:40.004806934 +0900 JST
Tick:  2016-12-19 00:59:50.005220006 +0900 JST

クライアント側の実行ログは次の通りです。

$ go run client_multicast.go ⏎
[master]
Listen tick server at 224.0.0.1:9999
Server 192.168.1.5:62402
Now    2016-12-19 00:59:40.004806934 +0900 JST
Server 192.168.1.5:62402
Now    2016-12-19 00:59:50.005220006 +0900 JST

今回は1対多通信を想定した例になっていますが、送信はIPアドレスとポート番号を知っていれば誰でもできるので、多対多通信に拡張するのは難しくありません。 クライアント側で複数のネットワーク接続があるときに特定のLAN環境のマルチキャストを受信するには、 net.InterfaceByName("en0")のように書いてイーサネットのインタフェース情報を取得し、それをnet.ListenMulticastUDP()関数の第二引数に渡します。

TCPとUDPの機能面の違い

最後に、TCPとUDPにはサーバとクライアントの実装例だけでは紹介できない違いもたくさんあります。 たとえば、TCPの場合は接続前のハンドシェイク(握手・通信開始の準備)に1.5RTT分の時間がかかりますが、UDPはメッセージを直接送りつけるだけなので0RTTです。

ここでは、アプリケーションでTCPとUDPを使い分けるときの参考に、両者の機能面での違いを紹介します。

公開当初の記事では、UDPアプリケーションにおけるデータ設計に関する解説で、IPフラグメンテーションとトランスポート層の機能が混同している説明になっていました。訂正してお詫びいたします。また、「UDPはTCPと比べて何をしていないのか」を説明するためにTCPの機能を紹介する部分で雑な解説になっていたので、合わせて補足しています。(2016/12/30)

TCPには再送処理とフロー処理がある

TCPでは送信するメッセージにシーケンス番号が入っているので、受信側ではこの数値を見て、もしパケットの順序が入れ替わっていたときは順序を並べ直します。 受信側はメッセージを受け取ると、受信したデータのシーケンス番号とサイズの合計を確認応答番号として返信します。 送信側はこの応答確認番号が受け取れず、届いたことが確認できない場合は、落ちたと思って再び送り直します。 これが再送処理です。

また、TCPにはウインドウ制御という機能があり、受信側が用意できていない状態で送信リクエストが集中して通信内容が失われたりするのを防ぎます。 具体的には、送受信用のバッファ(TCPではウインドウと呼ばれます)をあらかじめ決めておき、送信側ではそのサイズ(ウインドウサイズ)までは受信側からの受信確認を待たずにデータを送信できます。 このウインドウサイズは最初のコネクション確立時に決まりますが、 受信側のデータの読み込み処理が間に合わない場合には、 受信できるウインドウサイズを受信側から送信側に伝えて通信量を制御することができます。 これをフロー制御といいます。

UDPには、TCPにおけるウインドウやシーケンス番号を利用した再送処理やフロー処理の機能がありません。 クライアントからサーバへと一方的にデータを送りつけます。 受信確認もなく、順番のソートや再送処理もない代わりに高速になっています。 もちろん、自分でこれらを実装することもできます。

UDPではフレームサイズも気にしよう

TCPもUDPも、その下のデータリンク層の影響を受けます。 ひとかたまりで送信できるデータの大きさは、通信経路の種類やルーターなどの設定によって変わり、 ある経路でひとかたまりで送信できるデータの上限のことをその経路の最大転送単位(MTU)といいます。

一般的に使われるイーサネットのMTUは1500オクテット34ですが、現在の市販のルータで「ジャンボフレーム対応」と書かれているものだとそれよりも大きくなります5。 ただし、UDPやTCPのヘッダーや、PPP、VPNでカプセル化されたりするとヘッダーが増えるため、実データ領域は小さくなります。

MTUに収まらないデータは、IPレベル(TCP/UDPの下のレイヤー)で複数のパケットに分割されます。 これをIPフラグメンテーションと呼びます。 IPフラグメンテーション自体はIPレイヤーで再結合はしてくれますが、分割された最後のパケットが来るまではUDPパケットとして未完成のままなので、アプリケーション側にはデータが流れてくることはありません。 データが消失したら受信待ちのタイムアウトも発生しますし、UDPを使うメリットが薄れてしまいます。 UDPの売りである応答性の高さをカーネル内部の結合待ちでムダにしないためには、イーサネットのフレームサイズを意識したアプリケーションプロトコルの設計が必須でしょう。

具体的には、UDPを利用する場合にはデータ構造を1フレームで収まるサイズにし、毎フレームにフレームの内容を識別するヘッダーを付ける必要があるでしょう。 データが欠落しても支障がないストリーミングデータであっても、順序ぐらいは守りたいでしょうから、何らかのカウンターを用意することになるでしょう。 また、フレームに収まらないデータを格納するための仕組み(どのパーツかを指定するヘッダー、データの種類の識別子など)も必要になります。

巨大なデータをUDPとして送信するデメリットはもう1つあります。 IPレイヤーでデータを結合してくれるといっても、IPレイヤーや、その上のUDPレイヤーで取り扱えるデータは約64キロバイトまでです。 それ以上のデータになると別のUDPパケットとして扱うしかありません。 TCPであれば大きなデータでも受信側アプリケーションでの扱いを気にすることなく送れます6。 UDPではデータの分割などはアプリケーションで面倒を見るしかありません。 逆に言えば、データの最小単位がこの64キロバイト以下であればアプリケーション内でのデータの取り扱いはシンプルになります。

輻輳制御とフェアネス

輻輳制御とは、ネットワークの輻輳(渋滞)を避けるように流量を調整し、そのネットワークの最大効率で通信できるようにするとともに、複数の通信をお互いにフェアに行えるようにする仕組みです。

TCPには輻輳制御が備わっており、そのアルゴリズムにはさまざまな種類があります。 どのアルゴリズムもゆっくり通信量を増やしていき、通信量の限界値をさぐりつつ、パケット消失などの渋滞発生を検知すると流量を絞ったり増やしたりしながら最適な通信量を探ります。 最初の通信の限界を探る段階では2倍、4倍、8倍と指数的に速度を増やしていきます。 このステップは「スロースタート」と呼ばれます。

輻輳するとパケットが失われ、相手にデータが送られなくなります。 送信者には「輻輳したこと」そのものは観測できませんが、相手からの受信確認のようすから輻輳を察知できます。輻輳時には通信量を絞り、その後また増やしていきますが、その詳細はアルゴリズムによって異なります。

輻輳制御は、ネットワークの混雑を避けるアルゴリズムですが、目的としているのは自分の通信だけを最大化することではなく、他の通信回線にもきちんと帯域を譲り、全員が問題なく通信を継続しつつ必要な最大速度が得られることです。 これをフェアネスと呼び、これもTCPにおける大事な価値です。

UDPにはTCPのような輻輳を制御する仕組みはありません。 流量の制御は、UDPを利用する各プログラムに委ねられています。 そのため、UDPとTCPを利用するアプリケーションがそれぞれあって、UDPを利用するアプリケーションでフェアネスが考慮されていない場合には、両方の通信が重なったときに遠慮する機能が組み込まれたTCPの通信速度だけが極端に落ち込むこともあります。

コラム: HTTP/2とQUIC

前回のTCPソケットの解説では、HTTPのサーバとクライアントを実装しました。 そこで見たのはHTTP 1.0と1.1の機能が中心でしたが、 すでにHTTP/2が昨年規格化されています。

HTTP/2にはさまざまな機能が追加されていますが、そのなかにはフロー制御に関する機能もあります。 これは上記で説明したようなTCPの機能を踏まえるとだいぶわかりやすくなるでしょう。 いわばHTTP/2ではTCPの上に疑似的なTCPレイヤーが作られるわけです。

HTTP/1.1までは、TCP接続を6セッションつないで並列にアクセスする方法が使われていました。 それぞれの通信における渋滞はTCPレイヤーで解決し、HTTPのレイヤーでは特に何もしていませんでした。 HTTP/2では1本のTCP接続上でストリームという単位で並列化できるようになっており、同じサーバへの接続ではTCPのフロー制御より高度な優先順位による制御方法が組み込まれています。 また、HTTP/2でもTCPのウインドウ制御のような仕組みが取り入れられています。

HTTP/2とTCPで同じようなことを重複して行っている部分を統合し、UDPを使うことでさらに無駄を減らそうというのがQUICです。 こちらは現在RFC化を目指しています。

  • QUICでは、TCPだと1.5RTTかかっていたハンドシェイクなしに一方的な通信で済むので、通信開始までの時間が減らせる。 さらに、暗号化を行うTLSのレイヤーと協調して動くため、TCPセッション確立とTLSのセッション確立で個別に通信を行っていたところが一元化される。
  • 現代のスマートフォンでは通信中にWifiから3G/4G通信に切り替わったときに再接続になって時間がかかってしまうという問題がよく発生するが、その対処方法も組み込まれている。
  • TCPでは順序制御は全パケットについてまとめて管理しますが、アプリケーション層の事情も知っているQUICの場合は、同一ストリーム内でのみ順序を維持することでオーバーヘッドを減らしている。
  • TCPのレイヤーとHTTP2のレイヤーで個別に行っていたウインドウサイズの制御が一元化される。
  • IPフラグメンテーションが起きないサイズのフレームによる通信を行う仕様になっており、IPのレイヤーのパフォーマンスをアプリケーションが最大限に引き出せるような考慮もなされている。

まとめと次回予告

今回はGo言語を使ってUDPの仕組みを覗いてみました。 実装についてはUDPにしかできないマルチキャストを中心に紹介しました。 とはいえ、積極的にUDPを使わなければならない機会はそれほど多くないと思うので、TCPとUDPの機能的な違いについても簡単に説明しました。

次回は、ソケット編を締めくくるUNIXドメインソケットの紹介です。 UNIXドメインソケットは、名前からして明らかなようにWindowsではそのまま使えない機能ではありますが、Windowsユーザへのサポートもしっかり行いますのでご期待ください。

脚注

  1. TCPの輻輳制御の数々: https://en.wikipedia.org/wiki/TCP_congestion_control
  2. マルチキャストアドレスはIANAという団体が管理しています。IPv4については http://www.iana.org/assignments/multicast-addresses/multicast-addresses.xhtml で、 IPv6については http://www.iana.org/assignments/ipv6-multicast-addresses/ipv6-multicast-addresses.xhtml でマルチキャストアドレスの一覧が見られます。
  3. 1オクテット=1バイトです。昔は1バイトのビット数が機器によって違うため、8ビット単位のサイズをオクテットと呼んでいました。
  4. ATM(というデータリンクプロトコルがありました)のフレームサイズは57バイトとのことです。
  5. フレームサイズを推測する方法もRFC 4821に定義されています。http://blogs.itmedia.co.jp/komata/2011/04/udp-8fa9.html
  6. TCPでもio.Copy()などを使うと差はありませんが、自前でバッファを用意してRead()メソッドを呼ぶと、このフレーム単位での読み込みになるはずです。

カテゴリートップへ

この連載の記事
ピックアップ