[インデックス 17063] ファイルの概要
このコミットは、Go言語の標準ライブラリnet
パッケージにおけるネットワーク操作の内部実装に関するものです。具体的には、netFD
構造体にdial
、listenStream
、listenDatagram
という新しいメソッドを追加し、既存の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
との密な連携が課題となっていました。dial
、listenStream
、listenDatagram
: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
に追加されたメソッド:
-
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
に設定されます。 - 最終的に、
Getsockname
とGetpeername
システムコールを使用して、ソケットのローカルアドレスとリモートアドレスを取得し、netFD
に設定します。
-
func (fd *netFD) listenStream(laddr sockaddr, toAddr func(syscall.Sockaddr) Addr) error
:- このメソッドは、TCPなどのストリーム指向ソケットのリスニングを担当します。
setDefaultListenerSockopts(fd.sysfd)
を呼び出し、リスナーソケットにデフォルトのオプション(例:SO_REUSEADDR
)を設定します。laddr
(リスニングアドレス)が指定されていれば、ソケットをそのアドレスにバインドします(syscall.Bind
)。- バインド後、
Getsockname
を使用してソケットのローカルアドレスを取得し、netFD
に設定します。
-
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.go
のnetFD
メソッドに統合されたため、不要になりました。
- このファイル全体が削除されました。以前は、Unix系OS(Darwin, FreeBSD, Linux, NetBSD, OpenBSD)向けの
src/pkg/net/sock_windows.go
:- このファイルから
listenerSockaddr
関数が削除されました。同様に、Windows固有のlistenerSockaddr
の実装もsock_posix.go
のnetFD
メソッドに統合されました。
- このファイルから
このリファクタリングにより、ネットワーク操作のロジックが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
の初期化(newFD
とfd.init()
)に集中します。その後、laddr
とraddr
の組み合わせに基づいて、適切な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
メソッドでは、ローカルアドレスのバインドとリモートアドレスへの接続が統合され、デッドライン処理も含まれています。listenStream
とlistenDatagram
は、それぞれストリームとデータグラムのリスニングに特化し、マルチキャストUDPソケットの特別な処理もlistenDatagram
内に含まれるようになりました。
src/pkg/net/sock_unix.go
とsrc/pkg/net/sock_windows.go
の削除は、OS固有のlistenerSockaddr
の実装が不要になったことを意味します。これは、共通のロジックがsock_posix.go
のnetFD
メソッドに集約され、OS間の差異がより抽象化された形で扱われるようになったためです。これにより、コードベースの重複が減り、全体的な構造が簡素化されました。
関連リンク
- Go Issue 5199:
net: runtime-integrated network pollster for BSD variants
- このコミットが解決を目指す問題の背景にあるIssueです。 - Go Change List 12023043: このコミットのGo Gerrit上のレビューページです。
参考にした情報源リンク
- 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
構造体にdial
、listenStream
、listenDatagram
という新しいメソッドを追加し、既存の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
との密な連携が課題となっていました。dial
、listenStream
、listenDatagram
: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
に追加されたメソッド:
-
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
に設定されます。 - 最終的に、
Getsockname
とGetpeername
システムコールを使用して、ソケットのローカルアドレスとリモートアドレスを取得し、netFD
に設定します。 - エラーハンドリングには
os.NewSyscallError
が使用され、システムコールエラーをよりGoらしいエラー型でラップしています。
-
func (fd *netFD) listenStream(laddr sockaddr, toAddr func(syscall.Sockaddr) Addr) error
:- このメソッドは、TCPなどのストリーム指向ソケットのリスニングを担当します。
setDefaultListenerSockopts(fd.sysfd)
を呼び出し、リスナーソケットにデフォルトのオプション(例:SO_REUSEADDR
)を設定します。これは、ソケットを閉じた後すぐに同じアドレスとポートを再利用できるようにするためによく設定されます。laddr
(リスニングアドレス)が指定されていれば、ソケットをそのアドレスにバインドします(syscall.Bind
)。- バインド後、
Getsockname
を使用してソケットのローカルアドレスを取得し、netFD
に設定します。
-
func (fd *netFD) listenDatagram(laddr sockaddr, toAddr func(syscall.Sockaddr) Addr) error
:- このメソッドは、UDPなどのデータグラム指向ソケットのリスニングを担当します。
- 特に、マルチキャストアドレスが指定されたUDPソケットの場合、
setDefaultMulticastSockopts(fd.sysfd)
を呼び出し、マルチキャスト関連のソケットオプションを設定します。これにより、単一のUDPリスナーが複数のマルチキャストグループに参加したり、同じUDPポートで複数のリスナーが同じグループに参加したりすることが可能になります。具体的には、IP_MULTICAST_LOOP
やIP_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.go
のnetFD
メソッドに統合されたため、不要になりました。
- このファイル全体が削除されました。以前は、Unix系OS(Darwin, FreeBSD, Linux, NetBSD, OpenBSD)向けの
src/pkg/net/sock_windows.go
:- このファイルから
listenerSockaddr
関数が削除されました。同様に、Windows固有のlistenerSockaddr
の実装もsock_posix.go
のnetFD
メソッドに統合されました。
- このファイルから
このリファクタリングにより、ネットワーク操作のロジックが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
の初期化(newFD
とfd.init()
)に集中します。その後、laddr
とraddr
の組み合わせに基づいて、適切な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
メソッドでは、ローカルアドレスのバインドとリモートアドレスへの接続が統合され、デッドライン処理も含まれています。listenStream
とlistenDatagram
は、それぞれストリームとデータグラムのリスニングに特化し、マルチキャストUDPソケットの特別な処理もlistenDatagram
内に含まれるようになりました。
src/pkg/net/sock_unix.go
とsrc/pkg/net/sock_windows.go
の削除は、OS固有のlistenerSockaddr
の実装が不要になったことを意味します。これは、共通のロジックがsock_posix.go
のnetFD
メソッドに集約され、OS間の差異がより抽象化された形で扱われるようになったためです。これにより、コードベースの重複が減り、全体的な構造が簡素化されました。
関連リンク
- Go Issue 5199:
net: runtime-integrated network pollster for BSD variants
- このコミットが解決を目指す問題の背景にあるIssueです。 - Go Change List 12023043: このコミットのGo Gerrit上のレビューページです。
参考にした情報源リンク
- Go言語の
net
パッケージのドキュメント: - Go言語の
syscall
パッケージのドキュメント: - Goのネットワークポーラーに関する情報:
- BSDソケットプログラミングに関する一般的な情報源(
sockaddr
,bind
,connect
,listen
などのシステムコールについて) - マルチキャストソケットプログラミングに関する一般的な情報源(UDPマルチキャストの挙動について)