仍是以C/S模型爲例吧。less
TCP的三次握手流程是由內核協議棧完成的,也就是說,即便Server端一直不accept也不會影響Client端發送數據(接收緩衝區填滿除外),甚至在Client端調用close使Server端鏈接變成CLOSE_WAIT狀態後依然能夠成功accept並收取數據,但這時若是發送數據的話會在Client端觸發RST(指Client端的FIN_WAIT_2狀態超時後鏈接已經銷燬的狀況),致使send操做返回EPIPE(errno 32)錯誤,並觸發SIGPIPE信號(默認行爲是Terminate)。socket
EPIPE是send函數可能返回的錯誤碼之一,man中是這樣描述的:tcp
EPIPE The local end has been shut down on a connection oriented socket. In this case the process will also receive a SIGPIPE unless MSG_NOSIGNAL is set.函數
而man中對SIGPIPE的描述爲:學習
SIGPIPE 13 Term Broken pipe: write to pipe with no readersthis
可見,這個錯誤是在發送方會遇到的,翻一翻協議棧中的代碼,能夠找到3處相關內容:spa
Kernel 3.16.1:code
1. net/ipv4/tcp_input.c:隊列
/* When we get a reset we do this. */ void tcp_reset(struct sock *sk) { /* We want the right error as BSD sees it (and indeed as we do). */ switch (sk->sk_state) { case TCP_SYN_SENT: sk->sk_err = ECONNREFUSED; break; case TCP_CLOSE_WAIT:/*在CLOSE_WAIT狀態下收到RST*/ sk->sk_err = EPIPE; break; case TCP_CLOSE: return; default: sk->sk_err = ECONNRESET; } /* This barrier is coupled with smp_rmb() in tcp_poll() */ smp_wmb(); if (!sock_flag(sk, SOCK_DEAD)) sk->sk_error_report(sk); tcp_done(sk); }
2. net/ipv4/tcp.c:ip
static ssize_t do_tcp_sendpages(struct sock *sk, struct page *page, int offset, size_t size, int flags) { ... ... err = -EPIPE; if (sk->sk_err || (sk->sk_shutdown & SEND_SHUTDOWN)) goto out_err; ... ... out_err: return sk_stream_error(sk, flags, err); }
3. net/ipv4/tcp.c:
int tcp_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg, size_t size) { ... ... err = -EPIPE; if (sk->sk_err || (sk->sk_shutdown & SEND_SHUTDOWN)) goto out_err; ... ... out_err: err = sk_stream_error(sk, flags, err); release_sock(sk); return err; }
int sk_stream_error(struct sock *sk, int flags, int err) { if (err == -EPIPE) err = sock_error(sk) ? : -EPIPE; if (err == -EPIPE && !(flags & MSG_NOSIGNAL)) send_sig(SIGPIPE, current, 0); return err; } EXPORT_SYMBOL(sk_stream_error);
總結一下,應用程序會碰到EPIPE錯誤的場景有:
1)在CLOSE_WAIT狀態的鏈接上發送數據(對端已經關閉了鏈接),觸發對端的RST;
2)在本端socket上已經調用過shutdown(SEND_SHUTDOWN);
有的時候Server端因爲種種緣由(一般是異常),沒有及時從積壓隊列中取出鏈接(accept),Client端等的不耐煩了就會先行關閉鏈接,等Server端騰出手來處理這個鏈接的時候已經進入CLOSE_WAIT狀態了。
TCP協議的細節真的很是多,這還不包括更復雜的流控、擁塞、重傳等機制,努力學習吧!