淺談TCP EPIPE錯誤

    仍是以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.cip

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協議的細節真的很是多,這還不包括更復雜的流控、擁塞、重傳等機制,努力學習吧!

相關文章
相關標籤/搜索