【问题标题】:Can not connect to Linux "abstract" unix socket无法连接到 Linux“抽象”unix 套接字
【发布时间】:2012-07-24 23:39:41
【问题描述】:

我正在尝试使用 UNIX 套接字进行线程间通信。该程序仅用于在 Linux 上运行。为了避免创建套接字文件,我想使用“抽象”套接字,如 unix(7) 中所述。

但是,我似乎无法连接到这些套接字。不过,如果我使用“路径名”套接字,一切正常。

这是代码(我没有引用任何错误处理,但已经完成): 线程#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 ...>

我知道连接在接受之前,这不是问题(我尝试确保在连接()之前调用接受(),结果相同。此外,如果套接字是“路径名”,一切都很好)。

【问题讨论】:

  • 同一个进程的线程间通信,一个普通的pipe(2)就够了!如果所有通信进程和/或线程具有相同的父进程,您也可以使用管道!
  • @BasileStarynkevitch 管道在我的情况下不起作用。我需要多个线程来发送信息,并在继续之前接收同步响应。
  • 您可以有多个管道并使用poll 进行多路复用。
  • @BasileStarynkevitch 为此,我必须提前知道要打开多少个最大管道,或者使用锁限制对一个管道的访问。对于这种情况,套接字方法的开销较小。

标签: linux unix-socket


【解决方案1】:

当我发布这个问题并重新阅读 unix(7) 手册页时,这个措辞引起了我的注意:

一个抽象的套接字地址是由事实来区分的 sun_path[0] 是一个空字节('\0')。 所有剩余字节 在 sun_path 中定义套接字的“名称”

所以,如果我在将我的名字填入其中之前对 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() 中有所不同。

恕我直言,如果sun_path[0] 是0,strace 应该修复它显示抽象套接字名称的方式,并显示从 1 到所提供结构长度的所有 sun_path 字节

【讨论】:

  • 那么这如何回答这个问题?
  • @KarolyHorvath 是的,完全正确。可以说不改变行为,而只是定义套接字地址。 ALL sun_path 中的字节很重要,无论它们是什么。
  • @PawelVeselov(在任何情况下传递“不完整”的 sockaddr 结构实际上是非常可移植的,请参阅 man7.org/linux/man-pages/man7/unix.7.html - 在“路径名套接字”下 - addrlen 的推荐值) .
  • (另外,有趣的是,我链接到的页面似乎是您引用的手册页的更新版本。它说“此命名空间中的套接字地址由 sun_path 被地址结构的指定长度覆盖。” - 不是“所有剩余字节”)。
  • @PawelVeselov - 由于这个问题的年代久远,这可能不再有用,但我认为您传递给 bind() 的 addrlen 不正确:该值应该是地址,例如SUN_LEN(logaddr.sun_path)(注意:在使用该宏之前和之后,您需要分别将 logaddr.sun_path[0] 取消设置/设置为NULL)。
【解决方案2】:

使抽象命名空间中的套接字工作的关键是为“绑定”和“连接”命令提供适当的长度。为避免在 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);
}

【讨论】:

    【解决方案3】:

    在我的例子中,将 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);
    

    希望对你有帮助。

    【讨论】:

      【解决方案4】:

      不确定 SOCKET_PATH 是如何定义的,但如果我怀疑它是字符串文字,那么 sizeof(SOCKET_PATH) 将是 char* 的大小,通常为 4 或 8 个字节。

      【讨论】:

      • 不正确。 sizeof("ABCDEF") 为 7(比字节数多 1)。字符串文字的类型是数组,而不是指针。在大多数情况下,数组会转换为指针,但它们并不相同。
      猜你喜欢
      • 2015-08-29
      • 1970-01-01
      • 1970-01-01
      • 2012-07-24
      • 1970-01-01
      • 2015-11-14
      • 1970-01-01
      • 2018-01-03
      • 1970-01-01
      相关资源
      最近更新 更多