域套接字sendto errno -11分析

来源:https://www.cnblogs.com/forwards/archive/2023/08/29/17665281.html
-Advertisement-
Play Games

# sendto errno -11代碼分析 errno -11在內核代碼中代表EAGAIN(再試⼀次),域套接字sendto過程中` sendto->sock_sendmsg->unix_dgram_sendmsg`,在`unix_dgram_sendmsg`中有兩處會返回 EAGAIN: 第1處 ...


sendto errno -11代碼分析

errno -11在內核代碼中代表EAGAIN(再試⼀次),域套接字sendto過程中 sendto->sock_sendmsg->unix_dgram_sendmsg,在unix_dgram_sendmsg中有兩處會返回 EAGAIN:
第1處:sock_alloc_send_pskb
第2處:
other!=sk&&unlikely(unix_peer(other)!=sk&&unix_recvq_full_lockless(other))
unix_peer(sk)!=other||unix_dgram_peer_wake_me(sk,other)
當以上兩個條件都滿⾜時也會返回 EAGAIN。
另外需要註意的是unix_dgram_sendmsg中直接通過skb_queue_tail(&other->sk_receive_queue,skb)將數據放⼊了對端的接收隊列中。
image

第1處
sock_alloc_send_pskb函數中當socket發送緩衝區滿時( sk_wmem_alloc_get(sk)>=sk->sk_sndbuf)將返回 EAGAIN。
image
第2處

在 Linux 內核源代碼中,unix_peer(other) != sk 表⽰另⼀個 Unix 域套接字( other )的對端套接字(peer socket)不等於當前套接字( sk )本⾝。
在 Unix 域套接字通信中,每個發起連接的進程(或線程)都必須創建兩個⽂件描述符,⼀個⽤於客⼾端(client),稱為客⼾端套接字(client socket),另⼀個⽤於伺服器端
(server),稱為伺服器套接字(server socket)。這兩個套接字通過 Unix 域⽂件系統中的某個路徑名進⾏連接(bind)。
當對等⽅成功建⽴連接後,兩個套接字中的⼀個將⾃動成為對⽅的對端套接字(peer socket)。這意味著兩個對等⽅都有⼀個指向對⽅套接字的結構體,也就是所謂的“peer
socket”。
因此,unix_peer(other) != sk 表⽰當前套接字( sk )不是另⼀個 Unix 域套接字( other )的對端套接字。如果這個條件成⽴,那麼就不能向 other 套接字發送數據,因為 other 並不是當前套接字的對端套接字,這種情況下發送數據可能會引發錯誤或者產⽣不確定的結果

unix_peer() 函數嘗試返回指向當前 Unix Domain 套接字的對端套接字的指針,如果當前套接字不是連接狀態或者沒有對端套接字則返回空指針。該函數通常⽤於判斷當前
Unix Domain 套接字是否有對端套接字,以決定是否可以進⾏數據發送。

  1. other!=sk, 因為 other=unix_peer_get(sk)(其實就是other=sk->peer) ,該條件意味著 sk->peer!=sk,在域套接字中 sk代表通信的⼀端,sk->peer代表通信的另⼀端,該條件是為了避免迴圈引⽤。本⽂檔中預設 sk是客⼾端,other是服務端。
  2. unlikely(unix_peer(other)!=sk&&unix_recvq_full_lockless(other)),⾸先 unix_peer(other)!=sk意味著 sk->peer->peer!=sk說明 sk客⼾端所指向的服務端發⽣了變化(⽐如在客⼾端發送的過程中⼜有⼀個新的客⼾端與服務端建⽴了連接),其次是 unix_recvq_full_lockless(other)如下⾯代碼所⽰,當條件滿⾜時代表著 other服務端接收隊列深度⼤於sk_max_ack_backlog
af_unix.c:
static inline int unix_recvq_full_lockless(conststructsock*sk)
{
	return skb_queue_len_lockless(&sk->sk_receive_queue)>
		READ_ONCE(sk->sk_max_ack_backlog);
}
  1. unix_peer(sk)!=other||unix_dgram_peer_wake_me(sk,other)unix_peer(sk)!=other⽤於判斷當前 Unix Domain 套接字( sk )是否為另⼀個 Unix Domain 套接字(other )的對端套接字,這⾥只能是other發⽣了變化 ;在 unix_dgram_peer_wake_me中只有other端接收隊列深度⼤於 sk_max_ack_backlog時才會 return 1
af_unix.c:
static inline int unix_recvq_full(conststructsock*sk)
{
	return skb_queue_len(&sk->sk_receive_queue) > sk->sk_max_ack_backlog;
}
static int unix_dgram_peer_wake_me(structsock*sk, structsock*other)
{
	int connected;
	connected = unix_dgram_peer_wake_connect(sk,other);
	if(unix_recvq_full(other))
		return 1;
	if(connected)
		unix_dgram_peer_wake_disconnect(sk,other);
	return 0;
}

總結

域套接字sendto errno -11存在以下可能:

  1. socket發送緩衝區滿(可復現)。
  2. other的對端不是sk(本客⼾端)並且 unix_recvq_full_lockless成⽴
    2.1 sk(本客⼾端)的對端也不是other。
    2.2 other接收隊列深度⼤於 sk_max_ack_backlog(可復現)。

條件2中 unix_recvq_full_lockless,代表other接收隊列深度⼤於 sk_max_ack_backlog,不過這⾥ unix_recvq_full_lockless調⽤的是 skb_queue_len_lockless是不加鎖的,因此這⾥存在不確定性,但⾄少內核得到的信息是other接收隊列深度⼤於 sk_max_ack_backlog

條件2.1和2.2成⽴的前提是條件2先成⽴。
針對條件2.1成⽴的可能性:
1)sk 與 other 建鏈,此時 sk->peer==other,other==sk->peer
2)new_sk(新的客⼾端)與other建鏈,此時 sk->peer==other,other->peer!=sk,other->peer==new_sk,new_sk->peer==other
3)new_sk⾼速發消息到other使 unix_recvq_full_lockless條件滿⾜。
4)sk發消息進⼊unix_dgram_sendmsg內部併到達unlikely(unix_peer(other)!=sk&&unix_recvq_full_lockless(other))之後並且在 unix_peer(sk)!=other|| unix_dgram_peer_wake_me(sk,other)之前的位置時,other端重新初始化了,條件 unix_peer(sk)!=other滿⾜。

在sendto的過程中重新初始化other,⽬前沒有很好的復現⽅法。

當客⼾環境並沒有使⽤ socketpairconnect ,那麼sk->peer和other->peer並沒有相互引⽤,並且 other->peer==NULL。因此other的對端不是sk(本客⼾端)並且sk(本客⼾端)的對端
也不是other,在這種情況下當other接收隊列深度⼤於 sk_max_ack_backlog時,將返回 EAGAIN(error -11)。
事實上,unix_dgram_sendto返回 EAGAIN時,要麼 socket發送緩衝區滿 ,要麼 other接收隊列深度⼤於sk_max_ack_backlog,因為如果第1個if不成⽴(條件2),那麼第2個if也不會成⽴(條件2.1、條件2.2)。
image

調試手段

#sysctl-a|grepunix
net.unix.max_dgram_qlen=512
#sudosysctl-a|grep"net.core.wmem"
net.core.wmem_default=2097152
net.core.wmem_max=2097152

net.unix.max_dgram_qlen 代表緩衝區隊列深度(緩衝區中有多少個數據包)
net.core.wmem_max 代表緩衝區最⼤⼤小(所有數據包的總⻓度)
針對errno -11, 可適當增加
net.unix.max_dgram_qlen 的⼤小。

發送緩衝區溢出判斷

#include<sys/ioctl.h>
#include<linux/sockios.h>
long outq=0;
ioctl(sockfd,SIOCOUTQ,&outq);

errno -11 前後可根據該代碼獲取發送緩衝區的⼤小並與net.core.wmem_max 對⽐。

kprobe 監控socket 緩衝區是否溢出

kprobe監控sock_alloc_send_pskb、unix_dgram_peer_wake_me


     add_kprobe_event 'r:probeE1 sock_alloc_send_pskb $retval'
     set_kprobe_event_filter 'arg1 == 0' probeE1
     add_kprobe_event 'r:probeE2 unix_dgram_peer_wake_me $retval'
     set_kprobe_event_filter 'arg1 == 1' probeE2
     add_kprobe_event 'r:probeE3 unix_dgram_sendmsg $retval'
     set_kprobe_event_filter 'arg1 == 0xfffffff5' probeE3

     enable_trace_event 'sock/sock_rcvqueue_full'
     enable_trace_event 'sock/sock_exceed_buf_limit'

skb_queue_len 和 skb_queue_len_lockless區別

在 Linux 內核中,skb_queue_len 和 skb_queue_len_lockless 都是⽤於獲取 sk_buff 隊列⻓度的函數。這兩個函數的差異在於函數調⽤時是否⽀持鎖機制,具體描述如下:
skb_queue_len() 是⼀個基於鎖的隊列⻓度計算函數,當需要獲取 sk_buff 隊列⻓度時,該函數會獲取隊列的⾃旋鎖來保證隊列在計算期間不發⽣併發修改。因為該函數對隊列
實⾏鎖機制,當需要查詢 sk_buff 隊列⻓度時可能會受到鎖的競爭和等待時延等因素的影響。
skb_queue_len_lockless() 是⼀個不⽀持鎖的隊列⻓度計算函數,該函數可以在不加鎖的情況下,快速獲取 sk_buff 隊列的實際⻓度。由於該函數不需要獲取隊列的鎖,因此其
執⾏速度快,但也可能在併發寫⼊數據時因為⽆法保證數據⼀致性而產⽣錯誤的隊列⻓度計算結果。
根據內核實現,當我們希望檢查隊列的⻓度時,⼀般建議使⽤ skb_queue_len 而不是 skb_queue_len_lockless ,因為前者可以確保計算結果的準確性,並通過⾃旋鎖確保計算
過程不受併發修改的影響。而後者則主要⽤於在不需要確保 sk_buff 隊列準確性的情況下快速計算其⻓度,⽐如有些地⽅例如數據處理中可能觀察者只需要⼀個近似值,⽤
skb_queue_len_lockless() 可以顯著降低系統的開銷。

unix_socketpair

Unix 域套接字提供了⼀種可靠的進程間通信機制,其中 unix_socketpair 是其中的⼀個函數。該函數⽤於創建⼀對相互連接的套接字,這兩個套接字可以⽤於進程間的通信。
具體來說,unix_socketpair 函數創建⼀對套接字(⼜稱為 socket pair),這兩個套接字在創建時已經互相連接。這意味著,對其中⼀個套接字進⾏任何操作都會直接影響另⼀個
套接字。因此,可以使⽤這對套接字進⾏進程間通信,⽐如在兩個進程之間傳遞⽂件描述符、管道、消息等。
兩個套接字在創建時有以下特點:
套接字對是由內核創建的,不依賴於⽂件系統中存在的⽂件。
套接字對是⼀對⼀的,即⼀個套接字只能被⼀個進程所擁有,而另⼀個套接字只能被另⼀個進程所擁有。
套接字對是雙向的,即它們既可以⽤於讀也可以⽤於寫。
套接字對是數據傳輸的最小單位,因此數據交換的時候也是傳輸整塊數據,不能傳輸部分數據。
unix_socketpair 函數的調⽤⽅式如下:
#include <sys/socket.h>
int socketpair(int domain, int type, int protocol, int sv[2]);
其中 domain 參數⽤於指定套接字協議簇,type 參數⽤於指定套接字類型,protocol 參數⽤於指定協議類型。sv 參數是⼀個已經分配好的數組,⽤於返回 socket pair 的描述
符。調⽤成功將返回 0,否則返回 -1。

connect操作

在 Unix 域套接字中,unix_dgram_connect 函數⽤於建⽴連接並指定該連接的⽬標套接字地址。該函數主要⽤於 Datagram 套接字的客⼾端,能夠為該套接字指定⼀個預設
的⽬標地址,以便在後續的發送操作中⽆需再指定⽬標地址。
unix_dgram_connect 函數的具體作⽤如下:
建⽴連接。連接建⽴後,套接字就可以直接向指定的⽬標地址發送數據,不再需要每次都指定⽬標地址。
指定預設⽬標地址。連接建⽴後,可以使⽤ unix_dgram_sendmsg 等函數向預設⽬標地址發送數據。
接收數據。經過 unix_dgram_connect 函數連接的套接字也可以接收從指定的⽬標地址發送過來的數據。
請註意,unix_dgram_connect 函數並不是必須的,如果在套接字操作中指定了⽬標地址,則會⾃動建⽴連接。但是通過 unix_dgram_connect 函數建⽴連接可以使得發送數
據等操作更加⽅便。
unix_dgram_connect 函數的函數原型如下:
#include <sys/socket.h>
int unix_dgram_connect(int sockfd, const struct sockaddr_un* addr, socklen_t addrlen);
其中 sockfd 參數是待連接套接字的⽂件描述符,addr 參數是⽬標套接字地址,addrlen 參數是⽬標套接字地址的⻓度。調⽤成功將返回 0,否則返回 -1。

sendto
sendto時如果other不存在,則會主動通過地址和路徑去尋找other,然後通過 unix_may_send是否可以發送,允許發送的條件是 other->peer==NULL or other->peer==sk
image

本文來自博客園,作者:StepForwards,轉載請註明原文鏈接:https://www.cnblogs.com/forwards/p/17665281.html


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • MySqlConnector有個MySqlBulkCopy批量新增數據方法,不過只能用DataTable,需要把list轉成DataTable代碼如下: MySqlBulkCopy mySqlBulkCopy = new MySqlBulkCopy(conn) { DestinationTableN ...
  • 有個項目需要獲取項目內所有Action,併在凌晨定時任務跑完所有介面檢查是否有介面報錯,寫瞭如下方法: /// <summary> /// 獲取Action註釋 /// </summary> /// <param name="root"></param> /// <param name="metho ...
  • dapper是C#程式員比較喜歡用的輕量級ORM,簡單易學,只是沒有批量新增以及修改(收費版有),寫瞭如下擴展 /// <summary> /// dapper MySQL批量新增修改擴展 /// </summary> public static class DapperExtensions { / ...
  • 在開發某一個需求的時候,領導要求使用RocketMQ(阿裡雲版) 作為消息隊列。生產者主要有WebAPI/MVC/JOB(控制台應用程式),然後消費者採用的是Windows服務。那[西瓜程式猿]來記錄一下如何使用RocketMQ(阿裡雲版),給各位小伙伴作為參考防止踩坑。 ...
  • ##### 常用基本配置項 ```xml net35; net40; net45; net451; net452; net46; net461; net462; net47; net471; net472; net48; netstandard2.0; netstandard2.1; netcore ...
  • ### 前言 在非同步編程中,處理非同步操作之間的數據流轉是一個比較常用的操作。`C#`非同步編程提供了一個強大的工具來解決這個問題,那就是`AsyncLocal`。它是一個線程本地存儲的機制,可以在非同步操作之間傳遞數據。它為我們提供了一種簡單而可靠的方式來共用數據,而不必擔心線程切換或非同步上下文的變化。 ...
  • 本文將分享使用 GitHub Actions 完成對一個.Net Core+Vue 的前後端分離項目 zhontai 的構建,並使用 docker 部署到雲伺服器(阿裡雲),及對docker部署.Net Core+Vue的一些經驗分享。 ...
  • [toc] # Linux運維工程師面試題(4) > 祝各位小伙伴們早日找到自己心儀的工作。 > 持續學習才不會被淘汰。 > 地球不爆炸,我們不放假。 > 機會總是留給有有準備的人的。 > 加油,打工人! ## 1 redis 常用的數據類型 - String:字元串,最基礎的數據類型 - List ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...