前書き
始める前に、まず「ソケット」と「ファイルディスクリプタ」という 2 つの概念について簡単に説明します。これは後の内容の理解を助けるためです。
ソケットとは
ソケット(Socket)は、ネットワーク通信の基本的な抽象であり、アプリケーションがネットワークプロトコルスタックへアクセスするための標準インターフェースを提供します。簡単に言えば、ソケットはネットワーク通信のエンドポイントであり、異なるコンピュータ上のプログラム同士がネットワーク経由でデータ交換を行うことを可能にします。
ソケットの主な特徴:
- アプリケーション層とトランスポート層の間のインターフェースである
- 特別なファイルとして見なされ、読み書き操作が可能
- 種類が異なる(TCP ソケット:コネクション型、UDP ソケット:コネクションレス型など)
ファイルディスクリプタとは
ファイルディスクリプタ(File Descriptor)は、オペレーティングシステムが開いたファイルを識別・管理するために使用する整数値です。Unix/Linux システムでは、全てがファイルであり、通常のファイル、ディレクトリ、デバイス、さらにはネットワーク接続も含まれます。
ファイルディスクリプタの主なポイント:
- 非負の整数で、通常は 0 から始まる(0 は標準入力、1 は標準出力、2 は標準エラー)
- OS カーネル内では、ファイルディスクリプタはファイルテーブルエントリへのインデックスである
- 各プロセスは独自のファイルディスクリプタテーブルを持つ
ソケットとファイルディスクリプタの関係
Unix/Linux システムでは、ソケットも特別なファイルとして扱われるため、対応するファイルディスクリプタが割り当てられます。ソケットを作成すると:
- OS がファイルディスクリプタを割り当てる
- このファイルディスクリプタを使ってネットワーク操作(読み、書き、クローズなど)ができる
- アプリケーションはこのファイルディスクリプタを通じてソケットとやり取りする
TCP 接続確立のプロセス
ソケットの作成
// net パッケージ内部実装
fd, err := socket(family, syscall.SOCK_STREAM, syscall.IPPROTO_TCP)
このステップでは、システムコールによってソケットファイルディスクリプタが作成されます。
サーバー側のバインド(Bind)とリッスン(Listen)
// 簡略化したサーバー側コードフロー
bind(fd, addr)
listen(fd, backlog)
サーバーはソケットを特定のアドレスとポートにバインドし、接続要求のリッスンを開始します。
接続の受け入れ(Accept)
// net/http/server.go の簡略版
func (srv *Server) Serve(l net.Listener) error {
for {
rw, err := l.Accept() // 新しい接続を受け入れる
if err != nil {
// エラー処理
continue
}
go srv.newConn(rw).serve(ctx) // 各接続ごとに新しいgoroutineを作成
}
}
クライアント側の接続(Connect)
// net/http/transport.go の簡略版
func (t *Transport) dialConn(ctx context.Context, addr string) (*conn, error) {
// TCP接続の作成
netConn, err := t.dial(ctx, "tcp", addr)
if err != nil {
return nil, err
}
// HTTP接続としてラップする
return &conn{
conn: netConn,
// ... その他のフィールド
}, nil
}
データ転送:
// データの読み込み
n, err := syscall.Read(fd, buf)
// データの書き込み
n, err := syscall.Write(fd, data)
接続のクローズ:
err := syscall.Close(fd)
主要な実装の詳細
マルチプレクシング(多重化)
- HTTP/1.1 は Keep-Alive メカニズムで TCP 接続を再利用
- HTTP/2 はストリーム(Stream)で多重化を実現し、複数の HTTP リクエストが同じ TCP 接続を共有できる
接続プール管理
// net/http/transport.go
type Transport struct {
// アイドル接続プール
idleConn map[connectMethodKey][]*persistConn
// 最大アイドル接続数
maxIdleConns int
// ... その他のフィールド
}
タイムアウト制御
// 接続タイムアウトを設定
conn.SetDeadline(time.Now().Add(timeout))
エラー処理とリトライ機構
// 簡略化したリトライロジック
for retry := 0; retry < maxRetries; retry++ {
conn, err := dial()
if err == nil {
return conn
}
// 待機して再試行
time.Sleep(backoff)
}
ワークフロー
クライアントが HTTP リクエストを発行する場合:
- まず接続プールに利用可能な接続があるか確認する
- なければ新しい TCP 接続を作成する
- HTTP リクエストデータを送信する
- 応答を待って読み取る
サーバーがリクエストを処理する場合:
- Accept ループで新しい接続を受け入れる
- 各接続ごとに goroutine を作成
- HTTP リクエストをパースする
- リクエストを処理し、応答を返す
これが、net/http パッケージが TCP プロトコルの上に HTTP 接続を実装するコアな仕組みです。抽象化やカプセル化によって、開発者は TCP 接続の低レベルな詳細を直接扱う必要がなくなり、効率的な接続管理や再利用の仕組みを利用できます。
私たちはLeapcell、Goプロジェクトのホスティングの最適解です。
Leapcellは、Webホスティング、非同期タスク、Redis向けの次世代サーバーレスプラットフォームです:
複数言語サポート
- Node.js、Python、Go、Rustで開発できます。
無制限のプロジェクトデプロイ
- 使用量に応じて料金を支払い、リクエストがなければ料金は発生しません。
比類のないコスト効率
- 使用量に応じた支払い、アイドル時間は課金されません。
- 例: $25で6.94Mリクエスト、平均応答時間60ms。
洗練された開発者体験
- 直感的なUIで簡単に設定できます。
- 完全自動化されたCI/CDパイプラインとGitOps統合。
- 実行可能なインサイトのためのリアルタイムのメトリクスとログ。
簡単なスケーラビリティと高パフォーマンス
- 高い同時実行性を容易に処理するためのオートスケーリング。
- ゼロ運用オーバーヘッド — 構築に集中できます。
Xでフォローする:@LeapcellHQ