Linux 內核 tcp_sendmsg() 是 TCP 發送數據的核心入口。本文基於內核源碼(以 3.x/4.x 為例)逐段解讀發送流程、內存管理、阻塞/非阻塞處理、錯誤碼返回路徑,幫助排查「發送卡住、斷連、EAGAIN/EPIPE」等疑難雜症。
MSG_DONTWAIT,等待連接建立(sk_stream_wait_connect),處理 ERESTARTSYS/EINTR/EPIPE/EAGAIN。sk->sk_err、SEND_SHUTDOWN 觸發 EPIPE。skb 剩餘空間(skb_tailroom)。skb(sk_stream_alloc_skb),支持 CHECKSUM_PARTIAL 硬件校驗、SG (Scatter-Gather) 分頁模式。skb_can_coalesce),否則新增 page frag。sk_stream_memory_free / sk_wmem_schedule 檢查發送緩衝區/內存水位。wait_for_sndbuf/wait_for_memory 休眠等待,超時返回錯誤。MSG_MORE、TCP_CORK 決定何時調用 tcp_push/__tcp_push_pending_frames 真正下發網卡。copied);失敗返回負錯誤碼(經 sk_stream_error 轉換)。| 錯誤碼 | 觸發場景 | 應用層表現 | 排查方向 |
|---|---|---|---|
EPIPE (32) | 連接斷開/關閉寫端/對端 RST | send() 返回 -1, errno=EPIPE; SIGPIPE 信號 | 檢查對端是否崩潰/主動關閉、keepalive 設置 |
EAGAIN/EWOULDBLOCK (11) | 非阻塞發送緩衝區滿 | send() 返回 -1, errno=EAGAIN | 應用層需 epoll/poll 等待可寫、或調大 SO_SNDBUF |
ENOMEM (12) | 內核內存不足分配 skb/page 失敗 | 同 EAGAIN,但系統級內存壓力 | /proc/sys/net/ipv4/tcp_wmem、vm.min_free_kbytes |
ECONNRESET (104) | 收到 RST | 連接重置 | 對端異常關閉、防火牆截斷 |
ETIMEDOUT (110) | 重試超時無 ACK | 發送超時 | 網絡丟包、對端不回 ACK、tcp_retries2 |
| 信號中斷休眠 | 內部重試,通常不返應用層 | 信號處理邏輯 |
// 1. 非阻塞檢查
timeo = sock_sndtimeo(sk, flags & MSG_DONTWAIT); // 非阻塞 timeo=0
// 2. 等待連接建立
if ((1 << sk->sk_state) & ~(TCPF_ESTABLISHED | TCPF_CLOSE_WAIT))
if ((err = sk_stream_wait_connect(sk, &timeo)) != 0) goto out_err;
// 3. 發送緩衝區/錯誤檢查
if (sk->sk_err || (sk->sk_shutdown & SEND_SHUTDOWN)) { err = -EPIPE; goto out_err; }
// 4. 循環拷貝 iovec
while (--iovlen >= 0) {
// ... 分配/填充 skb ...
if (!sk_stream_memory_free(sk)) goto wait_for_sndbuf;
if (!sk_wmem_schedule(sk, copy)) goto wait_for_memory;
// ... 拷貝數據 ...
copied += copy;
// ... 推送判斷 ...
}
// 5. 內存等待
wait_for_sndbuf: set_bit(SOCK_NOSPACE, ...);
wait_for_memory:
if (copied) tcp_push(...); // 已有數據先推送
if ((err = sk_stream_wait_memory(sk, &timeo)) != 0) goto do_error;
mss_now = tcp_send_mss(sk, &size_goal, flags); // 重新計算 MSS
SO_SNDBUF、啟用 TCP_NOTSENT_LOWAT、應用層背壓控制。TCP_NODELAY)、TCP_CORK、tcp_autocorking、網卡隊列長度。/proc/net/sockstat、slabinfo 中 skbuff_head_cache、tcp_sendmsg 分配失敗計數。讀懂 tcp_sendmsg,就能從內核視角精準定位 90% 的 TCP 發送異常。