Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 17063] ファイルの概要

このコミットは、Go言語の標準ライブラリnetパッケージにおけるネットワーク操作の内部実装に関するものです。具体的には、netFD構造体にdiallistenStreamlistenDatagramという新しいメソッドを追加し、既存のlistenerSockaddr関数をこれらのメソッドにリファクタリングしています。これにより、ネットワークポーリングの統合に向けた基盤が強化されています。

コミット

commit b29d035fe61e32d55fffe19be0abc95baa9078ae
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date:   Wed Aug 7 06:15:50 2013 +0900

    net: add dial, listenStream and listenDatagram methods to netFD
    
    This CL refactors the existing listenerSockaddr function into several
    methods on netFD.
    
    This is in preparation for runtime-integrated network pollster for BSD
    variants.
    
    Update #5199
    
    R=golang-dev, dave, alex.brainman, dvyukov, remyoudompheng
    CC=golang-dev
    https://golang.org/cl/12023043

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/b29d035fe61e32d55fffe19be0abc95baa9078ae

元コミット内容

net: add dial, listenStream and listenDatagram methods to netFD

この変更は、既存のlistenerSockaddr関数をnetFD上の複数のメソッドにリファクタリングするものです。これは、BSD系のOS向けにランタイム統合されたネットワークポーラーを準備するためです。

変更の背景

このコミットの主な背景は、GoランタイムとネットワークI/Oの統合をより深く進めることにあります。特に、BSD系のオペレーティングシステム(FreeBSD, OpenBSD, NetBSDなど)におけるネットワークポーリングの効率化と、それによるパフォーマンス向上を目指しています。

GoのネットワークI/Oは、内部的にノンブロッキングI/Oとイベント通知メカニズム(Linuxのepoll、macOS/BSDのkqueueなど)を利用して実装されています。これにより、多数の同時接続を効率的に処理できるコルーチンベースの並行処理モデル(goroutine)が実現されています。

以前の実装では、ソケットのバインドやリスニングに関連するロジックがlistenerSockaddrという単一の関数に集約されていました。しかし、このアプローチでは、異なる種類のネットワーク操作(ダイヤル、ストリームリスニング、データグラムリスニング)に対して共通のnetFD構造体を通じて一貫したインターフェースを提供することが困難でした。また、ランタイムレベルでのネットワークポーラーとの連携を強化するためには、より粒度の細かい制御と、ソケットディスクリプタ(ファイルディスクリプタ)を抽象化するnetFD構造体への機能集約が必要とされていました。

この変更は、GoのIssue #5199に関連しています。Issue #5199は、Goのネットワークポーラーが特定の条件下でブロックする可能性があり、特にBSD系OSでのkqueueベースのポーリングメカニズムの改善を求めるものでした。このコミットは、その改善に向けた一歩として、ネットワークI/Oの内部構造を整理し、将来的なポーラー統合のための基盤を築くことを目的としています。

前提知識の解説

このコミットを理解するためには、以下の概念が重要です。

  • netFD (Network File Descriptor): Goのnetパッケージにおけるソケットの抽象化です。オペレーティングシステムが提供するファイルディスクリプタ(またはソケットディスクリプタ)をラップし、Goランタイムのスケジューラと連携してノンブロッキングI/Oを可能にします。netFDは、ソケットの状態管理、デッドラインの設定、I/Oイベントのポーリングなど、低レベルのネットワーク操作をカプセル化します。
  • sockaddr (Socket Address Structure): ソケット通信において、通信相手のアドレス(IPアドレスとポート番号など)を表現するための汎用的なデータ構造です。OSのシステムコール(bind, connectなど)に渡されます。Goでは、syscall.Sockaddrインターフェースとして抽象化されています。
  • syscallパッケージ: GoプログラムからOSのシステムコールを呼び出すためのパッケージです。ネットワーク操作においては、syscall.Bind(ソケットにアドレスをバインド)、syscall.Connect(ソケットをリモートアドレスに接続)、syscall.Listen(ソケットをリスニング状態にする)などが使用されます。
  • ネットワークポーラー (Network Pollster): ノンブロッキングI/Oにおいて、複数のI/O操作の完了を効率的に待機するためのメカニズムです。Linuxのepoll、macOS/BSDのkqueue、WindowsのIOCPなどがこれに該当します。Goランタイムは、これらのOS固有のメカニズムを抽象化し、goroutineのスケジューリングと連携させることで、高効率な並行ネットワークI/Oを実現しています。
  • ノンブロッキングI/O: I/O操作が即座に完了しない場合でも、呼び出し元のスレッドをブロックせずに制御を返すI/Oモデルです。これにより、単一のスレッドで複数のI/O操作を同時に管理できます。Goのgoroutineは、このノンブロッキングI/Oとポーラーの組み合わせによって、あたかもブロッキングI/Oであるかのように記述できる「見かけ上のブロッキングI/O」を実現しています。
  • listenerSockaddr関数: このコミット以前に存在した関数で、ソケットにアドレスをバインドし、リスニングを開始するための共通ロジックをカプセル化していました。しかし、その汎用性ゆえに、異なるネットワーク操作のコンテキストでの再利用性や、netFDとの密な連携が課題となっていました。
  • diallistenStreamlistenDatagram:
    • dial: クライアント側からリモートアドレスへの接続を確立する操作。
    • listenStream: TCPなどのストリーム指向プロトコルで、着信接続を待機する操作。
    • listenDatagram: UDPなどのデータグラム指向プロトコルで、データグラムの受信を待機する操作。

技術的詳細

このコミットの核心は、src/pkg/net/sock_posix.goファイルにおけるsocket関数の大幅な変更と、netFD構造体への新しいメソッドの追加です。

変更前は、socket関数内でソケットの作成、newFDによるnetFDの初期化、そしてlistenerSockaddr関数によるバインドとリスニングの処理が一連の流れとして行われていました。特に、listenerSockaddrはTCP、UDP、Unixドメインソケットなど、様々な種類のリスナーソケットのアドレス処理を担っていました。

変更後、socket関数はより汎用的なソケット作成とnetFDの初期化に特化し、具体的なネットワーク操作(ダイヤル、ストリームリスニング、データグラムリスニング)のロジックは、新しく追加されたnetFDのメソッドに委譲されるようになりました。

netFDに追加されたメソッド:

  1. func (fd *netFD) dial(laddr, raddr sockaddr, deadline time.Time, toAddr func(syscall.Sockaddr) Addr) error:

    • このメソッドは、クライアント側からの接続確立(ダイヤル)を担当します。
    • laddr(ローカルアドレス)が指定されていれば、ソケットをそのアドレスにバインドします(syscall.Bind)。
    • raddr(リモートアドレス)が指定されていれば、ソケットをそのアドレスに接続します(fd.connect)。
    • デッドライン(deadline)が設定されている場合、接続操作にタイムアウトを適用します。
    • 接続が成功すると、fd.isConnectedフラグがtrueに設定されます。
    • 最終的に、GetsocknameGetpeernameシステムコールを使用して、ソケットのローカルアドレスとリモートアドレスを取得し、netFDに設定します。
  2. func (fd *netFD) listenStream(laddr sockaddr, toAddr func(syscall.Sockaddr) Addr) error:

    • このメソッドは、TCPなどのストリーム指向ソケットのリスニングを担当します。
    • setDefaultListenerSockopts(fd.sysfd)を呼び出し、リスナーソケットにデフォルトのオプション(例: SO_REUSEADDR)を設定します。
    • laddr(リスニングアドレス)が指定されていれば、ソケットをそのアドレスにバインドします(syscall.Bind)。
    • バインド後、Getsocknameを使用してソケットのローカルアドレスを取得し、netFDに設定します。
  3. func (fd *netFD) listenDatagram(laddr sockaddr, toAddr func(syscall.Sockaddr) Addr) error:

    • このメソッドは、UDPなどのデータグラム指向ソケットのリスニングを担当します。
    • 特に、マルチキャストアドレスが指定されたUDPソケットの場合、setDefaultMulticastSockopts(fd.sysfd)を呼び出し、マルチキャスト関連のソケットオプションを設定します。これにより、単一のUDPリスナーが複数のマルチキャストグループに参加したり、同じUDPポートで複数のリスナーが同じグループに参加したりすることが可能になります。
    • laddr(リスニングアドレス)が指定されていれば、ソケットをそのアドレスにバインドします(syscall.Bind)。
    • バインド後、Getsocknameを使用してソケットのローカルアドレスを取得し、netFDに設定します。

ファイルの変更点:

  • src/pkg/net/sock_posix.go:
    • socket関数からlistenerSockaddrの呼び出しが削除され、代わりにnetFDの新しいメソッド(dial, listenStream, listenDatagram)が呼び出されるようになりました。
    • netFD構造体に上記の3つのメソッドが追加されました。
    • osパッケージがインポートされました。これは、os.NewSyscallErrorを使用してシステムコールエラーをより適切にラップするためです。
  • src/pkg/net/sock_unix.go:
    • このファイル全体が削除されました。以前は、Unix系OS(Darwin, FreeBSD, Linux, NetBSD, OpenBSD)向けのlistenerSockaddrの実装が含まれていましたが、そのロジックがsock_posix.gonetFDメソッドに統合されたため、不要になりました。
  • src/pkg/net/sock_windows.go:
    • このファイルからlistenerSockaddr関数が削除されました。同様に、Windows固有のlistenerSockaddrの実装もsock_posix.gonetFDメソッドに統合されました。

このリファクタリングにより、ネットワーク操作のロジックがnetFDに集約され、ソケットのライフサイクル管理とI/Oイベント処理が一貫した方法で行えるようになりました。これは、GoランタイムがネットワークI/Oをより細かく制御し、特にBSD系OSにおけるkqueueなどのイベント通知メカニズムとの統合を容易にするための重要なステップです。

コアとなるコードの変更箇所

src/pkg/net/sock_posix.go

--- a/src/pkg/net/sock_posix.go
+++ b/src/pkg/net/sock_posix.go
@@ -7,6 +7,7 @@
 package net
 
 import (
+\t"os"
 \t"syscall"
 \t"time"
 )
@@ -48,6 +49,15 @@ func socket(net string, f, t, p int, ipv6only bool, laddr, raddr sockaddr, deadl
 \t\treturn nil, err
 \t}\n \n+\tif fd, err = newFD(s, f, t, net); err != nil {\n+\t\tclosesocket(s)\n+\t\treturn nil, err\n+\t}\n+\tif err := fd.init(); err != nil {\n+\t\tfd.Close()\n+\t\treturn nil, err
+\t}\n+\n \t// This function makes a network file descriptor for stream
 \t// and datagram dialers, stream and datagram listeners.\n \t//
@@ -62,69 +72,115 @@ func socket(net string, f, t, p int, ipv6only bool, laddr, raddr sockaddr, deadl
 \t// it\'s just for a listener or a datagram dialer when laddr is\n \t// not nil but raddr is nil.\n \n-\tvar lsa syscall.Sockaddr\n \tif laddr != nil && raddr == nil {\n-\t\t// We provide a socket that listens to a wildcard\n-\t\t// address with reusable UDP port when the given laddr\n-\t\t// is an appropriate UDP multicast address prefix.\n-\t\t// This makes it possible for a single UDP listener\n-\t\t// to join multiple different group addresses, for\n-\t\t// multiple UDP listeners that listen on the same UDP\n-\t\t// port to join the same group address.\n-\t\tif lsa, err = listenerSockaddr(s, f, laddr); err != nil {\n-\t\t\tclosesocket(s)\n-\t\t\treturn nil, err\n-\t\t}\n-\t} else if laddr != nil && raddr != nil {\n-\t\tif lsa, err = laddr.sockaddr(f); err != nil {\n-\t\t\tclosesocket(s)\n-\t\t\treturn nil, err\n+\t\tswitch t {\n+\t\tcase syscall.SOCK_STREAM, syscall.SOCK_SEQPACKET:\n+\t\t\tif err := fd.listenStream(laddr, toAddr); err != nil {\n+\t\t\t\tfd.Close()\n+\t\t\t\treturn nil, err\n+\t\t\t}\n+\t\t\treturn fd, nil\n+\t\tcase syscall.SOCK_DGRAM:\n+\t\t\tif err := fd.listenDatagram(laddr, toAddr); err != nil {\n+\t\t\t\tfd.Close()\n+\t\t\t\treturn nil, err\n+\t\t\t}\n+\t\t\treturn fd, nil\n \t\t}\n \t}\n-\n-\tif lsa != nil {\n-\t\tif err = syscall.Bind(s, lsa); err != nil {\n-\t\t\tclosesocket(s)\n-\t\t\treturn nil, err\n-\t\t}\n-\t}\n-\n-\tif fd, err = newFD(s, f, t, net); err != nil {\n-\t\tclosesocket(s)\n-\t\treturn nil, err\n-\t}\n-\tif err := fd.init(); err != nil {\n+\tif err := fd.dial(laddr, raddr, deadline, toAddr); err != nil {\n \t\tfd.Close()\n \t\treturn nil, err\n \t}\n+\treturn fd, nil\n+}\n \n+func (fd *netFD) dial(laddr, raddr sockaddr, deadline time.Time, toAddr func(syscall.Sockaddr) Addr) error {\n+\tvar err error\n+\tvar lsa syscall.Sockaddr\n+\tif laddr != nil {\n+\t\tif lsa, err = laddr.sockaddr(fd.family); err != nil {\n+\t\t\treturn err\n+\t\t} else if lsa != nil {\n+\t\t\tif err := syscall.Bind(fd.sysfd, lsa); err != nil {\n+\t\t\t\treturn os.NewSyscallError(\"bind\", err)\n+\t\t\t}\n+\t\t}\n+\t}\n \tvar rsa syscall.Sockaddr\n \tif raddr != nil {\n-\t\trsa, err = raddr.sockaddr(f)\n-\t\tif err != nil {\n-\t\t\treturn nil, err\n+\t\tif rsa, err = raddr.sockaddr(fd.family); err != nil {\n+\t\t\treturn err\n+\t\t} else if rsa != nil {\n+\t\t\tif !deadline.IsZero() {\n+\t\t\t\tsetWriteDeadline(fd, deadline)\n+\t\t\t}\n+\t\t\tif err := fd.connect(lsa, rsa); err != nil {\n+\t\t\t\treturn err\n+\t\t\t}\n+\t\t\tfd.isConnected = true\n+\t\t\tif !deadline.IsZero() {\n+\t\t\t\tsetWriteDeadline(fd, noDeadline)\n+\t\t\t}\n \t\t}\n \t}\n+\tlsa, _ = syscall.Getsockname(fd.sysfd)\n+\tif rsa, _ = syscall.Getpeername(fd.sysfd); rsa != nil {\n+\t\tfd.setAddr(toAddr(lsa), toAddr(rsa))\n+\t} else {\n+\t\tfd.setAddr(toAddr(lsa), raddr)\n+\t}\n+\treturn nil\n+}\n \n-\tif rsa != nil {\n-\t\tif !deadline.IsZero() {\n-\t\t\tsetWriteDeadline(fd, deadline)\n-\t\t}\n-\t\tif err = fd.connect(lsa, rsa); err != nil {\n-\t\t\tfd.Close()\n-\t\t\treturn nil, err\n-\t\t}\n-\t\tfd.isConnected = true\n-\t\tif !deadline.IsZero() {\n-\t\t\tsetWriteDeadline(fd, noDeadline)\n+func (fd *netFD) listenStream(laddr sockaddr, toAddr func(syscall.Sockaddr) Addr) error {\n+\tif err := setDefaultListenerSockopts(fd.sysfd); err != nil {\n+\t\treturn err\n+\t}\n+\tif lsa, err := laddr.sockaddr(fd.family); err != nil {\n+\t\treturn err\n+\t} else if lsa != nil {\n+\t\tif err := syscall.Bind(fd.sysfd, lsa); err != nil {\n+\t\t\treturn os.NewSyscallError(\"bind\", err)\n \t\t}\n \t}\n+\tlsa, _ := syscall.Getsockname(fd.sysfd)\n+\tfd.setAddr(toAddr(lsa), nil)\n+\treturn nil\n+}\n \n-\tlsa, _ = syscall.Getsockname(s)\n-\tif rsa, _ = syscall.Getpeername(s); rsa != nil {\n-\t\tfd.setAddr(toAddr(lsa), toAddr(rsa))\n-\t} else {\n-\t\tfd.setAddr(toAddr(lsa), raddr)\n+func (fd *netFD) listenDatagram(laddr sockaddr, toAddr func(syscall.Sockaddr) Addr) error {\n+\tswitch addr := laddr.(type) {\n+\tcase *UDPAddr:\n+\t\t// We provide a socket that listens to a wildcard\n+\t\t// address with reusable UDP port when the given laddr\n+\t\t// is an appropriate UDP multicast address prefix.\n+\t\t// This makes it possible for a single UDP listener to\n+\t\t// join multiple different group addresses, for\n+\t\t// multiple UDP listeners that listen on the same UDP\n+\t\t// port to join the same group address.\n+\t\tif addr.IP != nil && addr.IP.IsMulticast() {\n+\t\t\tif err := setDefaultMulticastSockopts(fd.sysfd); err != nil {\n+\t\t\t\treturn err\n+\t\t\t}\n+\t\t\taddr := *addr\n+\t\t\tswitch fd.family {\n+\t\t\tcase syscall.AF_INET:\n+\t\t\t\taddr.IP = IPv4zero\n+\t\t\tcase syscall.AF_INET6:\n+\t\t\t\taddr.IP = IPv6unspecified\n+\t\t\t}\n+\t\t\tladdr = &addr\n+\t\t}\n \t}\n-\treturn fd, nil\n+\tif lsa, err := laddr.sockaddr(fd.family); err != nil {\n+\t\treturn err\n+\t} else if lsa != nil {\n+\t\tif err := syscall.Bind(fd.sysfd, lsa); err != nil {\n+\t\t\treturn os.NewSyscallError(\"bind\", err)\n+\t\t}\n+\t}\n+\tlsa, _ := syscall.Getsockname(fd.sysfd)\n+\tfd.setAddr(toAddr(lsa), nil)\n+\treturn nil\n }

src/pkg/net/sock_unix.go および src/pkg/net/sock_windows.go

これらのファイルは完全に削除されました。

コアとなるコードの解説

このコミットの主要な変更は、socket関数がnetFDの新しいメソッドを呼び出すように変更された点と、それらの新しいメソッドの実装です。

以前のsocket関数は、ソケットの作成、netFDの初期化、そしてlistenerSockaddrの呼び出しをすべて含んでいました。listenerSockaddrは、リスニングソケットのバインドとオプション設定のロジックをカプセル化していました。

新しい実装では、socket関数はソケットの作成とnetFDの初期化(newFDfd.init())に集中します。その後、laddrraddrの組み合わせに基づいて、適切なnetFDメソッドが呼び出されます。

  • laddr != nil && raddr == nil の場合: これはリスナーソケットのケースです。socketタイプ(syscall.SOCK_STREAM, syscall.SOCK_SEQPACKETはストリーム、syscall.SOCK_DGRAMはデータグラム)に応じて、fd.listenStreamまたはfd.listenDatagramが呼び出されます。
  • それ以外の場合(主にダイヤラーソケットのケース): fd.dialが呼び出されます。

これにより、各ネットワーク操作のロジックがnetFDのメソッドとして明確に分離され、コードの可読性と保守性が向上しました。また、netFDがソケットのバインド、接続、アドレス設定といった低レベルの操作を直接管理するようになったことで、GoランタイムがネットワークI/Oをより細かく制御し、将来的なポーラー統合のための柔軟性が高まりました。

特に、dialメソッドでは、ローカルアドレスのバインドとリモートアドレスへの接続が統合され、デッドライン処理も含まれています。listenStreamlistenDatagramは、それぞれストリームとデータグラムのリスニングに特化し、マルチキャストUDPソケットの特別な処理もlistenDatagram内に含まれるようになりました。

src/pkg/net/sock_unix.gosrc/pkg/net/sock_windows.goの削除は、OS固有のlistenerSockaddrの実装が不要になったことを意味します。これは、共通のロジックがsock_posix.gonetFDメソッドに集約され、OS間の差異がより抽象化された形で扱われるようになったためです。これにより、コードベースの重複が減り、全体的な構造が簡素化されました。

関連リンク

参考にした情報源リンク

  • Go言語のnetパッケージのドキュメント:
  • Go言語のsyscallパッケージのドキュメント:
  • GoのネットワークI/Oに関するブログ記事や解説(一般的なGoのネットワークプログラミングの理解に役立ちます):
    • "Go's netpoller" (Goのネットワークポーラーに関する詳細な解説)
    • "The Go net package" (Goのnetパッケージの内部構造に関する解説)
  • BSDソケットプログラミングに関する一般的な情報源(sockaddr, bind, connect, listenなどのシステムコールについて)
  • マルチキャストソケットプログラミングに関する一般的な情報源(UDPマルチキャストの挙動について)
  • GoのIssueトラッカーとGerritコードレビューシステムに関する情報。

[インデックス 17063] ファイルの概要

このコミットは、Go言語の標準ライブラリnetパッケージにおけるネットワーク操作の内部実装に関するものです。具体的には、netFD構造体にdiallistenStreamlistenDatagramという新しいメソッドを追加し、既存のlistenerSockaddr関数をこれらのメソッドにリファクタリングしています。これにより、ネットワークポーリングの統合に向けた基盤が強化されています。

コミット

commit b29d035fe61e32d55fffe19be0abc95baa9078ae
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date:   Wed Aug 7 06:15:50 2013 +0900

    net: add dial, listenStream and listenDatagram methods to netFD
    
    This CL refactors the existing listenerSockaddr function into several
    methods on netFD.
    
    This is in preparation for runtime-integrated network pollster for BSD
    variants.
    
    Update #5199
    
    R=golang-dev, dave, alex.brainman, dvyukov, remyoudompheng
    CC=golang-dev
    https://golang.org/cl/12023043

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/b29d035fe61e32d55fffe19be0abc95baa9078ae

元コミット内容

net: add dial, listenStream and listenDatagram methods to netFD

この変更は、既存のlistenerSockaddr関数をnetFD上の複数のメソッドにリファクタリングするものです。これは、BSD系のOS向けにランタイム統合されたネットワークポーラーを準備するためです。

変更の背景

このコミットの主な背景は、GoランタイムとネットワークI/Oの統合をより深く進めることにあります。特に、BSD系のオペレーティングシステム(FreeBSD, OpenBSD, NetBSDなど)におけるネットワークポーリングの効率化と、それによるパフォーマンス向上を目指しています。

GoのネットワークI/Oは、内部的にノンブロッキングI/Oとイベント通知メカニズム(Linuxのepoll、macOS/BSDのkqueueなど)を利用して実装されています。これにより、多数の同時接続を効率的に処理できるコルーチンベースの並行処理モデル(goroutine)が実現されています。

以前の実装では、ソケットのバインドやリスニングに関連するロジックがlistenerSockaddrという単一の関数に集約されていました。しかし、このアプローチでは、異なる種類のネットワーク操作(ダイヤル、ストリームリスニング、データグラムリスニング)に対して共通のnetFD構造体を通じて一貫したインターフェースを提供することが困難でした。また、ランタイムレベルでのネットワークポーラーとの連携を強化するためには、より粒度の細かい制御と、ソケットディスクリプタ(ファイルディスクリプタ)を抽象化するnetFD構造体への機能集約が必要とされていました。

この変更は、GoのIssue #5199に関連しています。Issue #5199は、Goのネットワークポーラーが特定の条件下でブロックする可能性があり、特にBSD系OSでのkqueueベースのポーリングメカニズムの改善を求めるものでした。このコミットは、その改善に向けた一歩として、ネットワークI/Oの内部構造を整理し、将来的なポーラー統合のための基盤を築くことを目的としています。

前提知識の解説

このコミットを理解するためには、以下の概念が重要です。

  • netFD (Network File Descriptor): Goのnetパッケージにおけるソケットの抽象化です。オペレーティングシステムが提供するファイルディスクリプタ(またはソケットディスクリプタ)をラップし、Goランタイムのスケジューラと連携してノンブロッキングI/Oを可能にします。netFDは、ソケットの状態管理、デッドラインの設定、I/Oイベントのポーリングなど、低レベルのネットワーク操作をカプセル化します。
  • sockaddr (Socket Address Structure): ソケット通信において、通信相手のアドレス(IPアドレスとポート番号など)を表現するための汎用的なデータ構造です。OSのシステムコール(bind, connectなど)に渡されます。Goでは、syscall.Sockaddrインターフェースとして抽象化されています。
  • syscallパッケージ: GoプログラムからOSのシステムコールを呼び出すためのパッケージです。ネットワーク操作においては、syscall.Bind(ソケットにアドレスをバインド)、syscall.Connect(ソケットをリモートアドレスに接続)、syscall.Listen(ソケットをリスニング状態にする)などが使用されます。
  • ネットワークポーラー (Network Pollster): ノンブロッキングI/Oにおいて、複数のI/O操作の完了を効率的に待機するためのメカニズムです。Linuxのepoll、macOS/BSDのkqueue、WindowsのIOCPなどがこれに該当します。Goランタイムは、これらのOS固有のメカニズムを抽象化し、goroutineのスケジューリングと連携させることで、高効率な並行ネットワークI/Oを実現しています。BSD系OSではkqueueが利用されます。kqueueは、Goランタイムが複数のファイルディスクリプタを効率的に監視し、I/O準備イベント(読み取り可能なデータがある、書き込みバッファが準備できたなど)を待機することを可能にします。これにより、Goは多数の同時ネットワーク接続を最小限のOSスレッドで管理し、従来の「接続ごとにスレッド」モデルと比較してオーバーヘッドを大幅に削減します。
  • ノンブロッキングI/O: I/O操作が即座に完了しない場合でも、呼び出し元のスレッドをブロックせずに制御を返すI/Oモデルです。これにより、単一のスレッドで複数のI/O操作を同時に管理できます。Goのgoroutineは、このノンブロッキングI/Oとポーラーの組み合わせによって、あたかもブロッキングI/Oであるかのように記述できる「見かけ上のブロッキングI/O」を実現しています。
  • listenerSockaddr関数: このコミット以前に存在した関数で、ソケットにアドレスをバインドし、リスニングを開始するための共通ロジックをカプセル化していました。しかし、その汎用性ゆえに、異なるネットワーク操作のコンテキストでの再利用性や、netFDとの密な連携が課題となっていました。
  • diallistenStreamlistenDatagram:
    • dial: クライアント側からリモートアドレスへの接続を確立する操作。
    • listenStream: TCPなどのストリーム指向プロトコルで、着信接続を待機する操作。
    • listenDatagram: UDPなどのデータグラム指向プロトコルで、データグラムの受信を待機する操作。

技術的詳細

このコミットの核心は、src/pkg/net/sock_posix.goファイルにおけるsocket関数の大幅な変更と、netFD構造体への新しいメソッドの追加です。

変更前は、socket関数内でソケットの作成、newFDによるnetFDの初期化、そしてlistenerSockaddr関数によるバインドとリスニングの処理が一連の流れとして行われていました。特に、listenerSockaddrはTCP、UDP、Unixドメインソケットなど、様々な種類のリスナーソケットのアドレス処理を担っていました。

変更後、socket関数はより汎用的なソケット作成とnetFDの初期化に特化し、具体的なネットワーク操作(ダイヤル、ストリームリスニング、データグラムリスニング)のロジックは、新しく追加されたnetFDのメソッドに委譲されるようになりました。

netFDに追加されたメソッド:

  1. func (fd *netFD) dial(laddr, raddr sockaddr, deadline time.Time, toAddr func(syscall.Sockaddr) Addr) error:

    • このメソッドは、クライアント側からの接続確立(ダイヤル)を担当します。
    • laddr(ローカルアドレス)が指定されていれば、ソケットをそのアドレスにバインドします(syscall.Bind)。
    • raddr(リモートアドレス)が指定されていれば、ソケットをそのアドレスに接続します(fd.connect)。
    • デッドライン(deadline)が設定されている場合、接続操作にタイムアウトを適用します。
    • 接続が成功すると、fd.isConnectedフラグがtrueに設定されます。
    • 最終的に、GetsocknameGetpeernameシステムコールを使用して、ソケットのローカルアドレスとリモートアドレスを取得し、netFDに設定します。
    • エラーハンドリングにはos.NewSyscallErrorが使用され、システムコールエラーをよりGoらしいエラー型でラップしています。
  2. func (fd *netFD) listenStream(laddr sockaddr, toAddr func(syscall.Sockaddr) Addr) error:

    • このメソッドは、TCPなどのストリーム指向ソケットのリスニングを担当します。
    • setDefaultListenerSockopts(fd.sysfd)を呼び出し、リスナーソケットにデフォルトのオプション(例: SO_REUSEADDR)を設定します。これは、ソケットを閉じた後すぐに同じアドレスとポートを再利用できるようにするためによく設定されます。
    • laddr(リスニングアドレス)が指定されていれば、ソケットをそのアドレスにバインドします(syscall.Bind)。
    • バインド後、Getsocknameを使用してソケットのローカルアドレスを取得し、netFDに設定します。
  3. func (fd *netFD) listenDatagram(laddr sockaddr, toAddr func(syscall.Sockaddr) Addr) error:

    • このメソッドは、UDPなどのデータグラム指向ソケットのリスニングを担当します。
    • 特に、マルチキャストアドレスが指定されたUDPソケットの場合、setDefaultMulticastSockopts(fd.sysfd)を呼び出し、マルチキャスト関連のソケットオプションを設定します。これにより、単一のUDPリスナーが複数のマルチキャストグループに参加したり、同じUDPポートで複数のリスナーが同じグループに参加したりすることが可能になります。具体的には、IP_MULTICAST_LOOPIP_ADD_MEMBERSHIPなどのオプションが設定される可能性があります。
    • laddr(リスニングアドレス)が指定されていれば、ソケットをそのアドレスにバインドします(syscall.Bind)。
    • バインド後、Getsocknameを使用してソケットのローカルアドレスを取得し、netFDに設定します。

ファイルの変更点:

  • src/pkg/net/sock_posix.go:
    • socket関数からlistenerSockaddrの呼び出しが削除され、代わりにnetFDの新しいメソッド(dial, listenStream, listenDatagram)が呼び出されるようになりました。
    • netFD構造体に上記の3つのメソッドが追加されました。
    • osパッケージがインポートされました。これは、os.NewSyscallErrorを使用してシステムコールエラーをより適切にラップするためです。
  • src/pkg/net/sock_unix.go:
    • このファイル全体が削除されました。以前は、Unix系OS(Darwin, FreeBSD, Linux, NetBSD, OpenBSD)向けのlistenerSockaddrの実装が含まれていましたが、そのロジックがsock_posix.gonetFDメソッドに統合されたため、不要になりました。
  • src/pkg/net/sock_windows.go:
    • このファイルからlistenerSockaddr関数が削除されました。同様に、Windows固有のlistenerSockaddrの実装もsock_posix.gonetFDメソッドに統合されました。

このリファクタリングにより、ネットワーク操作のロジックがnetFDに集約され、ソケットのライフサイクル管理とI/Oイベント処理が一貫した方法で行えるようになりました。これは、GoランタイムがネットワークI/Oをより細かく制御し、特にBSD系OSにおけるkqueueなどのイベント通知メカニズムとの統合を容易にするための重要なステップです。

コアとなるコードの変更箇所

src/pkg/net/sock_posix.go

--- a/src/pkg/net/sock_posix.go
+++ b/src/pkg/net/sock_posix.go
@@ -7,6 +7,7 @@
 package net
 
 import (
+\t"os"
 \t"syscall"
 \t"time"
 )
@@ -48,6 +49,15 @@ func socket(net string, f, t, p int, ipv6only bool, laddr, raddr sockaddr, deadl
 \t\treturn nil, err
 \t}\n \n+\tif fd, err = newFD(s, f, t, net); err != nil {\n+\t\tclosesocket(s)\n+\t\treturn nil, err\n+\t}\n+\tif err := fd.init(); err != nil {\n+\t\tfd.Close()\n+\t\treturn nil, err
+\t}\n+\n \t// This function makes a network file descriptor for stream
 \t// and datagram dialers, stream and datagram listeners.\n \t//
@@ -62,69 +72,115 @@ func socket(net string, f, t, p int, ipv6only bool, laddr, raddr sockaddr, deadl
 \t// it\'s just for a listener or a datagram dialer when laddr is\n \t// not nil but raddr is nil.\n \n-\tvar lsa syscall.Sockaddr\n \tif laddr != nil && raddr == nil {\n-\t\t// We provide a socket that listens to a wildcard\n-\t\t// address with reusable UDP port when the given laddr\n-\t\t// is an appropriate UDP multicast address prefix.\n-\t\t// This makes it possible for a single UDP listener\n-\t\t// to join multiple different group addresses, for\n-\t\t// multiple UDP listeners that listen on the same UDP\n-\t\t// port to join the same group address.\n-\t\tif lsa, err = listenerSockaddr(s, f, laddr); err != nil {\n-\t\t\tclosesocket(s)\n-\t\t\treturn nil, err\n-\t\t}\n-\t} else if laddr != nil && raddr != nil {\n-\t\tif lsa, err = laddr.sockaddr(f); err != nil {\n-\t\t\tclosesocket(s)\n-\t\t\treturn nil, err\n+\t\tswitch t {\n+\t\tcase syscall.SOCK_STREAM, syscall.SOCK_SEQPACKET:\n+\t\t\tif err := fd.listenStream(laddr, toAddr); err != nil {\n+\t\t\t\tfd.Close()\n+\t\t\t\treturn nil, err\n+\t\t\t}\n+\t\t\treturn fd, nil\n+\t\tcase syscall.SOCK_DGRAM:\n+\t\t\tif err := fd.listenDatagram(laddr, toAddr); err != nil {\n+\t\t\t\tfd.Close()\n+\t\t\t\treturn nil, err\n+\t\t\t}\n+\t\t\treturn fd, nil\n \t\t}\n \t}\n-\n-\tif lsa != nil {\n-\t\tif err = syscall.Bind(s, lsa); err != nil {\n-\t\t\tclosesocket(s)\n-\t\t\treturn nil, err\n-\t\t}\n-\t}\n-\n-\tif fd, err = newFD(s, f, t, net); err != nil {\n-\t\tclosesocket(s)\n-\t\treturn nil, err\n-\t}\n-\tif err := fd.init(); err != nil {\n+\tif err := fd.dial(laddr, raddr, deadline, toAddr); err != nil {\n \t\tfd.Close()\n \t\treturn nil, err\n \t}\n+\treturn fd, nil\n+}\n \n+func (fd *netFD) dial(laddr, raddr sockaddr, deadline time.Time, toAddr func(syscall.Sockaddr) Addr) error {\n+\tvar err error\n+\tvar lsa syscall.Sockaddr\n+\tif laddr != nil {\n+\t\tif lsa, err = laddr.sockaddr(fd.family); err != nil {\n+\t\t\treturn err\n+\t\t} else if lsa != nil {\n+\t\t\tif err := syscall.Bind(fd.sysfd, lsa); err != nil {\n+\t\t\t\treturn os.NewSyscallError(\"bind\", err)\n+\t\t\t}\n+\t\t}\n+\t}\n \tvar rsa syscall.Sockaddr\n \tif raddr != nil {\n-\t\trsa, err = raddr.sockaddr(f)\n-\t\tif err != nil {\n-\t\t\treturn nil, err\n+\t\tif rsa, err = raddr.sockaddr(fd.family); err != nil {\n+\t\t\treturn err\n+\t\t} else if rsa != nil {\n+\t\t\tif !deadline.IsZero() {\n+\t\t\t\tsetWriteDeadline(fd, deadline)\n+\t\t\t}\n+\t\t\tif err := fd.connect(lsa, rsa); err != nil {\n+\t\t\t\treturn err\n+\t\t\t}\n+\t\t\tfd.isConnected = true\n+\t\t\tif !deadline.IsZero() {\n+\t\t\t\tsetWriteDeadline(fd, noDeadline)\n+\t\t\t}\n \t\t}\n \t}\n+\tlsa, _ = syscall.Getsockname(fd.sysfd)\n+\tif rsa, _ = syscall.Getpeername(fd.sysfd); rsa != nil {\n+\t\tfd.setAddr(toAddr(lsa), toAddr(rsa))\n+\t} else {\n+\t\tfd.setAddr(toAddr(lsa), raddr)\n+\t}\n+\treturn nil\n+}\n \n-\tif rsa != nil {\n-\t\tif !deadline.IsZero() {\n-\t\t\tsetWriteDeadline(fd, deadline)\n-\t\t}\n-\t\tif err = fd.connect(lsa, rsa); err != nil {\n-\t\t\tfd.Close()\n-\t\t\treturn nil, err\n-\t\t}\n-\t\tfd.isConnected = true\n-\t\tif !deadline.IsZero() {\n-\t\t\tsetWriteDeadline(fd, noDeadline)\n+func (fd *netFD) listenStream(laddr sockaddr, toAddr func(syscall.Sockaddr) Addr) error {\n+\tif err := setDefaultListenerSockopts(fd.sysfd); err != nil {\n+\t\treturn err\n+\t}\n+\tif lsa, err := laddr.sockaddr(fd.family); err != nil {\n+\t\treturn err\n+\t} else if lsa != nil {\n+\t\tif err := syscall.Bind(fd.sysfd, lsa); err != nil {\n+\t\t\treturn os.NewSyscallError(\"bind\", err)\n \t\t}\n \t}\n+\tlsa, _ := syscall.Getsockname(fd.sysfd)\n+\tfd.setAddr(toAddr(lsa), nil)\n+\treturn nil\n+}\n \n-\tlsa, _ = syscall.Getsockname(s)\n-\tif rsa, _ = syscall.Getpeername(s); rsa != nil {\n-\t\tfd.setAddr(toAddr(lsa), toAddr(rsa))\n-\t} else {\n-\t\tfd.setAddr(toAddr(lsa), raddr)\n+func (fd *netFD) listenDatagram(laddr sockaddr, toAddr func(syscall.Sockaddr) Addr) error {\n+\tswitch addr := laddr.(type) {\n+\tcase *UDPAddr:\n+\t\t// We provide a socket that listens to a wildcard\n+\t\t// address with reusable UDP port when the given laddr\n+\t\t// is an appropriate UDP multicast address prefix.\n+\t\t// This makes it possible for a single UDP listener to\n+\t\t// join multiple different group addresses, for\n+\t\t// multiple UDP listeners that listen on the same UDP\n+\t\t// port to join the same group address.\n+\t\tif addr.IP != nil && addr.IP.IsMulticast() {\n+\t\t\tif err := setDefaultMulticastSockopts(fd.sysfd); err != nil {\n+\t\t\t\treturn err\n+\t\t\t}\n+\t\t\taddr := *addr\n+\t\t\tswitch fd.family {\n+\t\t\tcase syscall.AF_INET:\n+\t\t\t\taddr.IP = IPv4zero\n+\t\t\tcase syscall.AF_INET6:\n+\t\t\t\taddr.IP = IPv6unspecified\n+\t\t\t}\n+\t\t\tladdr = &addr\n+\t\t}\n \t}\n-\treturn fd, nil\n+\tif lsa, err := laddr.sockaddr(fd.family); err != nil {\n+\t\treturn err\n+\t} else if lsa != nil {\n+\t\tif err := syscall.Bind(fd.sysfd, lsa); err != nil {\n+\t\t\treturn os.NewSyscallError(\"bind\", err)\n+\t\t}\n+\t}\n+\tlsa, _ := syscall.Getsockname(fd.sysfd)\n+\tfd.setAddr(toAddr(lsa), nil)\n+\treturn nil\n }

src/pkg/net/sock_unix.go および src/pkg/net/sock_windows.go

これらのファイルは完全に削除されました。

コアとなるコードの解説

このコミットの主要な変更は、socket関数がnetFDの新しいメソッドを呼び出すように変更された点と、それらの新しいメソッドの実装です。

以前のsocket関数は、ソケットの作成、netFDの初期化、そしてlistenerSockaddrの呼び出しをすべて含んでいました。listenerSockaddrは、リスニングソケットのバインドとオプション設定のロジックをカプセル化していました。

新しい実装では、socket関数はソケットの作成とnetFDの初期化(newFDfd.init())に集中します。その後、laddrraddrの組み合わせに基づいて、適切なnetFDメソッドが呼び出されます。

  • laddr != nil && raddr == nil の場合: これはリスナーソケットのケースです。socketタイプ(syscall.SOCK_STREAM, syscall.SOCK_SEQPACKETはストリーム、syscall.SOCK_DGRAMはデータグラム)に応じて、fd.listenStreamまたはfd.listenDatagramが呼び出されます。
  • それ以外の場合(主にダイヤラーソケットのケース): fd.dialが呼び出されます。

これにより、各ネットワーク操作のロジックがnetFDのメソッドとして明確に分離され、コードの可読性と保守性が向上しました。また、netFDがソケットのバインド、接続、アドレス設定といった低レベルの操作を直接管理するようになったことで、GoランタイムがネットワークI/Oをより細かく制御し、将来的なポーラー統合のための柔軟性が高まりました。

特に、dialメソッドでは、ローカルアドレスのバインドとリモートアドレスへの接続が統合され、デッドライン処理も含まれています。listenStreamlistenDatagramは、それぞれストリームとデータグラムのリスニングに特化し、マルチキャストUDPソケットの特別な処理もlistenDatagram内に含まれるようになりました。

src/pkg/net/sock_unix.gosrc/pkg/net/sock_windows.goの削除は、OS固有のlistenerSockaddrの実装が不要になったことを意味します。これは、共通のロジックがsock_posix.gonetFDメソッドに集約され、OS間の差異がより抽象化された形で扱われるようになったためです。これにより、コードベースの重複が減り、全体的な構造が簡素化されました。

関連リンク

参考にした情報源リンク