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 を付けないように変更するだけであれば、問題なく動作します。

トレース出力:

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 に自分の名前を入力する前に bzero を実行すると、問題が解決し始めました。それは必ずしも簡単なことではないと思いました。さらに、@davmac と @StoneThrow によって正しく指摘されているように、アドレスとして考慮したいバイトをカバーするのに十分な長さのソケットアドレス構造のみを指定することで、これらの「残りのバイト」の数を減らすことができます。これを行う 1 つの方法は、SUN_LENマクロを使用することですが、の最初のバイトをsun_path!0 に設定する必要があります。SUN_LENstrlen

精緻化

sun_path[0] が \0 の場合、カーネルは、\0 で終了するかどうかに関係なく、sun_path の残りの部分全体をソケットの名前として使用するため、その残りのすべてがカウントされます。元のコードでは、最初のバイトをゼロにしてから、sun_path の位置 1 にソケット名を strcpy() します。構造体が割り当てられたときに sun_path にあった意味不明なもの (特に、スタックに割り当てられているため意味不明なものが含まれている可能性が高い) 、ソケット構造体の長さに含まれ (システムコールに渡される)、ソケットの名前としてカウントされ、bind() と connect() で異なりました。

IMHO、strace は、抽象ソケット名を表示する方法を修正し、sun_path1 から提供された構造体の長さまでのすべてのバイトを表示する必要sun_path[0]があります。0

于 2012-07-24T23:39:41.770 に答える
3

抽象名前空間でソケットを機能させるための鍵は、'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);
}
于 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 に答える