使用udp發(fā)送>=128K的消息會報ENOBUFS的錯誤的解決方法
這是一個困擾我兩天的問題,
Google和Baidu沒有找到解決方法!
此文為記錄這個問題,并給出原因和解決方法。
1、Unix domain socket簡介
unix域協(xié)議并不是一個實際的協(xié)議族,而是在單個主機上執(zhí)行客戶/服務器通信的一種方法,所用API于在不同主機上執(zhí)行客戶/服務器通信所有的API(套接字API,如AF_INET、AF_INET6等類型的API)相同。unix域協(xié)議可以視為是進程之間本地通信IPC的一種。
unix域提供兩類套接口:字節(jié)流套接口(類似TCP)和數(shù)據(jù)報套接口(類似UDP)。使用Unix域套接口的理由有三:
Unix域套接口往往比位于同一主機的TCP套接口快出一倍。Unix域套接口可用于在同一主機上的不同進程之間傳遞描述字。Unix域套接口把客戶的憑證(用戶ID和用戶組ID)提供給服務器,從而實現(xiàn)能夠提供額外的安全檢查措施。
Unix域中用域標識客戶和服務器的協(xié)議地址是普通文件系統(tǒng)中的路徑名(類比:IPv4協(xié)議的地址由一個32位地址和一個16位端口號構成,IPv6協(xié)議的地址由一個128位地址和16位端口號構成。)。
2、問題描述
簡單介紹了Unix域套接口之后,進入主題——描述我碰到的問題。由于unix域套接口用于本機間進程通信比網(wǎng)絡套接口效率高,因為它是不經(jīng)過協(xié)議棧的!在項目中選擇了unix域的數(shù)據(jù)報套接口。在使用過程中碰到了如下,問題:發(fā)送<128K的消息時,客戶、進程可以正常收發(fā)消息;發(fā)送>=128K的消息時,發(fā)送端(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\n",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\n",recvBufSize); int recvSize; memset(&pmapi_addr, 0, sizeof(pmapi_addr)); len = sizeof(pmapi_addr); printf("==============wait for msg from pmapi====================\n"); 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\n", 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\n",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\n\n\n", 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\n",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\n\n\n", tx_buf); } }
3、可能碰到的另外一個問題
如果你沒有設置足夠大的發(fā)送緩沖區(qū)大小,你很有可能碰到EMSGSIZE的錯誤!因為應用程序寫了一個大于套機口發(fā)送緩沖區(qū)大小的數(shù)據(jù)報,內核報EMSGSIZE錯誤。如下圖:
(注意:UDP套接口有發(fā)送緩沖區(qū)的大小,并且可以通過SO_SNDBUF套接口選項修改。不過它僅僅是寫到套接口的UDP數(shù)據(jù)報的大小,因為UDP是不可靠的,它不必保存應用進程的數(shù)據(jù)拷貝,因此無需一個真正的發(fā)送緩沖區(qū)。)上面的代碼已經(jīng)設置了足夠大的發(fā)送緩沖區(qū)大小。
4、我的嘗試
在sendto發(fā)送>=128K大小的消息時,返回ENOBUFS錯誤。
我懷疑是否是sendto()的原因,我改用sendmsg(),未果還是返回這個錯誤。有人說是:“發(fā)送消息太頻繁,間隔太短”。其實項目中發(fā)送消息根本就不頻繁,背著死馬當活馬醫(yī),未果還是返回這個錯誤。嘗試修改/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 asystem 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 bemade to disk of a real memory segment thereby allowing the listener access tosome more real memory.
可以看出一些端倪,這肯定跟內存分配有關!而且限制在分配128K就失??!利用Socket進行進程間的通信,需要經(jīng)過Linux內核:進程1將數(shù)據(jù)寫到內核,進程2從內核讀取數(shù)據(jù)。內核必須申請一個空間來存放數(shù)據(jù)包!實際上,socket發(fā)送數(shù)據(jù)包時,需要從slab中申請一塊cache存放數(shù)據(jù)包。在2.6.21內核中(這就是我們公司服務器的內核版本),slab分配器最大支持的size為128K(詳情可見/proc/slabinfo)。在2.6.31內核中,slab分配器最大支持的size大小為32M。所以2.6.21內核上,發(fā)送大于128K的數(shù)據(jù)包時,Kmalloc()會失敗,并返回no buffer的錯誤。建議:對于本地進程通信,可以使用其它的IPC方式,進行數(shù)據(jù)通信,如shm、pipe等。找出了原因,可以采用以下方式來解決該問題:升級內核,或修改內核的這個限制。改用unix 域udp套接口為unix域tcp套接口(最終我們采用的方式)。改用其它的IPC方式(這個涉及到太多的修改,故我們放棄使用)。附/proc/slabinfo 信息:size-131072即128K的限制!
代碼
size-131072(DMA) 0 0 131072 1 32 : tunables 8 4 0 : slabdata 0 0 0
size-131072 0 0 131072 1 32 : tunables 8 4 0 : slabdata 0 0 0
size-65536(DMA) 0 0 65536 1 16 : tunables 8 4 0 : slabdata 0 0 0
size-65536 0 0 65536 1 16 : tunables 8 4 0 : slabdata 0 0 0
size-32768(DMA) 0 0 32768 1 8 : tunables 8 4 0 : slabdata 0 0 0
size-32768 0 0 32768 1 8 : tunables 8 4 0 : slabdata 0 0 0
size-16384(DMA) 0 0 16384 1 4 : tunables 8 4 0 : slabdata 0 0 0
size-16384 0 0 16384 1 4 : tunables 8 4 0 : slabdata 0 0 0
size-8192(DMA) 0 0 8192 1 2 : tunables 8 4 0 : slabdata 0 0 0
size-8192 0 0 8192 1 2 : tunables 8 4 0 : slabdata 0 0 0
size-4096(DMA) 0 0 4096 1 1 : tunables 24 12 0 : slabdata 0 0 0
size-4096 4 4 4096 1 1 : tunables 24 12 0 : slabdata 4 4 0
size-2048(DMA) 0 0 2048 2 1 : tunables 24 12 0 : slabdata 0 0 0
size-2048 12 14 2048 2 1 : tunables 24 12 0 : slabdata 7 7 0
size-1024(DMA) 0 0 1024 4 1 : tunables 54 27 0 : slabdata 0 0 0
size-1024 11 12 1024 4 1 : tunables 54 27 0 : slabdata 3 3 0
size-512(DMA) 0 0 512 8 1 : tunables 54 27 0 : slabdata 0 0 0
size-512 208 208 512 8 1 : tunables 54 27 0 : slabdata 26 26 0
size-256(DMA) 0 0 256 15 1 : tunables 120 60 0 : slabdata 0 0 0
size-256 75 75 256 15 1 : tunables 120 60 0 : slabdata 5 5 0
size-192(DMA) 0 0 192 20 1 : tunables 120 60 0 : slabdata 0 0 0
size-192 40 40 192 20 1 : tunables 120 60 0 : slabdata 2 2 0
size-128(DMA) 0 0 128 30 1 : tunables 120 60 0 : slabdata 0 0 0
size-128 86 90 128 30 1 : tunables 120 60 0 : slabdata 3 3 0
size-96(DMA) 0 0 96 40 1 : tunables 120 60 0 : slabdata 0 0 0
size-96 388 400 96 40 1 : tunables 120 60 0 : slabdata 10 10 0
size-64(DMA) 0 0 64 59 1 : tunables 120 60 0 : slabdata 0 0 0
size-32(DMA) 0 0 32 113 1 : tunables 120 60 0 : slabdata 0 0 0
size-64 451 472 64 59 1 : tunables 120 60 0 : slabdata 8 8 0
size-32 871 904 32 113 1 : tunables 120 60 0 : slabdata 8 8 0
我在Ubuntu 10.10上測試,不會報ENOBUFS的錯誤。內核版本為:
/proc/slabinfo的信息如下,跟上面的有些差異:
kmalloc的最大限制是8192K,故我們運行上述程序沒有問題!原來都是內核惹得禍阿,害我困惑那么久?。?!baidu和google都沒有找到原因,因此分享此文,以警惕后者。
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持我們。
上一篇:Opencv3.4.0實現(xiàn)視頻中的幀保存為圖片功能
欄 目:C語言
本文標題:使用udp發(fā)送&gt;=128K的消息會報ENOBUFS的錯誤的解決方法
本文地址:http://mengdiqiu.com.cn/a1/Cyuyan/286.html
您可能感興趣的文章
- 04-02func函數(shù)+在C語言 func函數(shù)在c語言中
- 01-10使用OpenGL實現(xiàn)3D立體顯示的程序代碼
- 01-10使用C++實現(xiàn)全排列算法的方法詳解
- 01-10深入Main函數(shù)中的參數(shù)argc,argv的使用詳解
- 01-10C++大數(shù)模板(推薦)
- 01-10淺談C/C++中的static與extern關鍵字的使用詳解
- 01-10基于C語言fflush()函數(shù)的使用詳解
- 01-10linux c 查找使用庫的cflags與libs的方法詳解
- 01-10深入sizeof的使用詳解
- 01-10基于c中使用ftruncate()前需要fflush(),使用后需要rewind()的深入探討


閱讀排行
本欄相關
- 04-02c語言函數(shù)調用后清空內存 c語言調用
- 04-02func函數(shù)+在C語言 func函數(shù)在c語言中
- 04-02c語言的正則匹配函數(shù) c語言正則表達
- 04-02c語言用函數(shù)寫分段 用c語言表示分段
- 04-02c語言中對數(shù)函數(shù)的表達式 c語言中對
- 04-02c語言編寫函數(shù)冒泡排序 c語言冒泡排
- 04-02c語言沒有round函數(shù) round c語言
- 04-02c語言分段函數(shù)怎么求 用c語言求分段
- 04-02C語言中怎么打出三角函數(shù) c語言中怎
- 04-02c語言調用函數(shù)求fibo C語言調用函數(shù)求
隨機閱讀
- 04-02jquery與jsp,用jquery
- 08-05織夢dedecms什么時候用欄目交叉功能?
- 08-05dedecms(織夢)副欄目數(shù)量限制代碼修改
- 08-05DEDE織夢data目錄下的sessions文件夾有什
- 01-10使用C語言求解撲克牌的順子及n個骰子
- 01-10delphi制作wav文件的方法
- 01-10SublimeText編譯C開發(fā)環(huán)境設置
- 01-11Mac OSX 打開原生自帶讀寫NTFS功能(圖文
- 01-11ajax實現(xiàn)頁面的局部加載
- 01-10C#中split用法實例總結