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\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、可能碰到的另外一個問題
如果你沒有設置足夠大的發送緩衝區大小,你很有可能碰到EMSGSIZE的錯誤!因爲應用程序寫了一個大於套機口發送緩衝區大小的數據報,內核報EMSGSIZE錯誤。如下圖:
(注意:UDP套接口有發送緩衝區的大小,並且可以通過SO_SNDBUF套接口選項修改。不過它僅僅是寫到套接口的UDP數據報的大小,因爲UDP是不可靠的,它不必保存應用進程的數據拷貝,因此無需一個真正的發送緩衝區。)上面的代碼已經設置了足夠大的發送緩衝區大小。
4、我的嘗試
在sendto發送>=128K大小的消息時,返回ENOBUFS錯誤。
- 我懷疑是否是sendto()的原因,我改用sendmsg(),未果還是返回這個錯誤。
- 有人說是:“發送消息太頻繁,間隔太短”。其實項目中發送消息根本就不頻繁,揹着死馬當活馬醫,未果還是返回這個錯誤。
-
嘗試修改/proc/sys/net/core下面的各種相關選項,如
未果,還是返回這個錯誤。(其它路徑下的相關選項也試了,不行) - ?我無從下手了,不知道128K的這個限制在哪?既然“No buffer space available”,我怎樣給他空間?
5、最終原因及解決辦法(都是內核惹得禍!!)
- 在2.6.21內核中(這就是我們公司服務器的內核版本),slab分配器最大支持的size爲128K(詳情可見/proc/slabinfo)。
- 在2.6.31內核中,slab分配器最大支持的size大小爲32M。
- 升級內核,或修改內核的這個限制。
- 改用unix 域udp套接口爲unix域tcp套接口(最終我們採用的方式)。
- 改用其它的IPC方式(這個涉及到太多的修改,故我們放棄使用)。
。。。。。。
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的錯誤。內核版本爲: