Golangで作成したWEBサーバーのTCP通信(HTTP GET)をWiresharkで検証
ソースコード
サーバー側のソースコードです8888ポートでクライアントからの送信を待ち受けます
package main import ( "bufio" "fmt" "io/ioutil" "net" "net/http" "net/http/httputil" "strings" ) func main() { listener, err := net.Listen("tcp", "localhost:8888") if err != nil { panic(err) } fmt.Println("Server is running at localhost:8888") for { conn, err := listener.Accept() if err != nil { panic(err) } go func() { //fmt.Printf("Accept %v\n",conn.RemoteAddr()) //リクエストを読みこむ request, err := http.ReadRequest( bufio.NewReader(conn)) if err != nil { panic(err) } dump, err := httputil.DumpRequest(request, true) if err != nil { panic(err) } fmt.Println(string(dump)) //レスポンスを書き込む response := http.Response{ StatusCode: 200, ProtoMajor: 1, ProtoMinor: 0, Body: ioutil.NopCloser( strings.NewReader("hello world\n")), } response.Write(conn) conn.Close() }() } }
クライアントの方は後述のcurlを利用します
通信方法
サーバーの方で8888のポートで受信を受け付けているのでこちらにcurlで通信を試みています
$ curl localhost:8888 hello world
サーバーの方でリクエストを受け取るとhello world
という文字列を返すようにしているのでコマンドを実行するとこちらの文字列が帰ります
localhostに対する送受信をキャプチャする準備
こちらの記事を参考にLoopback:Io0を選択しています
netstatコマンドを利用したTCPコネクションの状態
以下のタイミングでnetstatコマンドを実行してコネクションの状態を確認しました
サーバー起動時
サーバーは起動したタイミングではnetstat
コマンドの出力に8888portの情報は含まれていません。
listner.Accept()メソッドに置いたブレイクポイントで処理を止めたタイミング
tcp4 78 0 localhost.ddi-tcp-1 localhost.53315 ESTABLISHED tcp4 0 0 localhost.53315 localhost.ddi-tcp-1 ESTABLISHED
クライアント側のポート番号として53315が選ばれていることがわかります
なおこの状態でパケットをキャプチャすると以下の情報が得られました。 サーバーがGETを受け取ってOKを返す直前という感じです。
接続終了時
netstatコマンドにport8888の出力は含まれませんでした
次の記事でHTTP/1.1のKeep-Aliveを検証するのでこのnetstatの情報の差分は興味深い結果になると思います
キャプチャ結果
Wiresharkを起動しフィルタをtcp.port==8888
に設定して通信内容を記録しました。すると下のような通信内容が得られました。
解説
- 色枠1のところでTCPの3way HandShakeをしています
- 色枠2のところでデータを送受信しています
- 色枠3のところで切断処理をしています
3Way HandShake
TCPの接続開始を示すこの3つのパケットのやりとりを3 Way HandShakeと言います。
データ送受信
- データ送信直後に、受信側が送信するパケットのACKがデータを受け取った分だけ増えていることが確認できます。
- データ受信確認の後、送信側から送ったもので受信が確認できたオクテット数だけSEQが増えていることが確認できます
切断
サーバーから送ったFINのパケットに対してクライアントもFINのパケットを送り返しています。 こちらは確認応答のたびにACKを+1しています
感想
キャプチャ結果冒頭*1の[SYN]パケットを送った後の[RST,ACK]が送り返されて再度SYNパケットを送信している理由がわかりませんでした。 一般的な挙動には見えないのですが。。。これはGoのListnerの実装などを読めばわかるのでしょうか。次回はKeep-Aliveを実験してみたいと思います
参考
*1:①の上に位置する2つのパケット