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

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

このコミットは、Go言語のnetパッケージにおけるlistenerSockaddr関数の重複コードを削除し、Unix系システム向けの共通実装としてsrc/pkg/net/sock_unix.goに集約するものです。これにより、コードの重複が解消され、保守性と一貫性が向上しています。

コミット

  • コミットハッシュ: dc6e51cfd22336654f5a3ab5b3dcbbe45d998f1a
  • 作者: Mikio Hara mikioh.mikioh@gmail.com
  • コミット日時: 2013年2月8日 金曜日 17:02:08 +0900

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

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

元コミット内容

net: delete duplicate listenerSockaddr

R=golang-dev, dave
CC=golang-dev
https://golang.org/cl/7299067

変更の背景

Go言語のnetパッケージは、ネットワーク通信を扱うための基本的な機能を提供します。このパッケージ内には、ソケットのリスニング(接続待ち受け)に関連する処理を行うlistenerSockaddrという関数が存在していました。しかし、この関数は、BSD系OS (sock_bsd.go)、Linux (sock_linux.go)、Windows (sock_windows.go) といった複数のOS固有のファイルに、ほぼ同じ内容で重複して実装されていました。

コードの重複は、以下のような問題を引き起こします。

  • 保守性の低下: 同じロジックが複数箇所に存在するため、バグ修正や機能追加の際に、すべての重複箇所を更新する必要があり、見落としや不整合のリスクが高まります。
  • コード量の増加: 不要なコードの繰り返しにより、全体のコードベースが肥大化します。
  • 一貫性の欠如: 将来的に各OS固有のファイルで個別に変更が加えられた場合、ロジックに差異が生じ、予期せぬ動作やバグにつながる可能性があります。

このコミットは、このようなコードの重複を解消し、Unix系OS(Darwin, FreeBSD, Linux, NetBSD, OpenBSD)で共通するlistenerSockaddrのロジックをsrc/pkg/net/sock_unix.goという新しいファイルに集約することで、上記の課題を解決することを目的としています。Windows固有のロジックはsock_windows.goに残しつつ、重複部分を削減しています。

前提知識の解説

Go言語のnetパッケージ

netパッケージは、Go言語でTCP/IPネットワークプログラミングを行うための主要なパッケージです。クライアントとサーバーの両方の機能を提供し、TCP、UDP、Unixドメインソケットなどのプロトコルをサポートします。ソケットの作成、接続、リスニング、データの送受信といった低レベルな操作を抽象化し、使いやすいAPIを提供しています。

syscall.Sockaddr

syscall.Sockaddrは、Go言語のsyscallパッケージで定義されているインターフェースで、ソケットアドレスを表します。ソケットアドレスは、IPアドレスとポート番号(TCP/UDPの場合)や、ファイルパス(Unixドメインソケットの場合)など、ソケットが通信相手を識別するために必要な情報を含みます。OSによってソケットアドレスの構造が異なるため、syscallパッケージは各OSのネイティブなソケットアドレス構造をGoの型にマッピングしています。例えば、IPv4アドレスとポート番号はsyscall.SockaddrInet4、IPv6アドレスとポート番号はsyscall.SockaddrInet6、Unixドメインソケットのパスはsyscall.SockaddrUnixといった具体的な型で表現されます。

listenerSockaddr関数の役割

listenerSockaddr関数は、Goのnetパッケージ内部で、ソケットのリスニングを開始する前に、そのソケットに適切なオプションを設定するために使用される補助関数です。具体的には、以下の処理を行います。

  • デフォルトのリスナーソケットオプション設定: TCPやUnixドメインソケットの場合、setDefaultListenerSockoptsを呼び出して、再利用可能なアドレス(SO_REUSEADDR)などの一般的なソケットオプションを設定します。
  • マルチキャストソケットオプション設定: UDPソケットでマルチキャストアドレスを使用する場合、setDefaultMulticastSockoptsを呼び出して、マルチキャストグループへの参加やTTL(Time-To-Live)などのオプションを設定します。また、マルチキャストIPアドレスをIPv4zero(0.0.0.0)やIPv6unspecified(::)に設定し、任意のインターフェースからのマルチキャストパケットを受け入れられるように調整します。
  • アドレス変換: syscall.SockaddrからGoのnet.Addr型(例: *net.TCPAddr, *net.UDPAddr, *net.UnixAddr)への変換を行い、その情報に基づいて適切なソケットオプションを適用します。

ビルドタグ (+build)

Go言語では、ファイルの先頭に+buildディレクティブを記述することで、特定の環境(OS、アーキテクチャなど)でのみコンパイルされるように制御できます。これを「ビルドタグ」と呼びます。例えば、// +build linuxと書かれたファイルはLinux環境でのみコンパイルされ、// +build darwin freebsd linux netbsd openbsdと書かれたファイルは、指定されたUnix系OSのいずれかでコンパイルされます。この機能は、OS固有のシステムコールやAPIを使用する際に、クロスプラットフォーム対応を容易にするために不可欠です。

コードの重複とその問題点

前述の通り、コードの重複はソフトウェア開発において避けるべきプラクティスです。DRY (Don't Repeat Yourself) 原則は、同じ情報を複数回記述することを避けるべきであるというソフトウェア開発の原則です。この原則に従うことで、コードの保守性、可読性、拡張性が向上します。

技術的詳細

このコミットの主要な技術的変更は、listenerSockaddr関数の実装をOS固有のファイルから共通のファイルに移動し、重複を排除することです。

変更前: 各OS固有ファイルでの重複実装

変更前は、src/pkg/net/sock_bsd.go (BSD系OS), src/pkg/net/sock_linux.go (Linux), src/pkg/net/sock_windows.go (Windows) のそれぞれに、ほぼ同一のlistenerSockaddr関数が定義されていました。これらの関数は、引数としてソケットディスクリプタs、アドレスファミリーf、ローカルソケットアドレスla、そしてsyscall.Sockaddrnet.Addrに変換する関数toAddrを受け取ります。

各実装は、net.Addrの型(*TCPAddr, *UnixAddr, *UDPAddr)に応じて異なる処理を行っていました。

  • TCPAddr / UnixAddr: setDefaultListenerSockopts(s)を呼び出して、一般的なリスナーソケットオプションを設定します。
  • UDPAddr:
    • IP.IsMulticast()でマルチキャストアドレスかどうかをチェックします。
    • マルチキャストの場合、setDefaultMulticastSockopts(s)を呼び出してマルチキャスト関連のオプションを設定します。
    • アドレスファミリー(syscall.AF_INETまたはsyscall.AF_INET6)に応じて、IPアドレスをIPv4zeroまたはIPv6unspecifiedに設定し、任意のインターフェースからのマルチキャストパケットを受け入れられるようにします。
    • 最後に、v.sockaddr(f)を呼び出して、更新されたアドレス情報でsyscall.Sockaddrを再構築します。

このロジックが各OS固有のファイルで繰り返されていたため、非効率的でした。

変更後: sock_unix.goへの集約とsock_windows.goの修正

このコミットでは、以下の変更が行われました。

  1. src/pkg/net/sock_unix.goの新規作成:

    • このファイルは、// +build darwin freebsd linux netbsd openbsdというビルドタグを持ち、Unix系OSでのみコンパイルされます。
    • 重複していたlistenerSockaddr関数のロジックがこのファイルに完全に移動されました。これにより、Unix系OS向けの共通実装が提供されます。
  2. src/pkg/net/sock_bsd.goおよびsrc/pkg/net/sock_linux.goからの削除:

    • これらのファイルからlistenerSockaddr関数が完全に削除されました。これにより、大幅なコード削減が実現されました。
  3. src/pkg/net/sock_windows.goの修正:

    • Windows固有のlistenerSockaddr関数は残されましたが、Unix系OSと共通するロジック部分が削除され、より簡潔な形に修正されました。特に、switch v := a.(type)の変数をvからaに変更するなど、新しいsock_unix.goのスタイルに合わせるための微調整も行われています。
  4. src/pkg/net/sock_posix.goの修正:

    • このファイルでは、コメントの削除(// Socketsから// Generic POSIX socket creation.への変更)と、ビルドタグの調整が行われています。listenerSockaddr関数自体は含まれていませんが、関連するコードの整理が行われたものと考えられます。

変更のメリット

この変更により、以下のメリットが得られます。

  • コードの重複排除: listenerSockaddrのロジックが単一の場所に集約され、コードベースがよりDRYになりました。
  • 保守性の向上: 今後listenerSockaddrのロジックに変更が必要になった場合、sock_unix.goの1箇所を修正するだけで済み、バグの混入リスクが低減されます。
  • コードの明確化: 各OS固有のファイルが、そのOSに特有のロジックのみを含むようになり、コードの役割がより明確になりました。
  • 一貫性の確保: Unix系OS間でのlistenerSockaddrの動作が一貫していることが保証されます。

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

このコミットで変更された主要なファイルと、その変更内容は以下の通りです。

src/pkg/net/sock_bsd.go

--- a/src/pkg/net/sock_bsd.go
+++ b/src/pkg/net/sock_bsd.go
@@ -4,8 +4,6 @@
 
 // +build darwin freebsd netbsd openbsd
 
-// Sockets for BSD variants
-
 package net
 
 import (
@@ -31,32 +29,3 @@ func maxListenerBacklog() int {
 	}
 	return int(n)
 }
-
-func listenerSockaddr(s, f int, la syscall.Sockaddr, toAddr func(syscall.Sockaddr) Addr) (syscall.Sockaddr, error) {
-	a := toAddr(la)
-	if a == nil {
-		return la, nil
-	}
-	switch v := a.(type) {
-	case *TCPAddr, *UnixAddr:
-		err := setDefaultListenerSockopts(s)
-		if err != nil {
-			return nil, err
-		}
-	case *UDPAddr:
-		if v.IP.IsMulticast() {
-			err := setDefaultMulticastSockopts(s)
-			if err != nil {
-				return nil, err
-			}
-			switch f {
-			case syscall.AF_INET:
-				v.IP = IPv4zero
-			case syscall.AF_INET6:
-				v.IP = IPv6unspecified
-			}
-			return v.sockaddr(f)
-		}
-	}
-	return la, nil
-}

listenerSockaddr関数が完全に削除されました。

src/pkg/net/sock_linux.go

--- a/src/pkg/net/sock_linux.go
+++ b/src/pkg/net/sock_linux.go
@@ -2,8 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-// Sockets for Linux
-
 package net
 
 import "syscall"
@@ -25,32 +23,3 @@ func maxListenerBacklog() int {
 	}
 	return n
 }
-
-func listenerSockaddr(s, f int, la syscall.Sockaddr, toAddr func(syscall.Sockaddr) Addr) (syscall.Sockaddr, error) {
-	a := toAddr(la)
-	if a == nil {
-		return la, nil
-	}
-	switch v := a.(type) {
-	case *TCPAddr, *UnixAddr:
-		err := setDefaultListenerSockopts(s)
-		if err != nil {
-			return nil, err
-		}
-	case *UDPAddr:
-		if v.IP.IsMulticast() {
-			err := setDefaultMulticastSockopts(s)
-			if err != nil {
-				return nil, err
-			}
-			switch f {
-			case syscall.AF_INET:
-				v.IP = IPv4zero
-			case syscall.AF_INET6:
-				v.IP = IPv6unspecified
-			}
-			return v.sockaddr(f)
-		}
-	}\n\treturn la, nil
-}

listenerSockaddr関数が完全に削除されました。

src/pkg/net/sock_posix.go

--- a/src/pkg/net/sock_posix.go
+++ b/src/pkg/net/sock_posix.go
@@ -4,8 +4,6 @@
 
 // +build darwin freebsd linux netbsd openbsd windows
 
-// Sockets
-
 package net
 
 import (
@@ -15,7 +13,7 @@ import (
 
 var listenerBacklog = maxListenerBacklog()
 
-// Generic socket creation.
+// Generic POSIX socket creation.
 func socket(net string, f, t, p int, ipv6only bool, ulsa, ursa syscall.Sockaddr, deadline time.Time, toAddr func(syscall.Sockaddr) Addr) (fd *netFD, err error) {
 	s, err := sysSocket(f, t, p)
 	if err != nil {

コメントが変更され、ビルドタグが調整されました。

src/pkg/net/sock_unix.go

--- /dev/null
+++ b/src/pkg/net/sock_unix.go
@@ -0,0 +1,36 @@
+// Copyright 2009 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build darwin freebsd linux netbsd openbsd
+
+package net
+
+import "syscall"
+
+func listenerSockaddr(s, f int, la syscall.Sockaddr, toAddr func(syscall.Sockaddr) Addr) (syscall.Sockaddr, error) {
+	a := toAddr(la)
+	if a == nil {
+		return la, nil
+	}
+	switch a := a.(type) {
+	case *TCPAddr, *UnixAddr:
+		if err := setDefaultListenerSockopts(s); err != nil {
+			return nil, err
+		}
+	case *UDPAddr:
+		if a.IP.IsMulticast() {
+			if err := setDefaultMulticastSockopts(s); err != nil {
+				return nil, err
+			}
+			switch f {
+			case syscall.AF_INET:
+				a.IP = IPv4zero
+			case syscall.AF_INET6:
+				a.IP = IPv6unspecified
+			}
+			return a.sockaddr(f)
+		}
+	}
+	return la, nil
+}

listenerSockaddr関数が新規ファイルとして追加されました。このファイルはUnix系OSでのみコンパイルされます。

src/pkg/net/sock_windows.go

--- a/src/pkg/net/sock_windows.go
+++ b/src/pkg/net/sock_windows.go
@@ -2,8 +2,6 @@
 // Use of this source code is governed by a BSD-style
 // license that can be found in the LICENSE file.
 
-// Sockets for Windows
-
 package net
 
 import "syscall"
@@ -18,25 +16,23 @@ func listenerSockaddr(s syscall.Handle, f int, la syscall.Sockaddr, toAddr func(\
 	if a == nil {
 		return la, nil
 	}
-	switch v := a.(type) {
+	switch a := a.(type) {
 	case *TCPAddr, *UnixAddr:
-		err := setDefaultListenerSockopts(s)
-		if err != nil {
+		if err := setDefaultListenerSockopts(s); err != nil {
 			return nil, err
 		}
 	case *UDPAddr:
-		if v.IP.IsMulticast() {
-			err := setDefaultMulticastSockopts(s)
-			if err != nil {
+		if a.IP.IsMulticast() {
+			if err := setDefaultMulticastSockopts(s); err != nil {
 				return nil, err
 			}
 			switch f {
 			case syscall.AF_INET:
-				v.IP = IPv4zero
+				a.IP = IPv4zero
 			case syscall.AF_INET6:
-				v.IP = IPv6unspecified
+				a.IP = IPv6unspecified
 			}
-			return v.sockaddr(f)
+			return a.sockaddr(f)
 		}
 	}
 	return la, nil

listenerSockaddr関数が修正され、重複部分が削除されました。switch文の変数名がvからaに変更されています。

コアとなるコードの解説

このコミットのコアとなるコードは、新しく作成されたsrc/pkg/net/sock_unix.go内のlistenerSockaddr関数です。

// +build darwin freebsd linux netbsd openbsd

package net

import "syscall"

func listenerSockaddr(s, f int, la syscall.Sockaddr, toAddr func(syscall.Sockaddr) Addr) (syscall.Sockaddr, error) {
	a := toAddr(la) // syscall.Sockaddrをnet.Addrに変換
	if a == nil {
		return la, nil // 変換できない場合は元のsyscall.Sockaddrを返す
	}
	switch a := a.(type) { // net.Addrの具体的な型によって処理を分岐
	case *TCPAddr, *UnixAddr:
		// TCPまたはUnixドメインソケットの場合、デフォルトのリスナーソケットオプションを設定
		if err := setDefaultListenerSockopts(s); err != nil {
			return nil, err
		}
	case *UDPAddr:
		// UDPソケットの場合
		if a.IP.IsMulticast() { // マルチキャストアドレスかどうかをチェック
			// マルチキャストの場合、デフォルトのマルチキャストソケットオプションを設定
			if err := setDefaultMulticastSockopts(s); err != nil {
				return nil, err
			}
			switch f { // アドレスファミリーによってIPアドレスを調整
			case syscall.AF_INET:
				a.IP = IPv4zero // IPv4マルチキャストの場合、0.0.0.0に設定
			case syscall.AF_INET6:
				a.IP = IPv6unspecified // IPv6マルチキャストの場合、::に設定
			}
			return a.sockaddr(f) // 更新されたアドレス情報でsyscall.Sockaddrを再構築して返す
		}
	}
	return la, nil // 上記のいずれにも該当しない場合は元のsyscall.Sockaddrを返す
}

この関数は、以下のロジックで動作します。

  1. アドレス変換: 引数として渡されたsyscall.Sockaddr (la) を、toAddr関数を使ってGoのnet.Addrインターフェースの具体的な型(例: *net.TCPAddr, *net.UDPAddr, *net.UnixAddr)に変換します。変換できない場合は、元のsyscall.Sockaddrをそのまま返します。
  2. 型による分岐: 変換されたnet.Addrの具体的な型によって処理を分岐します。
    • *TCPAddr または *UnixAddr の場合:
      • setDefaultListenerSockopts(s)を呼び出します。この関数は、ソケットの再利用(SO_REUSEADDR)や、TCPのNagleアルゴリズム無効化(TCP_NODELAY)など、一般的なリスナーソケットに推奨されるオプションを設定します。エラーが発生した場合はそれを返します。
    • *UDPAddr の場合:
      • まず、a.IP.IsMulticast()をチェックし、UDPアドレスがマルチキャストアドレスであるかどうかを判断します。
      • マルチキャストアドレスの場合:
        • setDefaultMulticastSockopts(s)を呼び出します。この関数は、マルチキャストグループへの参加(IP_ADD_MEMBERSHIP)や、マルチキャストパケットのTTL設定(IP_MULTICAST_TTL)など、マルチキャスト通信に必要なソケットオプションを設定します。
        • 次に、アドレスファミリー(f)に応じて、IPアドレスを調整します。syscall.AF_INET(IPv4)の場合はIPv4zero(0.0.0.0)に、syscall.AF_INET6(IPv6)の場合はIPv6unspecified(::)に設定します。これにより、ソケットが特定のネットワークインターフェースにバインドされることなく、システム上の任意のインターフェースからのマルチキャストパケットを受け入れられるようになります。
        • 最後に、更新されたUDPAddr情報からa.sockaddr(f)を呼び出して新しいsyscall.Sockaddrを構築し、それを返します。
      • ユニキャストアドレスの場合: 特にソケットオプションの変更は行わず、そのまま処理を続行します。
  3. デフォルトの戻り値: 上記のいずれのケースにも該当しない場合(例: net.Addrnilでないが、TCPAddr, UnixAddr, UDPAddrのいずれでもない場合)、または処理が完了した場合は、元のsyscall.Sockaddr (la) を返します。

この集約されたlistenerSockaddr関数は、Goのnetパッケージが様々なOS上で一貫したネットワークリスニング動作を提供するための重要な役割を担っています。

関連リンク

参考にした情報源リンク