Socket 用於進程間通信 --- UNIX Domain Socket


Socket 用於進程間通信 --- UNIX Domain Socket

 

 

 

  socket API原本是為網絡通訊設計的,但后來在socket的框架上發展出一種IPC機制,就是UNIX Domain Socket。雖然網絡socket也可用於同一台主機的進程間通訊(通過loopback地址127.0.0.1),但是UNIX Domain Socket用於IPC更有效率:不需要經過網絡協議棧,不需要打包拆包、計算校驗和、維護序號和應答等,只是將應用層數據從一個進程拷貝到另一個進程。這是因為,IPC機制本質上是可靠的通訊,而網絡協議是為不可靠的通訊設計的。UNIX Domain Socket也提供面向流和面向數據包兩種API接口,類似於TCP和UDP,但是面向消息的UNIX Domain Socket也是可靠的,消息既不會丟失也不會順序錯亂。

  UNIX Domain Socket是全雙工的,API接口語義豐富,相比其它IPC機制有明顯的優越性,目前已成為使用最廣泛的IPC機制,比如X Window服務器和GUI程序之間就是通過UNIX Domain Socket通訊的。

  使用UNIX Domain Socket的過程和網絡socket十分相似,也要先調用socket()創建一個socket文件描述符,address family指定為AF_UNIX,type可以選擇SOCK_DGRAM或SOCK_STREAM,protocol參數仍然指定為0即可。

  UNIX Domain Socket與網絡socket編程最明顯的不同在於地址格式不同,用結構體sockaddr_un表示,網絡編程的socket地址是IP地址加端口號,而UNIX Domain Socket的地址是一個socket類型的文件在文件系統中的路徑,這個socket文件由bind()調用創建,如果調用bind()時該文件已存在,則bind()錯誤返回。

  以下程序將UNIX Domain socket綁定到一個地址。

復制代碼
 1 #include <stdlib.h> 
 2 #include <stdio.h>
 3 #include <stddef.h>
 4 #include <sys/socket.h>
 5 #include <sys/un.h>  
 6 
 7 int main(void)  
 8 { 
 9     int fd, size;  
10     struct sockaddr_un un;
11 
12     memset(&un, 0, sizeof(un));  
13     un.sun_family = AF_UNIX;  
14     strcpy(un.sun_path, "foo.socket");  
15     if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) 
16     {  
17         perror("socket error");
18         exit(1);  
19     }  
20     size = offsetof(struct sockaddr_un, sun_path)   +strlen(un.sun_path);
21   
22     if (bind(fd, (struct sockaddr *)&un, size) < 0) 
23     {  
24         perror("bind error");  
25         exit(1); 
26      }  
27 
28     printf("UNIX domain socket bound/n");  
29     exit(0);  
30     }               
復制代碼

 

  注意程序中的offsetof宏,它在stddef.h頭文件中定義:

  #define offsetof(TYPE, MEMBER) ((int)&((TYPE *)0)->MEMBER)      

  offsetof(struct sockaddr_un, sun_path)就是取sockaddr_un結構體的sun_path成員在結構體中的偏移,也就是從結構體的第幾個字節開始是sun_path成員。想一想,這個宏是如何實現這一功能的?(先將TYPE類型的指針首地址設為0,然后取MEMBER成員的地址就是該成員在TYPE中的偏移數。)

該程序的運行結果如下。

$ ./a.out  UNIX domain socket bound  
$ ls -l
foo.socket srwxrwxr-x 1 user 0 Aug 22 12:43 foo.socket
$ ./a.out bind error: Address already in use
$ rm foo.socket
$ ./a.out UNIX domain socket bound

  以下是服務器的listen模塊,與網絡socket編程類似,在bind之后要listen,表示通過bind的地址(也就是socket文件)提供服務。

復制代碼
 1 #include <stddef.h>
 2 #include <sys/socket.h>
 3 #include <sys/un.h>
 4 #include <errno.h>
 5 
 6 #define QLEN 10
 7 
 8 /*
 9 * Create a server endpoint of a connection.
10 * Returns fd if all OK, <0 on error.
11 */
12 
13 int serv_listen(const char *name)
14 {
15     int fd, len, err, rval;
16     struct sockaddr_un  un;
17 
18     /* create a UNIX domain stream socket */
19     if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
20         return(-1);
21     unlink(name);   /* in case it already exists */
22 
23     /* fill in socket address structure */
24     memset(&un, 0, sizeof(un));
25     un.sun_family = AF_UNIX;
26 
27     strcpy(un.sun_path, name);
28     len = offsetof(struct sockaddr_un, sun_path) + strlen(name);
29 
30     /* bind the name to the descriptor */
31     if (bind(fd, (struct sockaddr *)&un, len) < 0)
32     {
33         rval = -2;
34         goto errout;
35     }
36 
37     if (listen(fd, QLEN) < 0)
38     { /* tell kernel we're a server */
39         rval = -3;
40         goto errout;
41     }
42 
43     return(fd);
44 
45 errout:
46     err = errno;
47     close(fd);
48     errno = err;
49     return(rval);
50 }
復制代碼

 

  以下是服務器的accept模塊,通過accept得到客戶端地址也應該是一個socket文件,如果不是socket文件就返回錯誤碼,如果是 socket文件,在建立連接后這個文件就沒有用了,調用unlink把它刪掉,通過傳出參數uidptr返回客戶端程序的user id。

復制代碼
 1 #include <stddef.h>  
 2 #include <sys/stat.h>  
 3 #include <sys/socket.h>  
 4 #include <sys/un.h>  
 5 #include <errno.h>  
 6 
 7 int serv_accept(int listenfd, uid_t *uidptr)  
 8 {  int clifd, len, err, rval;  
 9   time_t staletime;  
10   struct sockaddr_un  un;  
11   struct stat statbuf;
12   len = sizeof(un);
13   if ((clifd = accept(listenfd, (struct sockaddr *)&un, &len)) < 0)  
14     return(-1);     
15 /* often errno=EINTR, if signal caught */  
16 /* obtain the client's uid from its calling address */  
17   len -= offsetof(struct sockaddr_un, sun_path); /* len of pathname */  
18   un.sun_path[len] = 0;           /* null terminate */  
19   if (stat(un.sun_path, &statbuf) < 0) 
20   {  
21     rval = -2;  
22     goto errout;  
23   }  
24   if (S_ISSOCK(statbuf.st_mode) == 0) 
25   {  
26     rval = -3;     
27     /* not a socket */  
28     goto errout;  
29   } 
30   if (uidptr != NULL)  
31     *uidptr = statbuf.st_uid;   /* return uid of caller */  
32   unlink(un.sun_path);        /* we're done with pathname now */  
33   
34   return(clifd);  
35 
36 errout:   
37   err = errno;  
38   close(clifd);  
39   errno = err;  
40   return(rval);  
41 }      
復制代碼

 

  以下是客戶端的connect模塊,與網絡socket編程不同的是,UNIX Domain Socket客戶端一般要顯式調用bind函數,而不依賴系統自動分配的地址。客戶端bind一個自己指定的socket文件名的好處是,該文件名可以包含客戶端的pid以便服務器區分不同的客戶端。

復制代碼
 1 /*
 2  * ss.c
 3  *
 4  *  Created on: 2013-7-29
 5  *      Author: Administrator
 6  */
 7 #include <stdio.h>
 8 #include <stddef.h>
 9 #include <sys/stat.h>
10 #include <sys/socket.h>
11 #include <sys/un.h>
12 #include <errno.h>
13 
14 #define CLI_PATH    "/var/tmp/"
15 
16 /* +5 for pid = 14 chars */
17 /*
18  * Create a client endpoint and connect to a server.
19  * Returns fd if all OK, <0 on error.
20  */
21 int cli_conn(const char *name)
22 {
23     int fd, len, err, rval;
24     struct sockaddr_un un;
25     /* create a UNIX domain stream socket */
26     if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
27         return(-1);  /* fill socket address structure with our address */
28     memset(&un, 0, sizeof(un));
29     un.sun_family = AF_UNIX;
30     sprintf(un.sun_path, "%s%05d", CLI_PATH, getpid());
31     len = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);
32     unlink(un.sun_path);
33     /* in case it already exists */
34     if (bind(fd, (struct sockaddr *)&un, len) < 0)
35     {
36         rval = -2;
37         goto errout;
38     }
39     /* fill socket address structure with server's address */
40     memset(&un, 0, sizeof(un));
41     un.sun_family = AF_UNIX;
42     strcpy(un.sun_path, name);
43     len = offsetof(struct sockaddr_un, sun_path) + strlen(name);
44     if (connect(fd, (struct sockaddr *)&un, len) < 0)
45     {
46         rval = -4;
47         goto errout;
48     }  return(fd);
49     errout:  err = errno;
50     close(fd);
51     errno = err;
52     return(rval);  
53 }
復制代碼

 

  服務器端:

復制代碼
#include <stdio.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <errno.h>
#include <stddef.h>
#include <string.h>

// the max connection number of the server
#define MAX_CONNECTION_NUMBER 5

/* * Create a server endpoint of a connection. * Returns fd if all OK, <0 on error. */
int unix_socket_listen(const char *servername)
{ 
  int fd;
  struct sockaddr_un un; 
  if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
  {
       return(-1); 
  }
  int len, rval; 
  unlink(servername);               /* in case it already exists */ 
  memset(&un, 0, sizeof(un)); 
  un.sun_family = AF_UNIX; 
  strcpy(un.sun_path, servername); 
  len = offsetof(struct sockaddr_un, sun_path) + strlen(servername); 
  /* bind the name to the descriptor */ 
  if (bind(fd, (struct sockaddr *)&un, len) < 0)
  { 
    rval = -2; 
  } 
  else
  {
      if (listen(fd, MAX_CONNECTION_NUMBER) < 0)    
      { 
        rval =  -3; 
      }
      else
      {
        return fd;
      }
  }
  int err;
  err = errno;
  close(fd); 
  errno = err;
  return rval;    
}

int unix_socket_accept(int listenfd, uid_t *uidptr)
{ 
   int clifd, len, rval; 
   time_t staletime; 
   struct sockaddr_un un;
   struct stat statbuf; 
   len = sizeof(un); 
   if ((clifd = accept(listenfd, (struct sockaddr *)&un, &len)) < 0) 
   {
      return(-1);     
   }
 /* obtain the client's uid from its calling address */ 
   len -= offsetof(struct sockaddr_un, sun_path);  /* len of pathname */
   un.sun_path[len] = 0; /* null terminate */ 
   if (stat(un.sun_path, &statbuf) < 0) 
   {
      rval = -2;
   } 
   else
   {
       if (S_ISSOCK(statbuf.st_mode) ) 
       { 
          if (uidptr != NULL) *uidptr = statbuf.st_uid;    /* return uid of caller */ 
          unlink(un.sun_path);       /* we're done with pathname now */ 
          return clifd;         
       } 
       else
       {
          rval = -3;     /* not a socket */ 
       }
    }
   int err;
   err = errno; 
   close(clifd); 
   errno = err;
   return(rval);
 }
 
 void unix_socket_close(int fd)
 {
    close(fd);     
 }

int main(void)
{ 
  int listenfd,connfd; 
  listenfd = unix_socket_listen("foo.sock");
  if(listenfd<0)
  {
     printf("Error[%d] when listening...\n",errno);
     return 0;
  }
  printf("Finished listening...\n",errno);
  uid_t uid;
  connfd = unix_socket_accept(listenfd, &uid);
  unix_socket_close(listenfd);  
  if(connfd<0)
  {
     printf("Error[%d] when accepting...\n",errno);
     return 0;
  }  
   printf("Begin to recv/send...\n");  
  int i,n,size;
  char rvbuf[2048];
  for(i=0;i<2;i++)
  {
//===========接收==============
   size = recv(connfd, rvbuf, 804, 0);   
     if(size>=0)
     {
       // rvbuf[size]='\0';
        printf("Recieved Data[%d]:%c...%c\n",size,rvbuf[0],rvbuf[size-1]);
     }
     if(size==-1)
     {
         printf("Error[%d] when recieving Data:%s.\n",errno,strerror(errno));     
             break;        
     }
/*
 //===========發送==============
     memset(rvbuf, 'c', 2048);
         size = send(connfd, rvbuf, 2048, 0);
     if(size>=0)
     {
        printf("Data[%d] Sended.\n",size);
     }
     if(size==-1)
     {
         printf("Error[%d] when Sending Data.\n",errno);     
             break;        
     }
*/
 sleep(30);
  }
   unix_socket_close(connfd);
   printf("Server exited.\n");    
 }
復制代碼

  客戶端:

復制代碼
  1 #include <stdio.h>
  2 #include <stddef.h>
  3 #include <sys/stat.h>
  4 #include <sys/socket.h>
  5 #include <sys/un.h>
  6 #include <errno.h>
  7 #include <string.h>
  8 
  9 /* Create a client endpoint and connect to a server.   Returns fd if all OK, <0 on error. */
 10 int unix_socket_conn(const char *servername)
 11 { 
 12   int fd; 
 13   if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)    /* create a UNIX domain stream socket */ 
 14   {
 15     return(-1);
 16   }
 17   int len, rval;
 18    struct sockaddr_un un;          
 19   memset(&un, 0, sizeof(un));            /* fill socket address structure with our address */
 20   un.sun_family = AF_UNIX; 
 21   sprintf(un.sun_path, "scktmp%05d", getpid()); 
 22   len = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);
 23   unlink(un.sun_path);               /* in case it already exists */ 
 24   if (bind(fd, (struct sockaddr *)&un, len) < 0)
 25   { 
 26        rval=  -2; 
 27   } 
 28   else
 29   {
 30     /* fill socket address structure with server's address */
 31       memset(&un, 0, sizeof(un)); 
 32       un.sun_family = AF_UNIX; 
 33       strcpy(un.sun_path, servername); 
 34       len = offsetof(struct sockaddr_un, sun_path) + strlen(servername); 
 35       if (connect(fd, (struct sockaddr *)&un, len) < 0) 
 36       {
 37           rval= -4; 
 38       } 
 39       else
 40       {
 41          return (fd);
 42       }
 43   }
 44   int err;
 45   err = errno;
 46   close(fd); 
 47   errno = err;
 48   return rval;      
 49 }
 50  
 51  void unix_socket_close(int fd)
 52  {
 53     close(fd);     
 54  }
 55 
 56 
 57 int main(void)
 58 { 
 59   srand((int)time(0));
 60   int connfd; 
 61   connfd = unix_socket_conn("foo.sock");
 62   if(connfd<0)
 63   {
 64      printf("Error[%d] when connecting...",errno);
 65      return 0;
 66   }
 67    printf("Begin to recv/send...\n");  
 68   int i,n,size;
 69   char rvbuf[4096];
 70   for(i=0;i<10;i++)
 71   {
 72 /*
 73     //=========接收=====================
 74     size = recv(connfd, rvbuf, 800, 0);   //MSG_DONTWAIT
 75      if(size>=0)
 76      {
 77         printf("Recieved Data[%d]:%c...%c\n",size,rvbuf[0],rvbuf[size-1]);
 78      }
 79      if(size==-1)
 80      {
 81          printf("Error[%d] when recieving Data.\n",errno);     
 82              break;        
 83      }
 84          if(size < 800) break;
 85 */
 86     //=========發送======================
 87 memset(rvbuf,'a',2048);
 88          rvbuf[2047]='b';
 89          size = send(connfd, rvbuf, 2048, 0);
 90      if(size>=0)
 91      {
 92         printf("Data[%d] Sended:%c.\n",size,rvbuf[0]);
 93      }
 94      if(size==-1)
 95      {
 96         printf("Error[%d] when Sending Data:%s.\n",errno,strerror(errno));     
 97             break;        
 98      }
 99          sleep(1);
100   }
101    unix_socket_close(connfd);
102    printf("Client exited.\n");    
103  }
復制代碼

 參考鏈接:

http://blog.csdn.net/guxch/article/details/7041052


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM