tcpの仕様上、接続先がコネクションをcloseしているかはパケットを一度は実際に送るまでわからないよという話

# はじめに
サーバがコネクションをclose後しばらくして、クライアントが`write`しました。関数呼び出しの結果はエラーでしょうか？

上記の正解は「何事もなく、成功する」です。この挙動が理解できている方はこの記事を読む必要はないかもしれません。

自分は「接続先が`close`したソケットに`write`した場合はプロセスが`SIG-PIPE`を受け取るかシグナルをハンドリングしていた場合は`EPIPE`が返ってくる」と思っていました。

それは、Linuxのマニュアルに以下の記載があるためです。

```
  EPIPE  fd is connected to a pipe or socket whose reading end is closed.
          When this happens the writing process will also receive  a  SIG-
          PIPE  signal.  (Thus, the write return value is seen only if the
          program catches, blocks or ignores this signal.)
```

何でエラーにならないのか？
これを理解するのに[Writing to a closed, local TCP socket not failing](https://stackoverflow.com/questions/11436013/writing-to-a-closed-local-tcp-socket-not-failing)に全てが載っているのですが、なかなか読み解くのが難しいのでこちらを解説するかたちで書いていきたいと思います。

なお、C言語のシステムコールを前提に書いていきます。各言語の挙動は若干異なるかもしれませんが、C言語の挙動が理解できればマニュアルを読めばわかると思います。



# TL;DR
* TCPの仕様上、FINパケットが渡ってきても通信相手がソケットをまだ読もうとしている場合もあるし、もうcloseしている可能性もあるのでFINパケットを受け取ったOS側で通信相手がどちらの状態なのか判断できない。
* `write`した場合、前者の場合は自分の方から`close`するまで通常通りパケットを送り続けることが可能だが、後者の場合は通信相手側で意図していないパケットを受け取るかたちになり`RESET`パケットを送ってくる。いずれにしろ最初の`write`時にはエラーにはならない。
* `RESET`パケットを受け取るとOS側でコネクションを放棄しCLOSED状態にするので、そのあと、プログラム側で`read`や`write`をしようとすると、`ECONNRESET(connection reset by peer)`もしくは`EPIPE(broken pipe)`/`SIGPIPE(broken pipeのシグナル)`を受け取る




# 動機
自分はGo言語でアプリのバックエンドシステムを開発しているのでここまでlow levelの挙動を普段意識することが少ないのですが、気になったきっかけを書きます。

以前、アプリからDBにSQLを投げたところ、コネクションがinvalidだというエラーが起きました。この原因自体はとても簡単でサーバ側(DB側)のコネクションを保持するタイムアウト設定がクライアントよりも短かったというだけなのですが、「これってクライアントライブラリ側でソケットにwriteした時点でエラーになるんだからハンドリングしてコネクションプールに保持している他のコネクションをよしなに使ってよ！！」と思ったのでした。

でも、それって本当にできるの？と思って調べてみたのがきっかけです。

# tcpのコネクション解放の流れの復習
tcpのコネクションの解放をおさらいします。ネット上にたくさん説明が存在するので、わざわざ説明するほどでもないですが、軽く触れておきます。
[こちら](https://www.cresc.co.jp/tech/network/NET_TUTORIAL/Section_43.htm)や[こちら](http://www.silex.jp/blog/wireless/2015/11/tcp.html)あたりがまとまっていてサッと眺めるにはいいかもしれません。

## FINによる通常の切断


![tcp_close.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/370237/be95bea8-4ea7-4984-e370-963cbd4334a7.jpeg)

通常のFINによるコネクションの切断の流れです。
ポイントとしては、

* 切断を宣告された（passive close）側は相手からの`FIN`を受け取った後の状態（つまり`close_wait`）でさらにパケットを相手側に送りつけるのはTCPの仕様上合法であるし、`FIN`を送った(active close)側もそれを`read`して読むのも合法
* `FIN`を送った(active close)側は相手から`FIN`を受け取ってもすぐにはコネクションを解放しない。通常数分ほど`TIME_WAIT`になる。これはネットワーク上で遅れていたパケットが到着する可能性を考慮し、同じシーケンス番号、ポート番号などを利用しないようにするため。

## RSTによる強制切断
コネクションの強制切断です。プロトコルでは回復できない誤りが検出されたときに投げられ、通常はアプリケーションから意図しては送らない。TCPバッファ上の未送信データや受信済み/未受理データは消去されるようなので、安全な切断方法ではありません。

# closeとshutdown
`close`の説明は不要でしょうが、`shutdown`は知らない人も多いかもしれません。

```
int shutdown(int s, int how)
引数sにソケット記述子を指定します。引数howに以下のいずれかの定数を指定

SHUT_RD: 今後このソケットでデータを受信しない。今後このソケットで読もうとするとエラーになります
SHUT_WR: 今後このソケットでデータを送信しない。通信相手にはEOFが送られ、今後このソケットに描こうとするとエラーになります
SHUT_RDWR: 今後このソケットで送受信しない。上の療法の効果が得られます。
```

よほど凝った実装をしない限りは普通は何も考えず、`shutdown(s, SHUT_RDWR)` して`close(s)`をすればいいものと自分は理解してます。
なお、`close(s)`だけでもこのソケットで送受信しない効果があります（ソケットを解放するので当然ですが）。では、どのような場合に使うのでしょうか。


参考までに[詳解UNIXプログラミング 第3版](https://www.amazon.co.jp/%E8%A9%B3%E8%A7%A3UNIX%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0-%E7%AC%AC3%E7%89%88-W-Richard-Stevens/dp/4798134880/ref=sr_1_1?adgrpid=58845649888&gclid=EAIaIQobChMIh7qP6_P45wIV16iWCh1uLQhXEAAYASAAEgKncPD_BwE&hvadid=338568393734&hvdev=c&hvlocphy=1009303&hvnetw=g&hvqmt=b&hvrand=10968863284810531734&hvtargid=kwd-387876927487&hydadcr=27266_11561146&jp-ad-ap=0&keywords=%E8%A9%B3%E8%A7%A3unix%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0&qid=1583052963&sr=8-1)p.554を引用します。

> ソケットをcloseできるのに、なぜshutdownが必要なのでしょう？理由はいくつかあります。まず、closeは使用中の最後の参照がクローズされたときにのみネットワークの端点を解放します。ソケットを（例えばdupで）複製している場合、それを参照する最後のファイル記述子をクローズするまではソケットは解放されません。関数shutdownは、ソケットを参照する使用中のファイル記述子の個数に関係なくソケットを非活性にできます。第2に、ソケットのある方向だけを止めると便利なことがあります。例えば、通信相手のプロセスにデータ送出を完了したことを伝えるためにソケットへの書き込みを閉じても、当該ソケットを使って相手プロセスからデータを受信し続けることができます。

**2020/07/28 追記**
Goの[net/http/server.go](https://github.com/golang/go/blob/8696ae82c94f0a7707cbbbdf2cec44e93edf5b23/src/net/http/server.go#L1700)のソースをよんでいてたまたま気付きましたが、興味深い実装がされてました。
リンクのコメントにあるとおり、BSDやWindowsなど一部のOSでは全てのデータを`Read`せずに`Close`した場合、`RST`がクライアント側に飛ぶようです。
RSTを受け取るとクライアント側はデータをロストする可能性が高いため、リクエストデータが大きすぎるなどの理由で全てのデータを`Read`せずにサーバがレスポンスしたい場合、バッファリングされたデータをソケットに全てWriteした後、`shutdown(SHUT_WR)`をcallすることで`FIN`を飛ばしたあと、500msの間Sleepし、`Close`する実装になっているようです。この500msはクライアント側が`FIN`に反応し正常に終了処理することを期待する時間であり、この数字に特に根拠はないとのこと。


# 本題の説明
事前知識を復習できたので、[Writing to a closed, local TCP socket not failing](https://stackoverflow.com/questions/11436013/writing-to-a-closed-local-tcp-socket-not-failing)が理解できると思います。まず、このstackoverflowはどのような質問かというと、

クライアント側でソケットを`close`して、`FIN_WAIT2`/`CLOSE_WAIT`の状態になるまでは想定通りだが、サーバ側（passive close側にあたる）が`write`した際、`SIG-PIPE`シグナルを受け取ることも`EPIPE`で返ることもなかった。これはなぜ？？

という自分と全く同じ疑問を持っていたことになります。これに対して、jxhさんがかなり丁寧に説明してくれてます。`netstat`コマンドの状態やtcpのダンプが以下の状態にあることを確かめてくれてます。

* 質問者のようにクライアント側が`close`した場合
 * `FIN_WAIT2`/`CLOSE_WAIT`の状態で、サーバ側が`write`する
 * `RESET`パケットがクライアントからサーバへとぶ
 * コネクションが強制解放されたので、`netstat`の出力はすぐに消える
* クライアント側が`shutdown(SHUT_WR)`した場合
 * `FIN_WAIT2`/`CLOSE_WAIT`の状態で、サーバ側が`write`する
 * `FIN_WAIT2`/`CLOSE_WAIT`の状態のまま


この一見奇妙な現象はなぜ起きるのかというと、以下のように説明してくれてます。**サーバ側はクライアント側がどのシステムコールを叩いたのかはわからず、わかるのはtcpの状態**だけなので、`write`してクライアント側にパケットが届いてその反応をみて初めて相手がどちらのシステムコールを読んだのか知るのです。この点が今回一番伝えたかった点です！！

> So, the server doesn't know the client will reset the connection until after it tries to send some data to it. The reason for the reset is because the client called close, instead of something else.
The server cannot know for certain what system call the client has actually issued, it can only follow the TCP state. 

じゃあ、closeしたい側が`read`も`write`もする気がないことをシステムコールで伝えるのは不可能なのかというと、`setsockopt`システムコールでソケットの属性を変更してあげることで`RESET`パケットを意図的にとばせば、可能ではあります。

この場合、`write`が`ECONNRESET`ですぐエラーになります。`Connection reset by peer`というエラーメッセージは見かけたことがある方も多いのではないでしょうか。ただ前述のようにパケットのバッファが全てなくなるので安全ではありません。

え、でも待って、、じゃあ、「接続先がcloseしたソケットにwriteした場合はプロセスがSIG-PIPEを受け取るかシグナルをハンドリングしていた場合はEPIPEが返ってくる」という記述は嘘？と思う方もいるかもしれませんが、実は状況次第で正しく、たとえば、単純に(通信相手がではなく)自分が`shutdown(s_sock, SHUT_WR)`したソケットに対して`write`するとちゃんとSIG-PIPEが発生するのこと。


# おまけ
ややこしいので細かいことが気になる方だけ。

上の記述のままだと通信相手が`FIN`パケットを飛ばした時には、`write`時にSIG-PIPEに必ずならないと誤解されそうなので補足です。実は、[ECONNRESET in Send Linux C](https://stackoverflow.com/questions/33053507/econnreset-in-send-linux-c)によると、通信相手が`close`したソケットに対して、二回`write`を呼ぶと２回目でSIG-PIPEになるようです。(当然ですが、１回目は何度も書いているようにエラーにならない)

`ECONNRESET`になるべきでは？というのがstackoverflowの質問ですが、Steffen Ullrichさんによると、以下のように`FIN`の後の`RESET`か否かでエラーが変わるようです。難しいけど、納得はできますね！！

> the client does not have the knowledge. The difference is that in the case of ECONNRESET only the RST is sent by the peer (hard close with data inside socket buffer) while in case of EPIPE first a FIN is sent (normal close). The RST is only sent after the peer received more data. And this difference (RST vs. FIN followed by RST) can be seen at the client side.

簡単にまとめると、

* `RST`の前に`FIN`を受け取っていた場合`EPIPE`/`SIGPIPE`
* `RST`のみ受け取った場合は`ECONNRESET`

で、後者がどのような時におきるかというと、サーバがOSのソケットのバッファを全部readしていない状態でサーバ自身がcloseした場合などでおきます。

# 感想
素人の率直な感想ですが、tcpの仕様としてコネクション解放の過程で通信相手がまだ`read`する可能性があるのかはわかるようにした方が何かと便利だったのではと思ってしまいました。

また、この辺りちゃんと勉強しようとしたら、やはり[UNIXNetwork Programming](
https://scoecomp.files.wordpress.com/2014/02/2003-unix-network-programming-vol-1-3rd-ed.pdf)を読まないといけないんだろうと改めて思いましたが、なかなか読む気には、、

# 追記
続編を以下に書きました
https://qiita.com/behiron/items/6548718cf422e87f7cbe
まさにこの挙動起因によるものなのでリンクを以下に紹介
https://stackoverflow.com/questions/43189375/why-is-golang-http-server-failing-with-broken-pipe-when-response-exceeds-8kb