我正在尝试使用UNIX套接字进行线程间通信。 该程序仅用于在Linux上运行。 为避免创build套接字文件,我想使用“抽象”套接字,如unix(7)中所述。
但是,我似乎无法连接到这些套接字。 但是,如果我使用“path名”套接字,一切正常。
这里是代码(我没有引用任何error handling,但它已经完成):线程#1:
int log_socket = socket(AF_LOCAL, SOCK_STREAM, 0); struct sockaddr_un logaddr; socklen_t sun_len = sizeof(struct sockaddr_un); logaddr.sun_family = AF_UNIX; logaddr.sun_path[0] = 0; strcpy(logaddr.sun_path+1, "futurama"); bind(log_socket, &logaddr, sun_len); listen(log_socket, 5); accept(log_socket, &logaddr, &sun_len); ... // send - receive
线#2:
struct sockaddr_un tolog; int sock = socket(AF_LOCAL, SOCK_STREAM, 0); tolog.sun_family = AF_UNIX; tolog.sun_path[0] = 0; strcpy(tolog.sun_path+1, "futurama"); connect(sock, (struct sockaddr*)&tolog, sizeof(struct sockaddr_un));
如果我在上面的代码中做的,就是把sun_path改成无领先的\ 0,事情做的很好。
strace输出:
t1: socket(PF_FILE, SOCK_STREAM, 0) = 0 t1: bind(0, {sa_family=AF_FILE, path=@"futurama"}, 110) t1: listen(0, 5) t2: socket(PF_FILE, SOCK_STREAM, 0) = 1 t2: connect(1, {sa_family=AF_FILE, path=@"futurama"}, 110 <unfinished ...> t2: <... connect resumed> ) = -1 ECONNREFUSED (Connection refused) t1: accept(0, <unfinished ...>
我知道连接是在接受之前,这不是一个问题(我试图确保在connect()之前调用accept(),同样的结果。
当我发布这个问题,并重新阅读unix(7)手册页时,这个措辞引起了我的注意:
抽象套接字地址是由sun_path [0]是一个空字节('\ 0')的事实来区分的。 sun_path中的所有剩余字节定义套接字的“名称”
所以,如果我在填写我的名字之前就把太阳光照了下来,事情就开始奏效了。 我觉得这不一定是直截了当的。 此外,正如@davmac和@StoneThrow正确地指出的那样,只需指定足够长度的套接字地址结构来覆盖要作为地址的字节,就可以减少“剩余字节数”。 一种方法是使用SUN_LEN
宏,但是sun_path
的第一个字节必须设置为!0,因为SUN_LEN
使用strlen
。
阐述
如果sun_path [0]是\ 0,那么内核将使用sun_path的剩余部分作为套接字的名称,不管它是否以\ 0结尾,所有余数都是计数的。 在我原来的代码中,我会将第一个字节清零,然后将strcpy()套接字名称放到位置1的sun_path中。无论sun_path在结构被分配的时候是什么样的乱码(特别是可能包含乱码, ,并被包含在套接字结构的长度(作为传递给系统调用),计为套接字的名称,并在bind()和connect()中不同。
恕我直言,strace应该修复它显示抽象套接字名称的方式,并显示所有的sun_path
字节从1到任何提供的结构长度,如果sun_path[0]
是0
在我的情况下,将strncpy()替换为snprintf()并将副本大小增加到UNIX_PATH_MAX解决了问题。
strncpy(server_addr.sun_path, SOCKET_PATH, sizeof(SOCKET_PATH));
snprintf(server_addr.sun_path, UNIX_PATH_MAX, SOCKET_PATH);
希望能帮助到你。
在抽象名字空间中使用套接字的关键是为“绑定”和“连接”命令提供适当的长度。 为了避免在sockaddr_un中的地址末尾设置'\ 0',应该用strncpy或者类似的方法复制。
在Pawel的回答中已经解释过,所以我只是举个例子。
服务器:
int main(int argc, char** argv) { //to remove warning for unused variables. int dummy = argc; dummy = (int)argv; int fdserver = 0; int fdClient = 0; int iErr = 0; int n = 0; socklen_t addr_len = 0; char buff[1024]; char resp[1024]; const char* const pcSocketName = "/tmp/test"; struct sockaddr_un serv_addr; //set the structure with 'x' instead of 0 so that we're able //to see the full socket name by 'cat /proc/net/unix' //you may try playing with addr_len and see the actual name //reported in /proc/net/unix memset(&serv_addr, 'x', sizeof(serv_addr)); serv_addr.sun_family = AF_UNIX; serv_addr.sun_path[0] = '\0'; //sizeof(pcSocketName) returns the size of 'char*' this is why I use strlen strncpy(serv_addr.sun_path+1, pcSocketName, strlen(pcSocketName)); fdserver = socket(PF_UNIX, SOCK_STREAM, 0); if(-1 == fdserver) { printf("socket() failed: [%d][%s]\n", errno, strerror(errno)); return(-1); } iErr = bind(fdserver, (struct sockaddr*)&serv_addr, offsetof(struct sockaddr_un, sun_path) + 1/*\0*/ + strlen(pcSocketName)); if(0 != iErr) { printf("bind() failed: [%d][%s]\n", errno, strerror(errno)); return(-1); } iErr = listen(fdserver, 1); if(0 != iErr) { printf("listen() failed: [%d][%s]\n", errno, strerror(errno)); return(-1); } addr_len = sizeof(pcSocketName); while(1) { fdClient = accept(fdserver, (struct sockaddr*) &serv_addr, &addr_len); if(0 >= fdClient) { printf("accept() failed: [%d][%s]\n", errno, strerror(errno)); return(-1); } memset(resp, 0, sizeof(resp)); memset(buff, 0, sizeof(buff)); n = recv(fdClient, buff, sizeof(buff), 0); if(0 > n) { printf("recv() failed: [%d][%s]\n", errno, strerror(errno)); return(-1); } printf("[client]: %s\n", buff); sprintf(resp, "echo >> %s", buff); n = send(fdClient, resp, sizeof(resp), 0); if(0 > n) { printf("send() failed: [%d][%s]\n", errno, strerror(errno)); return(-1); } printf("[server]: %s\n", resp); } close(fdserver); return(0); }
客户:
int main(int argc, char** argv) { //to remove warning for unused variables. int dummy = argc; dummy = (int)argv; int fdClient = 0; struct sockaddr_un serv_addr; int iErr = 0; const char* const pcSocketName = "/tmp/test"; char buff[1024]; memset(&serv_addr, 0, sizeof(serv_addr)); serv_addr.sun_family = AF_UNIX; serv_addr.sun_path[0] = '\0'; strncpy(serv_addr.sun_path+1, pcSocketName, strlen(pcSocketName)); fdClient = socket(PF_UNIX, SOCK_STREAM, 0); if(-1 == fdClient) { printf("socket() failed: [%d][%s]\n", errno, strerror(errno)); return(-1); } iErr = connect(fdClient, (struct sockaddr*) &serv_addr, offsetof(struct sockaddr_un, sun_path) + 1/*\0*/ + strlen(pcSocketName)); if(0 != iErr) { printf("connect() failed: [%d][%s]\n", errno, strerror(errno)); return(-1); } memset(buff, 0, sizeof(buff)); sprintf(buff, "Hello from client!"); printf("[client]: %s\n", buff); iErr = send(fdClient, buff, sizeof(buff), 0); if(0 > iErr){ printf("write() failed: [%d][%s]\n", errno, strerror(errno)); return(-1); } iErr = recv(fdClient, buff, sizeof(buff), 0); if(0 > iErr){ printf("read() failed: [%d][%s]\n", errno, strerror(errno)); return(-1); } printf("[server]: %s\n", buff); return(0); }
不知道如何定义SOCKET_PATH,但如果它是一个字符串文字,那么sizeof(SOCKET_PATH)将是char *的大小,通常是4或8个字节。