「ソケット」が見えたので買った。積読がすごい勢いで増えてる。最初から全部読む必要もないし、いいかなと思っている。
Goならわかるシステムプログラミング 第2版www.lambdanote.com
GitHub Copilot にも手伝ってもらった。ありがとうありがとう。
第6章 TCPソケットとHTTPの実装
ソケットを繋いでからできることは、書き込み、読み込み、クローズのみでシンプル。ソケットは、アドレスとポート番号がわかればローカルのコンピューター内だけではなく外部のコンピュータとも通信が行える。
HTTP はどういった仕組みで下位のレイヤーを使っているか。それはほとんどの OS で、アプリケーション層からトランスポート層のプロトコルを利用するときの API としてソケットを利用する。TLS はソケットと HTTP の間に入る。通常ブラウザを利用した HTTP 通信では、サーバーの TCP ポート80番に対して、ソケットを使ったプロセス間通信を行う。
ソケット通信の基本構造
基本構成
- サーバー : ソケットを開いて待ち受ける
- クライアント : 開いているソケットに接続し、通信を行う
Go言語の場合
- サーバーが呼ぶのは Listen() メソッド
- クライアントが呼ぶのは Dial() メソッド
Go言語でHTTPサーバーを実装する
Go言語に組み込まれている TCPの機能(net.Conn) だけを使って HTTP 通信を実現してみる。GitHub Copilot にコメント入れてもらった。
listener
は、特定のネットワークインターフェースとポートでの接続を待ち受けるためのソケットを表すnet.Listen("tcp", "localhost: 8888")
は、指定したアドレスとポートで新しいTCPリスナーを作成、サーバーは新しい接続を待ち受ける状態になるlistener.Accept()
は、新しいクライアントからの接続を待ち、接続が確立されるとその接続を表すnet.Conn
オブジェクトを返す。これを通じて、クライアントとの間でデータの送受信が行われる
package main import ( "bufio" "fmt" "io" "net" "net/http" "net/http/httputil" "strings" ) func main() { // TCPネットワーク上のlocalhost:8888でリッスンを開始 listener, err := net.Listen("tcp", "localhost: 8888") if err != nil { panic(err) // エラーが発生した場合はpanicを発生 } fmt.Println("Server is running at localhost: 8888") for { // 新しい接続を受け入れる conn, err := listener.Accept() if err != nil { panic(err) // エラーが発生した場合はpanicを発生 } go func() { // 新しいゴルーチンを起動 fmt.Printf("Accept %v\n", conn.RemoteAddr()) // 接続のリモートアドレスをログに出力 // HTTPリクエストを読み取る request, err := http.ReadRequest( bufio.NewReader(conn)) if err != nil { panic(err) // エラーが発生した場合はpanicを発生 } // リクエストをダンプしてログに出力 dump, err := httputil.DumpRequest(request, true) if err != nil { panic(err) // エラーが発生した場合はpanicを発生 } fmt.Println(string(dump)) // HTTPレスポンスを作成 response := http.Response{ StatusCode: 200, // ステータスコード200(成功) ProtoMajor: 1, // HTTP/1.0を使用 ProtoMinor: 0, // HTTP/1.0を使用 Body: io.NopCloser( strings.NewReader("Hello World\n")), // ボディに"Hello World\n"を含む } response.Write(conn) // レスポンスを接続に書き込む conn.Close() // 接続を閉じる }() } }
サーバーを起動して確認する
% go run server.go Server is running at localhost: 8888 # 以下で curl 実行したら出た Accept 127.0.0.1:64219 GET / HTTP/1.1 Host: localhost:8888 Accept: */* User-Agent: curl/8.1.2 % curl -v localhost:8888 * Trying 127.0.0.1:8888... * Connected to localhost (127.0.0.1) port 8888 (#0) > GET / HTTP/1.1 > Host: localhost:8888 > User-Agent: curl/8.1.2 > Accept: */* > * HTTP 1.0, assume close after body < HTTP/1.0 200 OK < Hello World * Closing connection 0
この処理は新しい接続が来るたびに繰り返されるので、このサーバーは同時に複数の接続を処理することができる。