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. 应用zabbix的实时导出(real-time export)功能

    说明 zabbix作为监控软件,有时也会需要获取历史数据作进一步的分析,通常可以采用3种办法: 通过zabbix API定期获取(通过web) 通过后端数据库定期读取(通过db) 应用实时导出功能配合 ...

  2. RemoteView 替代品和类似软件

    RemoteView 是一款远程控制软件,使您可以通过Internet连接远程访问计算机和移动设备,而不受时间和地点的限制. 您可以快速,安全地实时轻松地控制计算机和移动设备. 您可以使用我们的iOS ...

  3. Linux环境下:程序的链接, 装载和库[动态链接]

    静态链接库在程序编译阶段就完成了链接工作,完成链接后,依赖的库就都打入了可执行文件中,所以文件大小一般会比较大. 而动态库链接库是在程序运行时才被链接的,所以磁盘上只要保留一份副本,因此节约了磁盘空间 ...

  4. 网络安全—SSL安全访问应用

    文章目录 网络拓扑 部署CA服务器颁发证书 开启Web服务 安装IIS服务 修改Web默认网页 申请Web证书 前提准备 申请文件生成 申请web证书 开始安装web证书 客户机访问web默认网站 使 ...

  5. 如何利用 Seaborn 实现高级统计图表

    本文分享自华为云社区<使用 Seaborn 实现高级统计图表从箱线图到多变量关系探索> ,作者:柠檬味拥抱. 在数据科学和数据可视化领域,Seaborn 是一个备受欢迎的 Python 可 ...

  6. CAD 标注镜像

    在代码里用镜像矩阵对标注进行镜像的时候,标注上面的文字也会被镜像掉,我在网上得到一个方法,可以解决这个问题.https://forums.autodesk.com/t5/net/dbtext-and- ...

  7. Vue cli使用Element UI

    当前的测试环境如下: ---- 新版的@vue/cli ---- Vue2.x版本 第一步:安装Element UI npm i element-ui -S 第二步:引入Element UI 在mai ...

  8. 根据raft协议动画总结raft协议的特点

    raft动画地址 1. 1事务提交的时候如果已经被一台follower(A)获取到了,此时leader(L)挂掉,然后其它follower跟A一起选举leader基本上都是A会被选举成功,然后不管1事 ...

  9. Android 13 - Media框架 - 异步消息机制

    关注公众号免费阅读全文,进入音视频开发技术分享群! b7693967-317e-4c46-96d3-d40d9d87e382 由于网上已经有许多优秀的博文讲解了Android的异步消息机制(ALoop ...

  10. Android 12(S) MultiMedia Learning(四)MediaPlayerService

    getMediaPlayerService方法获取到的是media.player服务 IMediaDeathNotifier::getMediaPlayerService() { // ...... ...