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. 程序是怎样跑起来的_第一章-对程序员来说CPU是什么

    通过对第一章的学习,我了解了大体上CPU可以说是电脑的"大脑",即中央处理器.从功能来看可以分为寄存器,控制器,运算器和时钟.在这四个部分中,寄存器是最值得程序员注意的.总的来说, ...

  2. Mac远程控制工具有哪些

    适用于Mac的远程控制工具有很多,这里我们给大家列举五个常用软件. 1.Apple Remote Desktop 苹果自带远程桌面正如其名称所承诺的那样.作为 Apple 出品的应用程序,您可以想象它 ...

  3. SpringCloud解决feign调用token丢失问题

    背景讨论 feign请求 在微服务环境中,完成一个http请求,经常需要调用其他好几个服务才可以完成其功能,这种情况非常普遍,无法避免.那么就需要服务之间的通过feignClient发起请求,获取需要 ...

  4. 『手撕Vue-CLI』拉取模板名称

    前言 好,经过上篇文章的介绍,已经可以有处理不同指令的能力了,接下来我们就来处理 vue create 指令,这个指令的本质就是从网络上下载提前准备好的模板,然后再自动安装模板中相关依赖. 所以实现 ...

  5. 【进阶篇】使用 Stream 流对比两个集合的常用操作分享

    目录 前言 一.集合的比较 1.1需要得到一个新的流 1.2只需要一个简单 boolean 结果 二.简单集合的对比 2.1整型元素集合 2.2字符串元素集合 2.3其它比较 三.Stream 基础回 ...

  6. PHP 中使用 ElasticSearch 的最佳实践 (中)

    引言 在上一篇文章当中,我们介绍了如何在 ElasticSearch 中创建索引以及建立字段映射关系. 接下来的这篇文章,我们将在 Laravel 中对商品信息进行增删改查及搜索. 记得 Elasti ...

  7. Vue cli路由

    上面是将Forecast组件作为了Home的子组件使用,现在我们将其作为一个路由组件使用. 在router/index.js路由系统注册路由: { path: '/forecast', name: ' ...

  8. kubernetes ingress网站发布

    ingress网站发布 单域名 # 1.创建nginx pod 名称: nginx-nodeport.yaml cat nginx-nodeport.yaml apiVersion: v1 kind: ...

  9. css 跑马灯

    html: <view class="in_scro"> <view class="in_scrview">恭喜139******1用户 ...

  10. Centos安装Redis(极速安装)

    下载 从官网找到下载文件,我下载的是redis-6.0.16.tar.gz. 安装 1. 解压文件 解压文件然后,进入解压文件夹: tar -zxvf redis-6.0.16.tar.gz cd r ...