[インデックス 14391] ファイルの概要
このコミットは、Go言語のnet
パッケージにおけるPlan 9オペレーティングシステム向けのソケット関連機能の共通化と整理を目的としています。具体的には、ソケット操作に関する共通のロジックをnetFD
構造体とconn
構造体に集約し、各プロトコル(TCP, UDP)固有の実装から共通部分を分離することで、コードの重複を排除し、保守性を向上させています。
コミット
commit 306afc7725e5174e7ad06fc7ba160ec7bc262cb7
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date: Tue Nov 13 16:18:37 2012 +0900
net: consolidate common socket functions for Plan 9
This CL extends changeset 13126:fc4a62e14aba to Plan 9.
R=ality, golang-dev, dave, rsc
CC=golang-dev
https://golang.org/cl/6820124
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/306afc7725e5174e7ad06fc7ba160ec7bc262cb7
元コミット内容
このコミットは、changeset 13126:fc4a62e14aba
をPlan 9に拡張するものです。元のコミット(fc4a62e14aba
)は、Goのnet
パッケージにおいて、POSIX準拠のシステム(Linux, macOS, FreeBSDなど)向けに共通のソケットファイルディスクリプタ(netFD
)とコネクション(conn
)の抽象化を導入し、ソケット操作の共通化を図ったものです。この変更により、各OS固有のソケット実装から共通部分が切り出され、コードの重複が削減されました。
変更の背景
Go言語のnet
パッケージは、様々なオペレーティングシステム上でネットワーク通信機能を提供します。各OSには独自のシステムコールやネットワークインターフェースが存在するため、それぞれのOSに合わせた実装が必要となります。しかし、多くのソケット操作(読み書き、クローズ、アドレス取得など)はOS間で共通の概念を持ちます。
このコミット以前は、Plan 9向けのネットワークコード(ipsock_plan9.go
, tcpsock_plan9.go
, udpsock_plan9.go
など)において、ソケットのファイルディスクリプタ管理や基本的なI/O操作に関するロジックが各ファイル内で重複して記述されていました。これは、コードの冗長性を生み、将来的な機能追加やバグ修正の際に複数のファイルを変更する必要があるため、保守性の低下を招いていました。
先行するコミット(fc4a62e14aba
)でPOSIXシステム向けに共通ソケット機能の整理が行われたことを受け、同様の改善をPlan 9環境にも適用することが求められました。これにより、Plan 9固有のネットワーク実装においても、共通の抽象化レイヤーを導入し、コードの再利用性と一貫性を高めることが変更の背景にあります。
前提知識の解説
- Plan 9 from Bell Labs: ベル研究所が開発した分散オペレーティングシステムです。Unixの概念をさらに推し進め、システム内のあらゆるリソース(ファイル、デバイス、ネットワーク接続など)をファイルシステムとして表現するという思想が特徴です。ネットワーク接続も
/net
という特殊なディレクトリ以下のファイルとして扱われます。例えば、TCP接続は/net/tcp/clone
を開いて制御ファイルディスクリプタを取得し、そのディレクトリ内のdata
ファイルを通じてデータの送受信を行います。 - ファイルディスクリプタ (File Descriptor, FD): Unix系OSにおけるファイルやI/Oリソースへの抽象的な参照です。Go言語の
net
パッケージでは、ネットワーク接続も内部的にはファイルディスクリプタとして扱われます。 - ソケット (Socket): ネットワーク通信のエンドポイントです。アプリケーションがネットワークを通じてデータを送受信するためのインターフェースを提供します。
net.Conn
インターフェース: Go言語のnet
パッケージで定義されている、ネットワーク接続の共通インターフェースです。Read
,Write
,Close
,LocalAddr
,RemoteAddr
,SetDeadline
などのメソッドを持ち、TCPやUDPなどの具体的なプロトコルに依存しない汎用的なネットワークI/Oを可能にします。net.Listener
インターフェース: ネットワーク接続を待ち受けるための共通インターフェースです。Accept
,Close
,Addr
などのメソッドを持ちます。- コードの共通化 (Consolidation): 複数の場所で重複して記述されている類似のコードを、一つの共通モジュールや関数にまとめること。これにより、コードの保守性、可読性、再利用性が向上します。
技術的詳細
このコミットの主要な技術的変更は、Plan 9環境におけるネットワークソケットの共通抽象化レイヤーの導入です。
-
netFD
構造体の導入 (src/pkg/net/fd_plan9.go
):netFD
は、Plan 9のネットワークファイルディスクリプタを抽象化するための新しい構造体です。proto
,name
,dir
といったPlan 9のネットワークパスに関する情報、ctl
(制御ファイル)とdata
(データファイル)の*os.File
ポインタ、そしてladdr
(ローカルアドレス)とraddr
(リモートアドレス)を保持します。Read
,Write
,Close
,CloseRead
,CloseWrite
といった基本的なI/O操作メソッドがnetFD
に実装されています。これにより、TCPやUDPといった具体的なプロトコルに依存しない共通のファイルディスクリプタ操作が可能になります。- 特に
Read
とWrite
メソッドでは、data
ファイルがまだ開かれていない場合にos.OpenFile
で開くロジックが含まれており、Plan 9のファイルシステムベースのネットワークI/Oモデルを反映しています。 setDeadline
,setReadDeadline
,setWriteDeadline
,setReadBuffer
,setWriteBuffer
,dup
といったメソッドは、Plan 9ではサポートされていないため、syscall.EPLAN9
エラーを返すように実装されています。
-
conn
構造体の導入 (src/pkg/net/net.go
):conn
は、net.Conn
インターフェースを実装するための共通構造体です。- 内部に
*netFD
を保持し、net.Conn
インターフェースの各メソッド(Read
,Write
,Close
,LocalAddr
,RemoteAddr
,SetDeadline
など)の呼び出しを、内部のnetFD
の対応するメソッドに委譲します。 - これにより、
TCPConn
やUDPConn
のような具体的なコネクション型は、直接netFD
の操作を行うのではなく、このconn
構造体を埋め込むことでnet.Conn
インターフェースを簡単に実装できるようになります。
-
net_posix.go
の削除:- このファイルは、POSIXシステム向けの共通
conn
構造体と関連関数を定義していました。 - このコミットでは、Plan 9固有の
netFD
と、それをラップする共通conn
構造体を導入したため、net_posix.go
で定義されていたconn
構造体は不要となり削除されました。これは、Goのnet
パッケージがOSごとに異なる実装を持つことを許容しつつ、共通のインターフェースを提供する設計パターンを示しています。
- このファイルは、POSIXシステム向けの共通
-
ipsock_plan9.go
の変更:- 以前は
plan9Conn
という独自のコネクション構造体を持っていましたが、これが削除され、新しく導入されたnetFD
構造体を使用するように変更されました。 dialPlan9
関数とlistenPlan9
関数が、plan9Conn
やplan9Listener
の代わりに*netFD
を返すように変更されました。これにより、Plan 9におけるソケットの確立とリスニングの共通ロジックがnetFD
に集約されました。
- 以前は
-
tcpsock_plan9.go
およびudpsock_plan9.go
の変更:TCPConn
とUDPConn
構造体が、以前のplan9Conn
の埋め込みから、新しく導入されたconn
構造体の埋め込みに変更されました。- これにより、
TCPConn
とUDPConn
は、conn
が提供するnet.Conn
インターフェースの実装を自動的に継承し、各プロトコル固有のメソッド(例:TCPConn.CloseRead
,UDPConn.ReadFromUDP
)のみを実装すればよくなりました。 dialTCP
,ListenTCP
,dialUDP
,ListenUDP
といった関数も、netFD
やconn
を介してソケットを生成するように変更され、共通化されたロジックを利用するようになりました。
これらの変更により、Plan 9環境におけるネットワークコードは、よりモジュール化され、共通部分とOS固有部分が明確に分離されました。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更は、主に以下のファイルに集中しています。
-
src/pkg/net/fd_plan9.go
(新規追加):netFD
構造体の定義と、そのRead
,Write
,Close
などのメソッドの実装。- Plan 9固有のファイルディスクリプタ操作の抽象化。
-
src/pkg/net/net.go
(変更):conn
構造体の定義と、そのnet.Conn
インターフェースの実装。netFD
をラップし、共通のコネクション機能を提供する。
-
src/pkg/net/net_posix.go
(削除):- POSIXシステム向けの共通
conn
構造体が削除された。
- POSIXシステム向けの共通
-
src/pkg/net/ipsock_plan9.go
(変更):plan9Conn
構造体の削除。dialPlan9
およびlistenPlan9
関数が*netFD
を返すように変更。
-
src/pkg/net/tcpsock_plan9.go
(変更):TCPConn
がconn
構造体を埋め込むように変更。dialTCP
およびListenTCP
がnetFD
とconn
を利用するように変更。
-
src/pkg/net/udpsock_plan9.go
(変更):UDPConn
がconn
構造体を埋め込むように変更。dialUDP
およびListenUDP
がnetFD
とconn
を利用するように変更。
コアとなるコードの解説
src/pkg/net/fd_plan9.go
// Network file descritor.
type netFD struct {
proto, name, dir string
ctl, data *os.File
laddr, raddr Addr
}
// ...
func (fd *netFD) Read(b []byte) (n int, err error) {
if !fd.ok() {
return 0, syscall.EINVAL
}
if fd.data == nil {
fd.data, err = os.OpenFile(fd.dir+"/data", os.O_RDWR, 0)
if err != nil {
return 0, err
}
}
n, err = fd.data.Read(b)
// ...
return
}
func (fd *netFD) Write(b []byte) (n int, err error) {
if !fd.ok() {
return 0, syscall.EINVAL
}
if fd.data == nil {
fd.data, err = os.OpenFile(fd.dir+"/data", os.O_RDWR, 0)
if err != nil {
return 0, err
}
}
return fd.data.Write(b)
}
func (fd *netFD) Close() error {
if !fd.ok() {
return syscall.EINVAL
}
err := fd.ctl.Close()
if err != nil {
return err
}
if fd.data != nil {
err = fd.data.Close()
}
fd.ctl = nil
fd.data = nil
return err
}
netFD
構造体は、Plan 9のネットワークデバイスをファイルとして扱うという特性を直接的に反映しています。ctl
は制御ファイル(例: /net/tcp/0/ctl
)を、data
はデータ送受信ファイル(例: /net/tcp/0/data
)を表します。Read
やWrite
メソッドでは、必要に応じてdata
ファイルを開き、そこからデータの読み書きを行います。Close
メソッドは、制御ファイルとデータファイルの両方を閉じます。
src/pkg/net/net.go
type conn struct {
fd *netFD
}
func (c *conn) ok() bool { return c != nil && c.fd != nil }
// Implementation of the Conn interface.
// Read implements the Conn Read method.
func (c *conn) Read(b []byte) (int, error) {
if !c.ok() {
return 0, syscall.EINVAL
}
return c.fd.Read(b)
}
// Write implements the Conn Write method.
func (c *conn) Write(b []byte) (int, error) {
if !c.ok() {
return 0, syscall.EINVAL
}
return c.fd.Write(b)
}
// Close closes the connection.
func (c *conn) Close() error {
if !c.ok() {
return syscall.EINVAL
}
return c.fd.Close()
}
// LocalAddr returns the local network address.
func (c *conn) LocalAddr() Addr {
if !c.ok() {
return nil
}
return c.fd.laddr
}
// RemoteAddr returns the remote network address.
func (c *conn) RemoteAddr() Addr {
if !c.ok() {
return nil
}
return c.fd.raddr
}
// SetDeadline implements the Conn SetDeadline method.
func (c *conn) SetDeadline(t time.Time) error {
if !c.ok() {
return syscall.EINVAL
}
return setDeadline(c.fd, t)
}
// ... (他の net.Conn メソッドも同様に fd に委譲)
conn
構造体は、netFD
を埋め込むことで、net.Conn
インターフェースのすべてのメソッドを実装します。これにより、TCPConn
やUDPConn
のような高レベルのコネクション型は、conn
を埋め込むだけでnet.Conn
としての振る舞いを獲得でき、コードの重複を大幅に削減できます。各メソッドは、単に内部のnetFD
の対応するメソッドを呼び出すだけです。
src/pkg/net/ipsock_plan9.go
func dialPlan9(net string, laddr, raddr Addr) (*netFD, error) {
f, dest, proto, name, err := startPlan9(net, raddr)
if err != nil {
return nil, err
}
_, err = f.WriteString("connect " + dest)
if err != nil {
f.Close()
return nil, err
}
laddr, err = readPlan9Addr(proto, "/net/"+proto+"/"+name+"/local")
if err != nil {
f.Close()
return nil, err
}
raddr, err = readPlan9Addr(proto, "/net/"+proto+"/"+name+"/remote")
if err != nil {
f.Close()
return nil, err
}
return newFD(proto, name, f, laddr, raddr), nil
}
func listenPlan9(net string, laddr Addr) (*netFD, error) {
f, dest, proto, name, err := startPlan9(net, laddr)
if err != nil {
return nil, err
}
_, err = f.WriteString("announce " + dest)
if err != nil {
f.Close()
return nil, err
}
laddr, err = readPlan9Addr(proto, "/net/"+proto+"/"+name+"/local")
if err != nil {
f.Close()
return nil, err
}
return &netFD{proto: proto, name: name, dir: "/net/" + proto + "/" + name, ctl: f, laddr: laddr}, nil
}
dialPlan9
とlistenPlan9
は、Plan 9のネットワークプロトコル(例: tcp
, udp
)とアドレス情報に基づいて、新しいnetFD
インスタンスを生成する共通のロジックを提供します。これらは、Plan 9の/net
ファイルシステムを操作して、接続の確立やリスニングの開始を行います。
src/pkg/net/tcpsock_plan9.go
type TCPConn struct {
conn
}
func newTCPConn(fd *netFD) *TCPConn {
return &TCPConn{conn{fd}}
}
// ...
func dialTCP(net string, laddr, raddr *TCPAddr, deadline time.Time) (*TCPConn, error) {
// ...
fd, err := dialPlan9(net, laddr, raddr)
if err != nil {
return nil, err
}
return &TCPConn{conn{fd}}, nil
}
// ...
type TCPListener struct {
fd *netFD
}
// ...
func ListenTCP(net string, laddr *TCPAddr) (*TCPListener, error) {
// ...
fd, err := listenPlan9(net, laddr)
if err != nil {
return nil, err
}
return &TCPListener{fd}, nil
}
TCPConn
はconn
を埋め込むことで、net.Conn
インターフェースの基本的な機能(Read
, Write
, Close
など)をconn
から継承します。dialTCP
やListenTCP
は、dialPlan9
やlistenPlan9
を呼び出してnetFD
を取得し、それをconn
にラップしてTCPConn
を生成します。これにより、TCP固有のロジックは最小限に抑えられ、共通のソケット操作はnetFD
とconn
に委譲されます。udpsock_plan9.go
も同様のパターンで変更されています。
関連リンク
- Go言語の
net
パッケージのドキュメント: https://pkg.go.dev/net - Plan 9 from Bell Labs 公式サイト: https://9p.io/plan9/
- Plan 9のネットワークに関する資料 (例:
ip(3)
man page): https://9p.io/magic/man2html/3/ip
参考にした情報源リンク
- Go言語のコミット履歴 (特に
fc4a62e14aba
): https://github.com/golang/go/commit/fc4a62e14aba - Go言語のコードレビューシステム (Gerrit) の変更リスト (CL): https://golang.org/cl/6820124
- Plan 9のネットワークプログラミングに関する情報 (例: "The Plan 9 Network Stack"): https://9p.io/sys/doc/net.html
- Go言語の
net
パッケージのソースコード (特にsrc/pkg/net/
ディレクトリ): https://github.com/golang/go/tree/master/src/net - Go言語の
syscall
パッケージのドキュメント: https://pkg.go.dev/syscall - Go言語の
os
パッケージのドキュメント: https://pkg.go.dev/os