zoukankan      html  css  js  c++  java
  • (unix domain socket)使用udp发送>=128K的消息会报ENOBUFS的错误

    一个困扰我两天的问题,

    Google和Baidu没有找到解决方法!

    此文为记录这个问题,并给出原因和解决方法。

    1、Unix domain socket简介

    unix域协议并不是一个实际的协议族,而是在单个主机上执行客户/服务器通信的一种方法,所用API于在不同主机上执行客户/服务器通信所有的API(套接字API,如AF_INET、AF_INET6等类型的API)相同。unix域协议可以视为是进程之间本地通信IPC的一种。

    unix域提供两类套接口:字节流套接口(类似TCP)和数据报套接口(类似UDP)。使用Unix域套接口的理由有三:

    • Unix域套接口往往比位于同一主机的TCP套接口快出一倍。
    • Unix域套接口可用于在同一主机上的不同进程之间传递描述字。
    • Unix域套接口把客户的凭证(用户ID和用户组ID)提供给服务器,从而实现能够提供额外的安全检查措施。

    Unix域中用域标识客户和服务器的协议地址是普通文件系统中的路径名(类比:IPv4协议的地址由一个32位地址和一个16位端口号构成,IPv6协议的地址由一个128位地址和16位端口号构成。)。

    2、问题描述

    简单介绍了Unix域套接口之后,进入主题——描述我碰到的问题。由于unix域套接口用于本机间进程通信比网络套接口效率高,因为它是不经过协议栈的!在项目中选择了unix域的数据报套接口。在使用过程中碰到了如下,问题:发送<128K的消息时,客户、进程可以正常收发消息;发送>=128K的消息时,发送端(sendto)返回ENOBUFS的错误。

    服务器的代码如下:

    复制代码
    服务器端
    #include<stdio.h>
    #include
    <stdlib.h>
    #include
    <sys/types.h>
    #include
    <sys/socket.h>
    #include
    <sys/un.h>
    #include
    <errno.h>

    //define send and recv buf size
    #define BUFSIZE 512*1024

    //define unix domain socket path
    #define pmmanager "/tmp/pmmanager"
    #define pmapi "/tmp/pmapi"

    int main(int argc, char** argv)
    {
    char rx_buf[BUFSIZE];
    int pmmanager_fd, ret;
    socklen_t len;
    struct sockaddr_un pmmanager_addr, pmapi_addr;

    //create pmmanager socket fd
    pmmanager_fd = socket(AF_UNIX, SOCK_DGRAM, 0);
    if(pmmanager_fd ==-1)
    {
    perror(
    "cannot create pmmanager fd.");
    }

    unlink(pmmanager);
    memset(
    &pmmanager_addr, 0, sizeof(pmmanager_addr));
    pmmanager_addr.sun_family
    = AF_UNIX;
    strncpy(pmmanager_addr.sun_path, pmmanager,
    sizeof(pmmanager_addr.sun_path)-1);

    //bind pmmanager_fd to pmmanager_addr
    ret = bind(pmmanager_fd, (struct sockaddr*)&pmmanager_addr, sizeof(pmmanager_addr));
    if(ret ==-1)
    {
    perror(
    "can not bind pmmanager_addr");
    }

    int recvBufSize;
    len
    =sizeof(recvBufSize);
    ret
    = getsockopt(pmmanager_fd, SOL_SOCKET, SO_RCVBUF, &recvBufSize, &len);
    if(ret ==-1)
    {
    perror(
    "getsocket error.");
    }
    printf(
    "Before setsockopt, SO_RCVBUF-%d ",recvBufSize);
    recvBufSize
    =512*1024;
    ret
    = setsockopt(pmmanager_fd, SOL_SOCKET, SO_RCVBUF, &recvBufSize, len);
    if(ret ==-1)
    {
    perror(
    "setsockopt error.");
    }
    ret
    = getsockopt(pmmanager_fd, SOL_SOCKET, SO_RCVBUF, &recvBufSize, &len);
    if(ret ==-1)
    {
    perror(
    "getsocket error.");
    }
    printf(
    "Set recv buf successful, SO_RCVBUF-%d ",recvBufSize);

    int recvSize;
    memset(
    &pmapi_addr, 0, sizeof(pmapi_addr));
    len
    =sizeof(pmapi_addr);
    printf(
    "==============wait for msg from pmapi==================== ");
    for(;;)
    {
    memset(rx_buf,
    0, sizeof(rx_buf));
    recvSize
    = recvfrom(pmmanager_fd, rx_buf, sizeof(rx_buf), 0, (struct sockaddr*)&pmapi_addr, &len);
    if(recvSize ==-1)
    {
    perror(
    "recvfrom error.");
    }
    printf(
    "Recved message from pmapi: %s ", rx_buf);
    }
    }
    复制代码

    客户端的代码如下:

    复制代码
    客户端
    #include<stdio.h>
    #include
    <stdlib.h>
    #include
    <sys/types.h>
    #include
    <sys/socket.h>
    #include
    <sys/un.h>
    #include
    <errno.h>

    //define send and recv buf size
    #define BUFSIZE 250*1024

    //define unix domain socket path
    #define pmmanager "/tmp/pmmanager"
    #define pmapi "/tmp/pmapi"

    int main(int argc, char** argv)
    {
    char tx_buf[BUFSIZE];
    int pmapi_fd, ret;
    socklen_t len;
    struct sockaddr_un pmmanager_addr, pmapi_addr;

    //create pmmanager socket fd
    pmapi_fd = socket(AF_UNIX, SOCK_DGRAM, 0);
    if(pmapi_fd ==-1)
    {
    perror(
    "cannot create pmapi fd.");
    }

    unlink(pmapi);
    //configure pmapi's addr
    memset(&pmapi_addr, 0, sizeof(pmapi_addr));
    pmapi_addr.sun_family
    = AF_UNIX;
    strncpy(pmapi_addr.sun_path, pmapi,
    sizeof(pmapi_addr.sun_path)-1);
    //bind pmapi_fd to pmapi_addr
    ret = bind(pmapi_fd, (struct sockaddr*)&pmapi_addr, sizeof(pmapi_addr));
    if(ret ==-1)
    {
    perror(
    "bind error.");
    }

    int sendBufSize;
    len
    =sizeof(sendBufSize);
    ret
    = getsockopt(pmapi_fd, SOL_SOCKET, SO_SNDBUF, &sendBufSize, &len);
    if(ret ==-1)
    {
    perror(
    "getsocket error.");
    }
    printf(
    "Before setsockopt, SO_SNDBUF-%d ",sendBufSize);
    sendBufSize
    =512*1024;
    ret
    = setsockopt(pmapi_fd, SOL_SOCKET, SO_SNDBUF, &sendBufSize, len);
    if(ret ==-1)
    {
    perror(
    "setsockopt error.");
    }
    ret
    = getsockopt(pmapi_fd, SOL_SOCKET, SO_SNDBUF, &sendBufSize, &len);
    if(ret ==-1)
    {
    perror(
    "getsocket error.");
    }
    printf(
    "Set send buf successful, SO_SNDBUF-%d ", sendBufSize);

    //configure pmmanager's addr
    memset(&pmmanager_addr, 0, sizeof(pmmanager_addr));
    pmmanager_addr.sun_family
    = AF_UNIX;
    strncpy(pmmanager_addr.sun_path, pmmanager,
    sizeof(pmmanager_addr)-1);
    len
    =sizeof(pmmanager_addr);

    int sendSize =0;
    int i;
    for(i=1; i<=4; i++)
    {
    memset(tx_buf,
    '0', sizeof(tx_buf));
    sprintf(tx_buf,
    "send msg %d to pmmanager.", i);
    printf(
    "%s, msg size - %d ",tx_buf, sizeof(tx_buf));
    sendSize
    = sendto(pmapi_fd, tx_buf, sizeof(tx_buf), 0, (struct sockaddr*)&pmmanager_addr, len);
    if(sendSize ==-1)
    {
    perror(
    "sendto error.");
    }
    printf(
    "Send message to pmmanager: %s ", tx_buf);
    }
    }
    复制代码

    3、可能碰到的另外一个问题

    如果你没有设置足够大的发送缓冲区大小,你很有可能碰到EMSGSIZE的错误!因为应用程序写了一个大于套机口发送缓冲区大小的数据报,内核报EMSGSIZE错误。如下图:

    (注意:UDP套接口有发送缓冲区的大小,并且可以通过SO_SNDBUF套接口选项修改。不过它仅仅是写到套接口的UDP数据报的大小,因为UDP是不可靠的,它不必保存应用进程的数据拷贝,因此无需一个真正的发送缓冲区。)上面的代码已经设置了足够大的发送缓冲区大小。

    4、我的尝试

    在sendto发送>=128K大小的消息时,返回ENOBUFS错误。

    • 我怀疑是否是sendto()的原因,我改用sendmsg(),未果还是返回这个错误。
    • 有人说是:“发送消息太频繁,间隔太短”。其实项目中发送消息根本就不频繁,背着死马当活马医,未果还是返回这个错误。
    • 尝试修改/proc/sys/net/core下面的各种相关选项,如
       未果,还是返回这个错误。(其它路径下的相关选项也试了,不行)
    • ?我无从下手了,不知道128K的这个限制在哪?既然“No buffer space available”,我怎样给他空间?

    5、最终原因及解决办法(都是内核惹得祸!!)

    至此,我实在没有办法了,不知道如何解决!但是从错误ENOBUFS的说明:
    ENOBUFS means there is no sufficient memory available and the system(kernel)  can not allocate any more. Application will usually retry the operation when it detects this error from a system call since it indicates there is a transient resource shortage. It is the Operating system that refuses the resource request from the listener. The virtual memory allocation routine of the OS will determine if a swap can be made to disk of a real memory segment thereby allowing the listener access to some more real memory.
    可以看出一些端倪,这肯定跟内存分配有关!而且限制在分配128K就失败!利用Socket进行进程间的通信,需要经过Linux内核:进程1将数据写到内核,进程2从内核读取数据。内核必须申请一个空间来存放数据包!实际上,socket发送数据包时,需要从slab中申请一块cache存放数据包。
    • 在2.6.21内核中(这就是我们公司服务器的内核版本),slab分配器最大支持的size为128K(详情可见/proc/slabinfo)。
    • 在2.6.31内核中,slab分配器最大支持的size大小为32M。
    所以2.6.21内核上,发送大于128K的数据包时,Kmalloc()会失败,并返回no buffer的错误。建议:对于本地进程通信,可以使用其它的IPC方式,进行数据通信,如shm、pipe等。
    找出了原因,可以采用以下方式来解决该问题:
    • 升级内核,或修改内核的这个限制。
    • 改用unix 域udp套接口为unix域tcp套接口(最终我们采用的方式)。
    • 改用其它的IPC方式(这个涉及到太多的修改,故我们放弃使用)。
    附/proc/slabinfo 信息:size-131072即128K的限制!
    复制代码
    代码
    。。。。。。
    size
    -131072(DMA) 00131072132 : tunables 840 : slabdata 000
    size
    -13107200131072132 : tunables 840 : slabdata 000
    size
    -65536(DMA) 0065536116 : tunables 840 : slabdata 000
    size
    -655360065536116 : tunables 840 : slabdata 000
    size
    -32768(DMA) 003276818 : tunables 840 : slabdata 000
    size
    -32768003276818 : tunables 840 : slabdata 000
    size
    -16384(DMA) 001638414 : tunables 840 : slabdata 000
    size
    -16384001638414 : tunables 840 : slabdata 000
    size
    -8192(DMA) 00819212 : tunables 840 : slabdata 000
    size
    -819200819212 : tunables 840 : slabdata 000
    size
    -4096(DMA) 00409611 : tunables 24120 : slabdata 000
    size
    -409644409611 : tunables 24120 : slabdata 440
    size
    -2048(DMA) 00204821 : tunables 24120 : slabdata 000
    size
    -20481214204821 : tunables 24120 : slabdata 770
    size
    -1024(DMA) 00102441 : tunables 54270 : slabdata 000
    size
    -10241112102441 : tunables 54270 : slabdata 330
    size
    -512(DMA) 0051281 : tunables 54270 : slabdata 000
    size
    -51220820851281 : tunables 54270 : slabdata 26260
    size
    -256(DMA) 00256151 : tunables 120600 : slabdata 000
    size
    -2567575256151 : tunables 120600 : slabdata 550
    size
    -192(DMA) 00192201 : tunables 120600 : slabdata 000
    size
    -1924040192201 : tunables 120600 : slabdata 220
    size
    -128(DMA) 00128301 : tunables 120600 : slabdata 000
    size
    -1288690128301 : tunables 120600 : slabdata 330
    size
    -96(DMA) 0096401 : tunables 120600 : slabdata 000
    size
    -9638840096401 : tunables 120600 : slabdata 10100
    size
    -64(DMA) 0064591 : tunables 120600 : slabdata 000
    size
    -32(DMA) 00321131 : tunables 120600 : slabdata 000
    size
    -6445147264591 : tunables 120600 : slabdata 880
    size
    -32871904321131 : tunables 120600 : slabdata 880
    。。。。。。
    复制代码

    我在Ubuntu 10.10上测试,不会报ENOBUFS的错误。内核版本为:

    /proc/slabinfo的信息如下,跟上面的有些差异:
    kmalloc的最大限制是8192K,故我们运行上述程序没有问题!
    原来都是内核惹得祸阿,害我困惑那么久!!!baidu和google都没有找到原因,因此分享此文,以警惕后者。
  • 相关阅读:
    14.[保护模式]TSS任务段
    13.[保护模式]陷阱门
    12.[保护模式]中断门
    11.[保护模式]调用门
    10.[保护模式]长调用与短调用
    9.[保护模式]代码的跨段跳转流程
    8.[保护模式]段权限检查
    7.[保护模式]段描述符DB位
    6.[保护模式]段描述符属性_S位_TYPE域
    5.[保护模式]段描述符属性_P位_G位
  • 原文地址:https://www.cnblogs.com/heyp/p/3410161.html
Copyright © 2011-2022 走看看