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

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

このコミットは、Go言語の net パッケージにおけるソケットアドレスの扱いを改善し、syscall.Sockaddr への不要な変換を削減することを目的としています。これにより、特にBSD系のOSにおけるランタイム統合型ネットワークポーラーの準備を進めています。

コミット

commit 8a7def2b3b8fc801eb4f02e58328e84ee311910e
Author: Mikio Hara <mikioh.mikioh@gmail.com>
Date:   Sat Aug 3 13:32:22 2013 +0900

    net: reduce unnecessary syscall.Sockaddr conversions
    
    This CL makes IPAddr, UDPAddr and TCPAddr implement sockaddr
    interface, UnixAddr is already sockaddr interface compliant, and
    reduces unnecessary conversions between net.Addr, net.sockaddr and
    syscall.Sockaddr.
    
    This is in preparation for runtime-integrated network pollster for BSD
    variants.
    
    Update #5199
    
    R=golang-dev, dave, bradfitz
    CC=golang-dev
    https://golang.org/cl/12010043

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

https://github.com/golang/go/commit/8a7def2b3b8fc801eb4f02e58328e84ee311910e

元コミット内容

net: reduce unnecessary syscall.Sockaddr conversions

この変更は、IPAddrUDPAddrTCPAddr 型が sockaddr インターフェースを実装するようにし、UnixAddr が既に sockaddr インターフェースに準拠していることを利用して、net.Addrnet.sockaddrsyscall.Sockaddr 間の不要な変換を削減します。これは、BSD系のOS向けにランタイム統合型ネットワークポーラーを準備するための一環です。

関連するIssue: #5199

変更の背景

Go言語のネットワークスタックは、OSのシステムコールを抽象化して、クロスプラットフォームで一貫したネットワークプログラミングインターフェースを提供しています。この抽象化の過程で、Goの内部表現(net.Addrnet.sockaddr インターフェース)と、OSが要求するシステムコールレベルの表現(syscall.Sockaddr)との間で頻繁なデータ変換が発生していました。

特に、syscall.Sockaddr はOS固有のソケットアドレス構造体(例: syscall.SockaddrInet4, syscall.SockaddrInet6, syscall.SockaddrUnix など)を抽象化するインターフェースであり、Goの net パッケージ内でこれらの変換が繰り返されることは、パフォーマンスのオーバーヘッドやコードの複雑さを招いていました。

このコミットの主な背景は以下の点にあります。

  1. パフォーマンスの最適化: net.Addr から syscall.Sockaddr への頻繁な変換は、特に高負荷なネットワークアプリケーションにおいて、CPUサイクルを無駄に消費する可能性がありました。この変換を削減することで、ネットワーク処理の効率化が期待されます。
  2. コードの簡素化と保守性の向上: 変換ロジックが複数箇所に散らばっていると、コードの可読性が低下し、バグの温床となる可能性があります。インターフェースの実装を統一することで、コードベースがよりクリーンになり、将来的な変更やデバッグが容易になります。
  3. BSD系OS向けネットワークポーラーの準備: コミットメッセージに明記されているように、「runtime-integrated network pollster for BSD variants」の準備が重要な動機です。ネットワークポーラーは、複数のI/O操作を効率的に監視し、準備ができたI/Oイベントをアプリケーションに通知するメカニズムです。BSD系のOS(FreeBSD, OpenBSD, NetBSDなど)では、kqueue などのポーリングメカニズムが利用されます。これらのポーラーとGoランタイムをより密接に統合するためには、ソケットアドレスの表現と変換を最適化し、システムコール層との連携をスムーズにする必要がありました。不要な変換を排除することで、ポーラーが直接Goの内部アドレス表現を扱えるようになり、効率的なI/O多重化が可能になります。
  4. Issue #5199 の解決: このコミットは、GoのIssueトラッカーで報告されていた #5199 に関連しています。このIssueは、net パッケージにおけるソケットアドレスの変換効率に関するものであったと推測されます。

前提知識の解説

このコミットを理解するためには、以下のGo言語およびネットワークプログラミングに関する基本的な概念を理解しておく必要があります。

  1. net.Addr インターフェース: Go言語の net パッケージで定義されている基本的なインターフェースで、ネットワークアドレスを表します。

    type Addr interface {
        Network() string // "tcp", "udp", "ip", "unix" など
        String() string  // アドレスの文字列表現
    }
    

    TCPAddr, UDPAddr, IPAddr, UnixAddr など、具体的なアドレス型がこのインターフェースを実装しています。

  2. syscall.Sockaddr インターフェース: Go言語の syscall パッケージで定義されているインターフェースで、OSのシステムコールが期待するソケットアドレス構造体(例: sockaddr_in, sockaddr_in6, sockaddr_un など)を抽象化します。

    type Sockaddr interface {
        // このインターフェース自体にはメソッドは定義されていないが、
        // 各OS固有のSockaddr実装(例: SockaddrInet4)が
        // 内部的にOSのC言語構造体に対応するフィールドを持つ。
    }
    

    Goの net パッケージは、ネットワーク操作を行う際に、Goの net.Addr 型をこの syscall.Sockaddr 型に変換してシステムコールに渡す必要があります。

  3. net.sockaddr インターフェース (内部インターフェース): このコミットで言及されている net.sockaddr は、Goの net パッケージ内部で定義されている非公開(またはパッケージプライベート)のインターフェースです。これは、net.Addrsyscall.Sockaddr の間の変換ロジックをカプセル化し、ソケット操作の共通部分で利用されることを意図しています。コミット前のコードでは、IPAddr, UDPAddr, TCPAddr は直接 sockaddr インターフェースを実装していませんでした。

  4. ソケットプログラミングの基本:

    • ソケット (Socket): ネットワーク通信のエンドポイント。ファイルディスクリプタのように扱われます。
    • bind(): ソケットにローカルアドレス(IPアドレスとポート番号)を割り当てるシステムコール。サーバー側で特定のポートで接続を待ち受けるために使用されます。
    • connect(): ソケットをリモートアドレスに接続するシステムコール。クライアント側でサーバーに接続するために使用されます。
    • listen(): サーバーソケットが着信接続を待ち受ける状態にするシステムコール。
    • accept(): サーバーソケットが着信接続を受け入れるシステムコール。
    • syscall パッケージ: GoプログラムからOSのシステムコールを直接呼び出すためのパッケージ。低レベルのネットワーク操作やファイル操作などに使用されます。
  5. ネットワークポーラー (Network Pollster): 複数のI/Oディスクリプタ(ソケットなど)の状態変化(読み込み可能、書き込み可能など)を効率的に監視するためのメカニズム。Unix/Linuxでは select, poll, epoll が、BSD系OSでは kqueue が代表的です。Goランタイムは、これらのポーラーを利用して、ゴルーチンのスケジューリングとネットワークI/Oを非同期に処理しています。

  6. IPAddr, UDPAddr, TCPAddr, UnixAddr: Goの net パッケージで提供される具体的なネットワークアドレス型。

    • IPAddr: IPアドレスのみを表す。
    • UDPAddr: UDP通信のためのIPアドレスとポート番号。
    • TCPAddr: TCP通信のためのIPアドレスとポート番号。
    • UnixAddr: Unixドメインソケットのためのパス。

技術的詳細

このコミットの核心は、Goの net パッケージ内部でソケットアドレスを扱う方法の変更にあります。

変更前: IPAddr, UDPAddr, TCPAddr といった具体的なアドレス型は、直接 net パッケージ内部の sockaddr インターフェースを実装していませんでした。これらの型を syscall.Sockaddr に変換する必要がある場合、toAddr() メソッドを呼び出して net.sockaddr インターフェース型に変換し、さらにその sockaddr インターフェース型から sockaddr() メソッドを呼び出して syscall.Sockaddr に変換するという、多段階の変換プロセスが必要でした。

特に、internetSocket 関数(TCP/UDP/IPソケットの作成に使用される汎用関数)は、laddr (ローカルアドレス) と raddr (リモートアドレス) を sockaddr インターフェース型として受け取っていました。関数内部でこれらの sockaddr インターフェースから syscall.Sockaddr への変換が行われていました。

変更後:

  1. IPAddr, UDPAddr, TCPAddrsockaddr インターフェースを直接実装: このコミットにより、IPAddr, UDPAddr, TCPAddr 型が、net パッケージ内部で定義されている sockaddr インターフェースを直接実装するようになりました。具体的には、これらの型に sockaddr(family int) (syscall.Sockaddr, error) メソッドが追加されました。これにより、これらのアドレス型から直接 syscall.Sockaddr を取得できるようになります。 UnixAddr は元々 sockaddr インターフェースに準拠していたため、変更はありません。

  2. toAddr() メソッドの役割変更: toAddr() メソッドは、以前は *IPAddr*TCPAddr などのポインタ型を sockaddr インターフェース型に変換するために使用されていましたが、この変更により、その役割が簡素化されました。IPAddr, UDPAddr, TCPAddr 自体が sockaddr インターフェースを実装したため、toAddr() は単にレシーバ自身を sockaddr インターフェースとして返すようになりました。これにより、不要な型変換が削減されます。

  3. internetSocket 関数の引数変更: internetSocket 関数は、laddrraddr の引数として、以前は sockaddr インターフェース型を受け取っていましたが、このコミットにより、直接 IPAddr, UDPAddr, TCPAddr などの具体的なアドレス型(またはそれらをラップする sockaddr インターフェース型)を受け取るように変更されました。これにより、internetSocket 内部での syscall.Sockaddr への変換ロジックが簡素化され、呼び出し元から渡されたアドレス型が直接利用されるようになります。

  4. socket 関数の引数変更と内部ロジックの修正: socket 関数は、ソケットの作成と初期化を行う汎用関数です。この関数も laddrraddr の引数として sockaddr インターフェース型を直接受け取るように変更されました。関数内部では、laddr.sockaddr(f)raddr.sockaddr(f) を呼び出すことで、直接 syscall.Sockaddr を取得し、syscall.Bindfd.connect に渡すようになりました。これにより、中間的な syscall.Sockaddr 変数の宣言と代入が不要になり、コードがより直接的になります。

  5. listenerSockaddr 関数の引数変更: listenerSockaddr 関数は、リスナーソケットのアドレスを準備するために使用されます。この関数も laddr 引数として sockaddr インターフェース型を直接受け取るように変更され、内部で laddr.sockaddr(f) を呼び出すことで syscall.Sockaddr を取得するようになりました。

これらの変更により、net.Addr の具体的な実装型(IPAddr, UDPAddr, TCPAddr)が net パッケージ内部の sockaddr インターフェースを直接実装することで、net.Addr -> net.sockaddr -> syscall.Sockaddr という多段階の変換パスが短縮され、直接 net.Addr -> syscall.Sockaddr の変換が可能になります。これにより、コードのパスが短くなり、変換に伴うオーバーヘッドが削減されます。

特に、BSD系のOSにおけるネットワークポーラーとの統合を考慮すると、この最適化は重要です。ポーラーは低レベルのソケット操作に密接に関わるため、アドレス変換の効率化はシステムコール呼び出しのオーバーヘッドを減らし、全体的なI/O性能を向上させることに寄与します。

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

変更は主に src/pkg/net ディレクトリ内の以下のファイルに集中しています。

  • src/pkg/net/iprawsock_posix.go
  • src/pkg/net/ipsock_posix.go
  • src/pkg/net/sock_posix.go
  • src/pkg/net/sock_unix.go
  • src/pkg/net/sock_windows.go
  • src/pkg/net/tcpsock_posix.go
  • src/pkg/net/udpsock_posix.go
  • src/pkg/net/unixsock_posix.go

具体的な変更のパターンは以下の通りです。

  1. IPAddr, UDPAddr, TCPAddrsockaddr メソッドを追加/修正: 例: src/pkg/net/iprawsock_posix.go

    --- a/src/pkg/net/iprawsock_posix.go
    +++ b/src/pkg/net/iprawsock_posix.go
    @@ -39,12 +39,15 @@ func (a *IPAddr) isWildcard() bool {
     }
     
     func (a *IPAddr) sockaddr(family int) (syscall.Sockaddr, error) {
    +	if a == nil {
    +		return nil, nil
    +	}
     	return ipToSockaddr(family, a.IP, 0, a.Zone)
     }
     
     func (a *IPAddr) toAddr() sockaddr {
    -	if a == nil { // nil *IPAddr
    -		return nil // nil interface
    +	if a == nil {
    +		return nil
     	}
     	return a
     }
    

    sockaddr メソッドが追加され、toAddr メソッドが簡素化されています。

  2. internetSocket 関数の引数変更: 例: src/pkg/net/ipsock_posix.go

    --- a/src/pkg/net/ipsock_posix.go
    +++ b/src/pkg/net/ipsock_posix.go
    @@ -132,19 +132,8 @@ func favoriteAddrFamily(net string, laddr, raddr sockaddr, mode string) (family
     // Internet sockets (TCP, UDP, IP)
     
     func internetSocket(net string, laddr, raddr sockaddr, deadline time.Time, sotype, proto int, mode string, toAddr func(syscall.Sockaddr) Addr) (fd *netFD, err error) {
    -	var la, ra syscall.Sockaddr
      	family, ipv6only := favoriteAddrFamily(net, laddr, raddr, mode)
    -	if laddr != nil {
    -		if la, err = laddr.sockaddr(family); err != nil {
    -			goto Error
    -		}
    -	}
    -	if raddr != nil {
    -		if ra, err = raddr.Sockaddr(family); err != nil {
    -			goto Error
    -		}
    -	}
    -	fd, err = socket(net, family, sotype, proto, ipv6only, la, ra, deadline, toAddr)
    +	fd, err = socket(net, family, sotype, proto, ipv6only, laddr, raddr, deadline, toAddr)
      	if err != nil {
      		goto Error
      	}
    

    laddrraddr が直接 sockaddr インターフェースとして渡され、内部での syscall.Sockaddr への変換が削除されています。

  3. socket 関数の引数変更と内部ロジックの修正: 例: src/pkg/net/sock_posix.go

    --- a/src/pkg/net/sock_posix.go
    +++ b/src/pkg/net/sock_posix.go
    @@ -37,7 +37,7 @@ type sockaddr interface {
     }
     
     // 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) {
    +func socket(net string, f, t, p int, ipv6only bool, laddr, raddr sockaddr, deadline time.Time, toAddr func(syscall.Sockaddr) Addr) (fd *netFD, err error) {
      	s, err := sysSocket(f, t, p)
      	if err != nil {
      		return nil, err
    @@ -48,23 +48,42 @@ func socket(net string, f, t, p int, ipv6only bool, ulsa, ursa syscall.Sockaddr,
      		return nil, err
      	}
      
    -	// This socket is used by a listener.
    -	if ulsa != nil && ursa == nil {
    +	// This function makes a network file descriptor for stream
    +	// and datagram dialers, stream and datagram listeners.
    +	//
    +	// For dialers, they will require either named or unnamed
    +	// sockets for their flights.  We can assume that it's just a
    +	// request from a dialer that wants a named socket when both
    +	// laddr and raddr are not nil.  A dialer will also require a
    +	// connection setup initiated socket when raddr is not nil.
    +	//
    +	// For listeners and some dialers on datagram networks, they
    +	// will only require named sockets.  So we can assume that
    +	// it's just for a listener or a datagram dialer when laddr is
    +	// not nil but raddr is nil.
    +
    +	var lsa syscall.Sockaddr
    +	if laddr != nil && raddr == nil {
      		// We provide a socket that listens to a wildcard
    -		// address with reusable UDP port when the given ulsa
    +		// address with reusable UDP port when the given laddr
      		// is an appropriate UDP multicast address prefix.
      		// This makes it possible for a single UDP listener
      		// to join multiple different group addresses, for
      		// multiple UDP listeners that listen on the same UDP
      		// port to join the same group address.
    -		if ulsa, err = listenerSockaddr(s, f, ulsa, toAddr); err != nil {
    +		if lsa, err = listenerSockaddr(s, f, laddr); err != nil {
    +			closesocket(s)
    +			return nil, err
    +		}
    +	} else if laddr != nil && raddr != nil {
    +		if lsa, err = laddr.sockaddr(f); err != nil {
      			closesocket(s)
      			return nil, err
      		}
      	}
      
    -	if ulsa != nil {
    -		if err = syscall.Bind(s, ulsa); err != nil {
    +	if lsa != nil {
    +		if err = syscall.Bind(s, lsa); err != nil {
      			closesocket(s)
      			return nil, err
      		}
    @@ -75,12 +94,19 @@ func socket(net string, f, t, p int, ipv6only bool, ulsa, ursa syscall.Sockaddr,
      		return nil, err
      	}
      
    -	// This socket is used by a dialer.
    -	if ursa != nil {
    +	var rsa syscall.Sockaddr
    +	if raddr != nil {
    +		rsa, err = raddr.sockaddr(f)
    +		if err != nil {
    +			return nil, err
    +		}
    +	}
    +
    +	if rsa != nil {
      		if !deadline.IsZero() {
      			setWriteDeadline(fd, deadline)
      		}
    -		if err = fd.connect(ulsa, ursa); err != nil {
    +		if err = fd.connect(lsa, rsa); err != nil {
      			fd.Close()
      			return nil, err
      		}
    @@ -90,13 +116,11 @@ func socket(net string, f, t, p int, ipv6only bool, ulsa, ursa syscall.Sockaddr,
      		}
      	}
      
    -	lsa, _ := syscall.Getsockname(s)
    -	laddr := toAddr(lsa)
    -	rsa, _ := syscall.Getpeername(s)
    -	if rsa == nil {
    -		rsa = ursa
    +	lsa, _ = syscall.Getsockname(s)
    +	if rsa, _ = syscall.Getpeername(s); rsa != nil {
    +		fd.setAddr(toAddr(lsa), toAddr(rsa))
    +	} else {
    +		fd.setAddr(toAddr(lsa), raddr)
     	}
    -	raddr := toAddr(rsa)
    -	fd.setAddr(laddr, raddr)
      	return fd, nil
     }
    

    socket 関数の引数が syscall.Sockaddr から sockaddr インターフェースに変更され、内部で laddr.sockaddr(f)raddr.sockaddr(f) を呼び出すことで syscall.Sockaddr を取得するようになっています。また、fd.setAddr の呼び出しロジックも変更されています。

  4. listenerSockaddr 関数の引数変更: 例: src/pkg/net/sock_unix.go

    --- a/src/pkg/net/sock_unix.go
    +++ b/src/pkg/net/sock_unix.go
    @@ -8,29 +8,29 @@ 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) {
    +func listenerSockaddr(s, f int, laddr sockaddr) (syscall.Sockaddr, error) {
    +	switch laddr := laddr.(type) {
      	case *TCPAddr, *UnixAddr:
      		if err := setDefaultListenerSockopts(s); err != nil {
      			return nil, err
      		}
    +		return laddr.sockaddr(f)
      	case *UDPAddr:
    -		if a.IP.IsMulticast() {
    +		if laddr.IP != nil && laddr.IP.IsMulticast() {
      			if err := setDefaultMulticastSockopts(s); err != nil {
      				return nil, err
      			}
    +			addr := *laddr
      			switch f {
      			case syscall.AF_INET:
    -				a.IP = IPv4zero
    +				addr.IP = IPv4zero
      			case syscall.AF_INET6:
    -				a.IP = IPv6unspecified
    +				addr.IP = IPv6unspecified
      			}
    -			return a.sockaddr(f)
    +			laddr = &addr
      		}
    +		return laddr.sockaddr(f)
    +	default:
    +		return laddr.sockaddr(f)
      	}
    -	return la, nil
     }
    

    listenerSockaddr の引数も syscall.Sockaddr から sockaddr インターフェースに変更され、内部で laddr.sockaddr(f) を呼び出すようになっています。

コアとなるコードの解説

このコミットの主要な変更は、net パッケージ内のソケットアドレス表現と syscall.Sockaddr との間の変換ロジックを合理化することにあります。

  1. sockaddr インターフェースの実装の統一: 以前は、net.IPAddr, net.UDPAddr, net.TCPAddr は、net パッケージ内部の sockaddr インターフェースを直接実装していませんでした。そのため、これらの型を syscall.Sockaddr に変換する際には、一度 toAddr() メソッドを介して net.sockaddr インターフェース型に変換し、さらにその net.sockaddr インターフェース型が持つ sockaddr() メソッドを呼び出す必要がありました。

    このコミットでは、net.IPAddr, net.UDPAddr, net.TCPAddr が直接 sockaddr インターフェースを実装するように変更されました。具体的には、これらの型に以下のシグネチャを持つメソッドが追加されました。

    func (a *IPAddr) sockaddr(family int) (syscall.Sockaddr, error)
    func (a *UDPAddr) sockaddr(family int) (syscall.Sockaddr, error)
    func (a *TCPAddr) sockaddr(family int) (syscall.Sockaddr, error)
    

    これにより、これらのアドレス型から直接 syscall.Sockaddr を取得できるようになり、中間的な変換ステップが不要になりました。

  2. toAddr() メソッドの役割の簡素化: toAddr() メソッドは、以前は具体的なアドレス型(例: *IPAddr)を net.sockaddr インターフェース型に変換する役割を担っていました。しかし、IPAddr などが直接 sockaddr インターフェースを実装するようになったため、toAddr() メソッドは単にレシーバ自身を sockaddr インターフェースとして返すだけのシンプルな実装になりました。

    func (a *IPAddr) toAddr() sockaddr {
        if a == nil {
            return nil
        }
        return a // a は既に sockaddr インターフェースを実装している
    }
    

    これにより、toAddr() の呼び出しが不要になるか、あるいはその内部処理が大幅に簡素化され、不要な型アサーションやインターフェース変換が削減されます。

  3. internetSocket および socket 関数の引数と内部ロジックの変更: internetSocket 関数は、TCP/UDP/IPソケットの作成に使用される汎用関数です。以前は laddrraddrsockaddr インターフェース型として受け取り、関数内部で laddr.sockaddr(family) を呼び出して syscall.Sockaddr を取得していました。

    変更後も引数の型は sockaddr インターフェース型ですが、IPAddr, UDPAddr, TCPAddr が直接 sockaddr インターフェースを実装したことで、呼び出し元から渡される具体的なアドレス型がそのまま sockaddr インターフェースとして扱えるようになりました。これにより、internetSocket 内部での syscall.Sockaddr への変換ロジックがより直接的になり、中間変数が削減されました。

    同様に、socket 関数(ソケットの作成と初期化を行う低レベル関数)も、laddrraddrsockaddr インターフェース型として受け取るように変更されました。関数内部では、laddr.sockaddr(f)raddr.sockaddr(f) を直接呼び出すことで syscall.Sockaddr を取得し、syscall.Bindfd.connect に渡しています。これにより、コードのパスが短縮され、変換に伴うオーバーヘッドが削減されます。

    特に socket 関数では、lsa, _ := syscall.Getsockname(s)rsa, _ := syscall.Getpeername(s) の結果を toAddrnet.Addr に変換し、fd.setAddr に渡すロジックも変更されています。リモートアドレスが取得できない場合(例: listen ソケット)、以前は ursa (引数で渡されたリモート syscall.Sockaddr) を使用していましたが、変更後は raddr (引数で渡されたリモート sockaddr インターフェース) を直接使用するようになっています。これは、raddr が既に net.Addr の情報を含んでいるため、より自然な流れとなります。

これらの変更は、Goのネットワークスタックにおけるソケットアドレスの内部表現とOSシステムコール間のインターフェースをより効率的かつ直接的にすることで、パフォーマンスの向上とコードの簡素化を実現しています。特に、BSD系のOSにおけるランタイム統合型ネットワークポーラーのような低レベルの最適化を可能にするための基盤となります。

関連リンク

参考にした情報源リンク

  • Go言語の net パッケージのドキュメント: https://pkg.go.dev/net
  • Go言語の syscall パッケージのドキュメント: https://pkg.go.dev/syscall
  • Go言語のネットワークプログラミングに関する一般的な情報源 (例: Go by Example - Sockets, Go's net package internalsなど)
  • BSD系OSの kqueue に関する情報 (例: FreeBSD Handbook, OpenBSD man pagesなど)
  • Go言語のランタイムとスケジューラに関する情報 (Goの並行処理モデル、I/O多重化の仕組みなど)
  • Go言語のコミット履歴とChange Listのレビュー
  • Go言語のIssueトラッカー (Issue 5199の内容確認)
  • ソケットプログラミングの基本概念に関する一般的なネットワークプログラミングの書籍やオンラインリソース