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)将数据放⼊了对端的接收队列中。

第1处

sock_alloc_send_pskb函数中当socket发送缓冲区满时( sk_wmem_alloc_get(sk)>=sk->sk_sndbuf)将返回 EAGAIN。



第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)。

调试手段

#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

域套接字sendto errno -11分析的更多相关文章

  1. 通过UNIX域套接字传递描述符和 sendmsg/recvmsg 函数

    在前面我们介绍了UNIX域套接字编程,更重要的一点是UNIX域套接字可以在同一台主机上各进程之间传递文件描述符. 下面先来看两个函数: #include <sys/types.h>  #i ...

  2. UNIX网络编程——通过UNIX域套接字传递描述符和 sendmsg/recvmsg 函数

    在前面我们介绍了UNIX域套接字编程,更重要的一点是UNIX域套接字可以在同一台主机上各进程之间传递文件描述符. 下面先来看两个函数: #include <sys/types.h> #in ...

  3. UNIX网络编程——UNIX域套接字编程和socketpair 函数

    一.UNIX Domain Socket IPC socket API原本是为网络通讯设计的,但后来在socket的框架上发展出一种IPC机制,就是UNIX Domain Socket.虽然网络soc ...

  4. 通过UNIX域套接字传递描述符的应用

      传送文件描述符是高并发网络服务编程的一种常见实现方式.Nebula 高性能通用网络框架即采用了UNIX域套接字传递文件描述符设计和实现.本文详细说明一下传送文件描述符的应用. 1. TCP服务器程 ...

  5. UNIX域套接字编程和socketpair 函数

    一.UNIX Domain Socket IPC socket API原本是为网络通讯设计的,但后来在socket的框架上发展出一种IPC机制,就是UNIX Domain Socket.虽然网络soc ...

  6. UNIX域套接字(unix domain)

    UNIX域套接字用于在同一台机器上运行的进程之间的通信. UNIX域套接字提供流和数据报两种接口. 说明:UNIX域套接字比因特网套接字效率更高.它仅赋值数据:不进行协议处理,如添加或删除网络报头.计 ...

  7. 高级进程间通信之UNIX域套接字

    UNIX域套接字用于在同一台机器上运行的进程之间的通信.虽然因特网域套接字可用于同一目的,但UNIX域套接字的效率更高.UNIX域套接字仅仅复制数据:它们并不执行协议处理,不需要添加或删除网络报头,无 ...

  8. 《网络编程》Unix 域套接字

    概述 Unix 域套接字是一种client和server在单主机上的 IPC 方法.Unix 域套接字不运行协议处理,不须要加入或删除网络报头,无需验证和,不产生顺序号,无需发送确认报文,比因特网域套 ...

  9. unix域套接字UDP网络编程

    unix域套接字UDP网络编程,服务器如下面: #include <stdio.h> #include <stdlib.h> #include <string.h> ...

  10. UNIX 域套接字——UNIX domain socket

    /*********************程序相关信息********************* * 程序编号:015 * 程序编写起始日期:2013.11.30 * 程序编写完成日期:2013.1 ...

随机推荐

  1. Surge多配置文件聚合配置方法

    目录 摘要 1. Surge配置原理 2. Surge托管配置 3. Surge多配置文件聚合配置 (1)找到配置文件 (2)编辑配置文件 参考 摘要 Surge 是一个在 macOS 和iOS 平台 ...

  2. Mac Docker 挂载数据卷失败

    问题描述: docker: Error response from daemon: Mounts denied: The path /srv/docker/bind is not shared fro ...

  3. iptables命令详解

    安装iptables yum install iptables-services 编写允许访问的策略 vim /etc/sysconfig/iptables # sample configuratio ...

  4. gin里获取http请求过来的参数

    https://www.bilibili.com/video/av68769981/?p=2 课程代码: https://www.qfgolang.com/?special=ginkuangjia&a ...

  5. 利用FileReader进行二进制文件传输

    一.读取本地二进制文件,上传(数据库文件为例) 二进制文件读取的时候应当直接读取成字节数组,以免在调试时造成误解.比如数据库文件里面的有些字段是utf8编码,因此,采用utf-8编码读出来也能看到一些 ...

  6. PyQGIS二次开发指南

    当你的数据处理使用的是Python语言,而你的导师又让你开发界面,那么PyQGIS二次开发指南是你必读的圣经.QGIS支持Python语言进行二次开发,你将学会如何使用Qt Designer进行界面设 ...

  7. 微信开发者工具拉取gitlab远程代码报Pull failed原因分析:

    可能出现的原因: 本地主机上没有安装node node下载地址: 1 https://nodejs.org/zh-cn/download/ 没有保存gitlab的用户名和密码

  8. jsonp原理详解——终于弄明白了JSONP

    什么是JSONP? 其实网上关于JSONP的讲解有很多,但却千篇一律,而且云里雾里,对于很多刚接触的人来讲理解起来有些困难,着用自己的方式来阐释一下这个问题,看看是否有帮助. 1.一个众所周知的问题, ...

  9. FFmpeg开发笔记(二十四)Linux环境给FFmpeg集成AV1的编解码器

    ​AV1是一种新兴的免费视频编码标准,它由开放媒体联盟(Alliance for Open Media,简称AOM)于2018年制定,融合了Google VP10.Mozilla Daala以及Cis ...

  10. SpringAi

    Spring AI 初学 Spring AI 官方地址 "spring 不生产 AI,只是 AI 工具的搬运工" 项目可以查看gitee Open AI 前期准备 Open AI官 ...