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

[インデックス 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環境におけるネットワークソケットの共通抽象化レイヤーの導入です。

  1. 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といった具体的なプロトコルに依存しない共通のファイルディスクリプタ操作が可能になります。
    • 特にReadWriteメソッドでは、dataファイルがまだ開かれていない場合にos.OpenFileで開くロジックが含まれており、Plan 9のファイルシステムベースのネットワークI/Oモデルを反映しています。
    • setDeadline, setReadDeadline, setWriteDeadline, setReadBuffer, setWriteBuffer, dupといったメソッドは、Plan 9ではサポートされていないため、syscall.EPLAN9エラーを返すように実装されています。
  2. conn 構造体の導入 (src/pkg/net/net.go):

    • connは、net.Connインターフェースを実装するための共通構造体です。
    • 内部に*netFDを保持し、net.Connインターフェースの各メソッド(Read, Write, Close, LocalAddr, RemoteAddr, SetDeadlineなど)の呼び出しを、内部のnetFDの対応するメソッドに委譲します。
    • これにより、TCPConnUDPConnのような具体的なコネクション型は、直接netFDの操作を行うのではなく、このconn構造体を埋め込むことでnet.Connインターフェースを簡単に実装できるようになります。
  3. net_posix.go の削除:

    • このファイルは、POSIXシステム向けの共通conn構造体と関連関数を定義していました。
    • このコミットでは、Plan 9固有のnetFDと、それをラップする共通conn構造体を導入したため、net_posix.goで定義されていたconn構造体は不要となり削除されました。これは、GoのnetパッケージがOSごとに異なる実装を持つことを許容しつつ、共通のインターフェースを提供する設計パターンを示しています。
  4. ipsock_plan9.go の変更:

    • 以前はplan9Connという独自のコネクション構造体を持っていましたが、これが削除され、新しく導入されたnetFD構造体を使用するように変更されました。
    • dialPlan9関数とlistenPlan9関数が、plan9Connplan9Listenerの代わりに*netFDを返すように変更されました。これにより、Plan 9におけるソケットの確立とリスニングの共通ロジックがnetFDに集約されました。
  5. tcpsock_plan9.go および udpsock_plan9.go の変更:

    • TCPConnUDPConn構造体が、以前のplan9Connの埋め込みから、新しく導入されたconn構造体の埋め込みに変更されました。
    • これにより、TCPConnUDPConnは、connが提供するnet.Connインターフェースの実装を自動的に継承し、各プロトコル固有のメソッド(例: TCPConn.CloseRead, UDPConn.ReadFromUDP)のみを実装すればよくなりました。
    • dialTCP, ListenTCP, dialUDP, ListenUDPといった関数も、netFDconnを介してソケットを生成するように変更され、共通化されたロジックを利用するようになりました。

これらの変更により、Plan 9環境におけるネットワークコードは、よりモジュール化され、共通部分とOS固有部分が明確に分離されました。

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

このコミットにおけるコアとなるコードの変更は、主に以下のファイルに集中しています。

  1. src/pkg/net/fd_plan9.go (新規追加):

    • netFD構造体の定義と、そのRead, Write, Closeなどのメソッドの実装。
    • Plan 9固有のファイルディスクリプタ操作の抽象化。
  2. src/pkg/net/net.go (変更):

    • conn構造体の定義と、そのnet.Connインターフェースの実装。
    • netFDをラップし、共通のコネクション機能を提供する。
  3. src/pkg/net/net_posix.go (削除):

    • POSIXシステム向けの共通conn構造体が削除された。
  4. src/pkg/net/ipsock_plan9.go (変更):

    • plan9Conn構造体の削除。
    • dialPlan9およびlistenPlan9関数が*netFDを返すように変更。
  5. src/pkg/net/tcpsock_plan9.go (変更):

    • TCPConnconn構造体を埋め込むように変更。
    • dialTCPおよびListenTCPnetFDconnを利用するように変更。
  6. src/pkg/net/udpsock_plan9.go (変更):

    • UDPConnconn構造体を埋め込むように変更。
    • dialUDPおよびListenUDPnetFDconnを利用するように変更。

コアとなるコードの解説

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)を表します。ReadWriteメソッドでは、必要に応じて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インターフェースのすべてのメソッドを実装します。これにより、TCPConnUDPConnのような高レベルのコネクション型は、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
}

dialPlan9listenPlan9は、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
}

TCPConnconnを埋め込むことで、net.Connインターフェースの基本的な機能(Read, Write, Closeなど)をconnから継承します。dialTCPListenTCPは、dialPlan9listenPlan9を呼び出してnetFDを取得し、それをconnにラップしてTCPConnを生成します。これにより、TCP固有のロジックは最小限に抑えられ、共通のソケット操作はnetFDconnに委譲されます。udpsock_plan9.goも同様のパターンで変更されています。

関連リンク

参考にした情報源リンク