我正在尝试使用UNIX套接字进行线程间通信。该程序仅适用于在Linux上运行。为了避免创建套接字文件,我想使用“抽象”套接字,如unix(7)中所述。
但是,我似乎无法连接到这些套接字。但是,如果我使用“pathname”套接字,一切都会正常工作。
这是代码(我没有引用任何错误处理,但已经完成): 线#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(),结果相同。另外,如果套接字是“路径名”,那么一切都很好)。
答案 0 :(得分:14)
当我发布这个问题并重新阅读unix(7)手册页时,这句话引起了我的注意:
抽象套接字地址的区别在于事实 sun_path [0]是一个空字节('\ 0')。 所有剩余字节 在sun_path中定义套接字的“名称”
所以,如果我在填写我的名字之前先填写了sun_path,那么事情就开始起作用了。我认为这不一定是直截了当的。另外,正如@davmac和@StoneThrow正确指出的那样,通过仅指定套接字地址结构的足够长度来覆盖您想要考虑作为地址的字节,可以减少那些“剩余字节”的数量。一种方法是使用SUN_LEN
宏,但sun_path
的第一个字节必须设置为!0,因为SUN_LEN
使用strlen
。< / p>
阐述
如果sun_path [0]为\ 0,则内核使用sun_path的全部余数作为套接字的名称,无论它是否为0终止,因此所有剩余的都计数。在我的原始代码中,我将第一个字节归零,然后将套接字名称strcpy()放入位置1的sun_path。当分配结构时,sun_path中的乱码(特别是可能包含乱码,因为它已在堆栈上分配) ,并且包含在套接字结构的长度中(传递给syscalls),计为套接字的名称,并且在bind()和connect()中有所不同。
恕我直言,如果sun_path
为sun_path[0]
< / p>
答案 1 :(得分:2)
在抽象命名空间中使套接字工作的关键是为'bind'和'connect'命令提供适当的长度。为避免在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);
}
答案 2 :(得分:1)
就我而言,将 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);
希望它有所帮助。
答案 3 :(得分:0)
不确定如何定义SOCKET_PATH,但如果它是我怀疑的字符串文字,则sizeof(SOCKET_PATH)将是char *的大小,通常为4或8字节。