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  
 2 #include 
 3 #include 
 4 #include 
 5 #include   
 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     }               
bind

 

  注意程序中的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 
 2 #include 
 3 #include 
 4 #include 
 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 }
listen

 

  以下是服务器的accept模块,通过accept得到客户端地址也应该是一个socket文件,如果不是socket文件就返回错误码,如果是 socket文件,在建立连接后这个文件就没有用了,调用unlink把它删掉,通过传出参数uidptr返回客户端程序的user id。

 1 #include   
 2 #include   
 3 #include   
 4 #include   
 5 #include   
 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 }      
accept

 

  以下是客户端的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 
 8 #include 
 9 #include 
10 #include 
11 #include 
12 #include 
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 }
connect

 

  服务器端:

#include 
#include 
#include 
#include 
#include 
#include 
#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");    
 }
server

  客户端:

  1 #include 
  2 #include 
  3 #include 
  4 #include 
  5 #include 
  6 #include 
  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  }
Client

 参考链接:

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

 

 

 

 

 

 

 

 

 

转载于:https://www.cnblogs.com/chekliang/p/3222950.html

你可能感兴趣的:(Socket 用于进程间通信 --- UNIX Domain Socket)