10

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

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

这是代码(我没有引用任何错误处理,但已经完成):thread#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之前,这不是问题(我尝试确保在connect()之前调用accept(),结果相同。此外,如果套接字是“路径名”,一切都很好)。

4

4 回答 4

17

当我发布这个问题并重新阅读 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() 中有所不同。

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

于 2012-07-24T23:39:41.770 回答
3

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

希望能帮助到你。

于 2013-10-25T08:36:15.520 回答
0

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

于 2014-12-24T15:12:48.840 回答