UNIX Domain Socket使用

壤驷经国
2023-12-01

一、Socket概述

  Socket最初用在基于TCP/IP网络间进程通信中,以客户端/服务器模式进行通信。

实现异步操作,共享资源集中处理,提高客户端响应能力。

Tcp通信基本流程:

  服务器端                                                                       客户端

  1.创建socket                                                             1.创建socket

  2.bind()                                                                         

  3.listen()

  4.accecp()

  ----等待客户端连接----                                                2.connect()

  5.读数据(recv)                                                       3.写数据(send)

  6.写数据(send)                                                      4.读数据(recv)

  7.关闭socket(closesocket())                               5.关闭socket(closesocket())


二、Unix domain socket

    Unix domain socket 或者 IPC socket是一种终端,可以使同一台操作系统上的两个或多个进程进行数据通信。与管道相比,Unix domain sockets 既可以使用字节流,又可以使用数据队列,而管道通信则只能使用字节流。Unix domain sockets的接口和Internet socket很像,但它不使用网络底层协议来通信。Unix domain socket 的功能是POSIX操作系统里的一种组件。

   Unix domain sockets 使用系统文件的地址来作为自己的身份。它可以被系统进程引用。所以两个进程可以同时打开一个Unix domain sockets来进行通信。不过这种通信方式是发生在系统内核里而不会在网络里传播。

   UNIX Domain Socket是在socket架构上发展起来的用于同一台主机的进程间通讯(IPC),它不需要经过网络协议栈,不需要打包拆包、计算校验和、维护序号和应答等,只是将应用层数据从一个进程拷贝到另一个进程。UNIX Domain Socket有SOCK_DGRAM或SOCK_STREAM两种工作模式,类似于UDP和TCP,但是面向消息的UNIX Domain Socket也是可靠的,消息既不会丢失也不会顺序错乱。UNIX Domain Socket可用于两个没有亲缘关系的进程,是全双工的,是目前使用最广泛的IPC机制,比如X Window服务器和GUI程序之间就是通过UNIX Domain Socket通讯的。

     因为应用于IPC,所以UNIXDomain socket不需要IP和端口,取而代之的是文件路径来表示“网络地址”,这点体现在下面两个方面:

  •    1. 地址格式不同,UNIXDomain socket用结构体sockaddr_un表示,是一个socket类型的文件在文件系统中的路径,这个socket文件由bind()调用创建,如果调用bind()时该文件已存在,则bind()错误返回。                                                                                                  
      2. UNIX Domain Socket客户端一般要显式调用bind函数,而不象网络socket一样依赖系统自动分配的地址。客户端bind的socket文件名可以包含客户端的pid,这样服务器就可以区分不同的客户端。

    三、相关API

    int socket(int domain, int type, int protocol)

    domain:说明我们网络程序所在的主机采用的通讯协族(AF_UNIX和AF_INET等). AF_UNIX只能够用于单一的Unix系统进程间通信,而AF_INET是针对               Internet的,因而可以允许在远程主机之间通信
    type:我们网络程序所采用的通讯协议(SOCK_STREAM,SOCK_DGRAM等) SOCK_STREAM表明我们用的是TCP协议,这样会提供按顺序的,可靠,双向,面向连接的       比特流. SOCK_DGRAM 表明我们用的是UDP协议,这样只会提供定长的,不可靠,无连接的通信.
    protocol:由于我们指定了type,所以这个地方我们一般只要用0来代替就可以了
    socket为网络通讯做基本的准备.成功时返回文件描述符,失败时返回-1,看errno可知道出错的详细情况

    int bind( int sockfd , const struct sockaddr * my_addr, socklen_t addrlen)

    sockfd:是由socket调用返回的文件描述符.
    addrlen:是sockaddr结构的长度.
    my_addr:是一个指向sockaddr的指针. 在中有 sockaddr的定义
    struct sockaddr{
    unisgned short as_family;
    char sa_data[14];
    };

    int listen(int sockfd,int backlog)

    sockfd:是bind后的文件描述符.
    backlog:设置请求排队的最大长度.当有多个客户端程序和服务端相连时, 使用这个表示可以介绍的排队长度. listen函数将bind的文件描述符变为监听套接字.返回的情况和bind一样.

    int accept(int sockfd, struct sockaddr *addr,int *addrlen)
    sockfd:是listen后的文件描述符.
    addr,addrlen是用来给客户端的程序填写的,服务器端只要传递指针就可以了. bind,listen和accept是服务器端用的函数,accept调用时,服务器端的程序会一直阻塞到有一个客户程序发出了连接. accept成功时返回最后的服务器端的文件描述符,这个时候服务器端可以向该描述符写信息了. 失败时返回-1

    过程:有人从很远的地方通过一个你在侦听(listen())的端口连接(connect())到你的机器。它的连接将加入到等待接受(accept())的队列中。你调用accept()告诉它你有空闲的连接。它将返回一个新的套接字文件描述符!这样你就有两个套接字了,原来的一个还在侦听你的那个端口,新的在准备发送(send())和接收(recv())数据。这就是Linux Accept函数的过程!

    Ps:Linux Accept函数注意事项,在系统调用send()和recv()中你应该使用新的套接字描述符new_fd。如果你只想让一个连接进来,那么你可以使用close()去关闭原来的文件描述符sockfd来避免同一个端口更多的连接。

    int connect(int sockfd, struct sockaddr * serv_addr,int addrlen)
    sockfd:socket返回的文件描述符.
    serv_addr:储存了服务器端的连接信息.其中sin_add是服务端的地址
    addrlen:serv_addr的长度
    connect函数是客户端用来同服务端连接的.成功时返回0,sockfd是同服务端通讯的文件描述符失败时返回-1

    ssize_t recv(int sockfd, void *buff, size_t nbytes, int flags);

    ssize_t send(int sockfd, const void *buff, size_t nbytes, int flags)

    recv 和send的前3个参数等同于read和write。

    flags参数值为0或:

     
    flags 说明 recv send
     MSG_DONTROUTE 绕过路由表查找      •
     MSG_DONTWAIT 仅本操作非阻塞    •       •
     MSG_OOB     发送或接收带外数据   •   •
     MSG_PEEK   窥看外来消息   •  
     MSG_WAITALL   等待所有数据    •  

     1. send解析

     sockfd:指定发送端套接字描述符。

     buff:    存放要发送数据的缓冲区

     nbytes:  实际要改善的数据的字节数

     flags:   一般设置为0

     1) send先比较发送数据的长度nbytes和套接字sockfd的发送缓冲区的长度,如果nbytes > 套接字sockfd的发送缓冲区的长度, 该函数返回SOCKET_ERROR;

     2) 如果nbtyes <= 套接字sockfd的发送缓冲区的长度,那么send先检查协议是否正在发送sockfd的发送缓冲区中的数据,如果是就等待协议把数据发送完,如果协议还没有开始发送sockfd的发送缓冲区中的数据或者sockfd的发送缓冲区中没有数据,那么send就比较sockfd的发送缓冲区的剩余空间和nbytes

     3) 如果 nbytes > 套接字sockfd的发送缓冲区剩余空间的长度,send就一起等待协议把套接字sockfd的发送缓冲区中的数据发送完

     4) 如果 nbytes < 套接字sockfd的发送缓冲区剩余空间大小,send就仅仅把buf中的数据copy到剩余空间里(注意并不是send把套接字sockfd的发送缓冲区中的数据传到连接的另一端的,而是协议传送的,send仅仅是把buf中的数据copy到套接字sockfd的发送缓冲区的剩余空间里)。

     5) 如果send函数copy成功,就返回实际copy的字节数,如果send在copy数据时出现错误,那么send就返回SOCKET_ERROR; 如果在等待协议传送数据时网络断开,send函数也返回SOCKET_ERROR。

     6) send函数把buff中的数据成功copy到sockfd的改善缓冲区的剩余空间后它就返回了,但是此时这些数据并不一定马上被传到连接的另一端。如果协议在后续的传送过程中出现网络错误的话,那么下一个socket函数就会返回SOCKET_ERROR。(每一个除send的socket函数在执行的最开始总要先等待套接字的发送缓冲区中的数据被协议传递完毕才能继续,如果在等待时出现网络错误那么该socket函数就返回SOCKET_ERROR)

     7) 在unix系统下,如果send在等待协议传送数据时网络断开,调用send的进程会接收到一个SIGPIPE信号,进程对该信号的处理是进程终止。

    2.recv函数

    sockfd: 接收端套接字描述符

    buff:   用来存放recv函数接收到的数据的缓冲区

    nbytes: 指明buff的长度

    flags:   一般置为0

     1) recv先等待s的发送缓冲区的数据被协议传送完毕,如果协议在传送sock的发送缓冲区中的数据时出现网络错误,那么recv函数返回SOCKET_ERROR

     2) 如果套接字sockfd的发送缓冲区中没有数据或者数据被协议成功发送完毕后,recv先检查套接字sockfd的接收缓冲区,如果sockfd的接收缓冲区中没有数据或者协议正在接收数据,那么recv就一起等待,直到把数据接收完毕。当协议把数据接收完毕,recv函数就把s的接收缓冲区中的数据copy到buff中(注意协议接收到的数据可能大于buff的长度,所以在这种情况下要调用几次recv函数才能把sockfd的接收缓冲区中的数据copy完。recv函数仅仅是copy数据,真正的接收数据是协议来完成的)

     3) recv函数返回其实际copy的字节数,如果recv在copy时出错,那么它返回SOCKET_ERROR。如果recv函数在等待协议接收数据时网络中断了,那么它返回0。

     4) 在unix系统下,如果recv函数在等待协议接收数据时网络断开了,那么调用 recv的进程会接收到一个SIGPIPE信号,进程对该信号的默认处理是进程终止。

    ps:读数据的时候需要考虑的是当recv()返回的大小如果等于请求的大小,那么很有可能是缓冲区还有数据未读完,也意味着该次事件还没有处理完,所以还需要再次读取:

    while(rs)
    {
            buflen = recv(activeevents[i].data.fd, buf, sizeof(buf), 0);
            if(buflen < 0)
           {
               // 由于是非阻塞的模式,所以当errno为EAGAIN时,表示当前缓冲区已无数据可读
               // 在这里就当作是该次事件已处理
               if(errno == EAGAIN)
                  break;
               else
              return;
           } else if(buflen == 0)
           {
             // 这里表示对端的socket已正常关闭.
           }
           if(buflen != sizeof(buf))
                   rs = 0;
           else
                   rs = 1;// 需要再次读取
    }

    四、example

    服务端:

    #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, rs;
      char rvbuf[2048];
      for(i=0;i<2;i++)
      {
        //===========接收==============
       do
       {
         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;		
    	 }
    
    	  if(size != sizeof(rvbuf))
                   rs = 1;// 需要再次读取
           else
                   rs = 0;
       }while (rs);
    /*
     //===========发送==============
    	 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");    
     }
    

    客户端:

    #include <stdio.h>
    #include <stddef.h>
    #include <sys/stat.h>
    #include <sys/socket.h>
    #include <sys/un.h>
    #include <errno.h>
    #include <string.h>
    
    /* Create a client endpoint and connect to a server.   Returns fd if all OK, <0 on error. */
    int unix_socket_conn(const char *servername)
    { 
      int fd; 
      if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)    /* create a UNIX domain stream socket */ 
      {
        return(-1);
      }
      int len, rval;
       struct sockaddr_un un;          
      memset(&un, 0, sizeof(un));            /* fill socket address structure with our address */
      un.sun_family = AF_UNIX; 
      sprintf(un.sun_path, "scktmp%05d", getpid()); 
      len = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);
      unlink(un.sun_path);               /* in case it already exists */ 
      if (bind(fd, (struct sockaddr *)&un, len) < 0)
      { 
      	 rval=  -2; 
      } 
      else
      {
    	/* fill socket address structure with server's address */
    	  memset(&un, 0, sizeof(un)); 
    	  un.sun_family = AF_UNIX; 
    	  strcpy(un.sun_path, servername); 
    	  len = offsetof(struct sockaddr_un, sun_path) + strlen(servername); 
    	  if (connect(fd, (struct sockaddr *)&un, len) < 0) 
    	  {
    		  rval= -4; 
    	  } 
    	  else
    	  {
    	     return (fd);
    	  }
      }
      int err;
      err = errno;
      close(fd); 
      errno = err;
      return rval;	  
    }
     
     void unix_socket_close(int fd)
     {
        close(fd);     
     }
    
    
    int main(void)
    { 
      srand((int)time(0));
      int connfd; 
      connfd = unix_socket_conn("foo.sock");
      if(connfd<0)
      {
         printf("Error[%d] when connecting...",errno);
    	 return 0;
      }
       printf("Begin to recv/send...\n");  
      int i,n,size;
      char rvbuf[4096];
      for(i=0;i<10;i++)
      {
    /*
        //=========接收=====================
        size = recv(connfd, rvbuf, 800, 0);   //MSG_DONTWAIT
    	 if(size>=0)
    	 {
    	    printf("Recieved Data[%d]:%c...%c\n",size,rvbuf[0],rvbuf[size-1]);
    	 }
    	 if(size==-1)
    	 {
    	     printf("Error[%d] when recieving Data.\n",errno);	 
                 break;		
    	 }
             if(size < 800) break;
    */
        //=========发送======================
    memset(rvbuf,'a',2048);
             rvbuf[2047]='b';
             size = send(connfd, rvbuf, 2048, 0);
    	 if(size>=0)
    	 {
    		printf("Data[%d] Sended:%c.\n",size,rvbuf[0]);
    	 }
    	 if(size==-1)
    	 {
    	    printf("Error[%d] when Sending Data:%s.\n",errno,strerror(errno));	 
                break;		
    	 }
             sleep(1);
      }
       unix_socket_close(connfd);
       printf("Client exited.\n");    
     }

 类似资料: