UNIX本地套接字:IPC

版权声明:guojawee https://blog.csdn.net/weixin_36750623/article/details/83582409

UNIX Domain Socket的特点

1.UNIX Domain Socket为什么比TCP/IP在本地上通信更加快
因为UNIX Domain Socket不经过网络协议栈 / 不需要打包拆包 / 计算校验和 / 维护序号和应答,只是将应用层数据从一个进程拷贝到另一个进程
2.UNIX Domain Socket也提供面向流和面向数据包的两种API接口,类似于TCP和UDP,但是面向消息的 UNIX Domain Socket也是可靠的,消息既不会丢失也不会失序。
3.全双工
4.目前UNIX Domain Socket已经成为最广泛的IPC机制

Unix 本地套接字 API

1.socket创建套接字

mysocket = socket(int socket_family, int socket_type, int protocol)

网络socket UNIX Domain Socket
address family AF_INET或PF_INET AF_UNIX
地址格式不同 sockaddr_in(IP+Port) sockaddr_un(本地文件)
type SOCK_STREAM、SOCK_DGRAM 和 SOCK_RAW SOCK_STREAM、SOCK_DGRAM 和 SOCK_RAW
Protocol 0 0

2.绑定地址bind

int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);
my_addr参数

  • struct sockaddr*
  • struct sockaddr_in*
  • struct sockaddr_un*

对于 Unix 本地套接字来说,绑定的地址就不是原来的“IP地址 + 端口号”了,而是一个有效的路径。

struct sockaddr_un {
    sa_family_t     sun_family;     /* AF_UNIX ,2字节*/
    char    sun_path[UNIX_PATH_MAX];        /* 路径名 */
};

当不再需要这个 Unix 域套接字时,应删除路径名对应的文件

	int unlink(const char *pathname);
	int remove(const char *pathname);
注意: 如果是抽象路径名,就不需要在使用完本地套接字后手动
     删除对应的套接字文件,因为当本地套接字被关闭之后,内核会自动删除这个抽象名。

文件名:本地socket进程通信的关联文件命名方式有两种

  • 具体文件名
    服务器绑定(bind) socket会根据此命名创建一个同名的socket文件,客户端连接的时候通过读取该socket文件连接到socket服务端。
    这种方式的弊端是服务端必须对socket文件的路径具备写权限,客户端必须知道socket文件路径,且必须对该路径有读权限。

缺点:这个文件很容易被其他程序不经意中删除,这导致很奇怪的问题,而很难发现

server_addr.sun_family = AF_UNIX;  
strncpy(server_addr.sun_path,UNIX_DOMAIN,sizeof(server_addr.sun_path)-1);  
server_len = sizeof(struct sockaddr_un);  
  • 抽象命名空间
      这种方式不需要创建socket文件,只需要命名一个全局名字,即可让客户端根据此名字进行连接。
      后者的实现过程与前者的差别是,后者在对地址结构成员sun_path字符数组赋值的时候,必须把第一个字节置0(因第二种方式会对首字节置0,可以在命名字符串SERVER_NAME前添加一个占位符@),即sun_path[0] = 0,代码如:#define SERVER_NAME @"/tmp/socket_server"
#define SERVER_NAME @"/tmp/socket_server"   

server_addr.sun_family = AF_UNIX;  
strcpy(server_addr.sun_path, SERVER_NAME);  
server_addr.sun_path[0]=0;  
server_len = strlen(SERVER_NAME)  + offsetof(struct sockaddr_un, sun_path);

offsetof函数详解

引出offsetof函数的原因:因为struct sockaddr_un结构体的成员sun_path存放文件路径,该路径通常很短,占据不了sizeof(sun_path)的长度,因此

size_t offsetof(type, member); //表示member元素在type的偏移位置
1.具体文件名的长度 =

offsetof(struct sockaddr_un,sun_path) + strlen(cli_addr.sun_path);

2.抽象文件名的长度 =

offsetof(struct sockaddr_un,sun_path) + 1 + strlen(cli_addr.sun_path+1);
//用抽象命名地址,那么sun_path[0]必须为’\0’

当接收(accept,recvfrom等)数据时,Linux也不会在sun_path后面附上一个'\0',所以你必须根据返回的长度谨慎地处理字符串。

3.其他API

其他的一些 API,比如 listen()、accept()、connect(),以及数据通信用的 read()、write()、recv()、send()、recvfrom()、sendto()、recvmsg()、sendmsg(),用法跟网络 socket 基本一样,主要是地址结构体需要注意一下



C/S模型代码

基于SOCK_STREAM式套接字

client

UNIX socket的client 与 网络socket的client不同,它需要两个与socket相关的操作,即

  1. 给自己绑定一个本地unix socket:cli_addr
  2. connect的参数,用于连接服务器的unix socket:svr_addr
#include <sys/types.h>  
#include <sys/socket.h>  
#include <sys/un.h>  
#include <unistd.h>  
#include <stdlib.h>  
#include <stdio.h>  
#include <string.h>

#include <stddef.h>

#define UNIX_DOMAIN_CLI "/tmp/UNIX.CLI"  
#define UNIX_DOMAIN_SVR "/tmp/UNIX.SVR"  

int main()  
{  
  unlink(UNIX_DOMAIN_CLI); 
  //unlink(UNIX_DOMAIN_SVR);
  //创建本地socket
  int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);  
  
  //初始化本地unix socket
  struct sockaddr_un cli_addr;  
  bzero(&cli_addr,sizeof(cli_addr));
  cli_addr.sun_family = AF_UNIX;  
  memcpy(cli_addr.sun_path, UNIX_DOMAIN_CLI, sizeof(cli_addr.sun_path));  

  //bind
  socklen_t len = offsetof(struct sockaddr_un,sun_path) + strlen(cli_addr.sun_path);
  bind(sockfd,(struct sockaddr*)&cli_addr,len);
  printf("len = %d\n",len);
  
  //初始化对方unix socket
  struct sockaddr_un svr_addr;
  bzero(&svr_addr,sizeof(svr_addr));
  svr_addr.sun_family = AF_UNIX;
  memcpy(svr_addr.sun_path, UNIX_DOMAIN_SVR, sizeof(svr_addr.sun_path));

  len = offsetof(struct sockaddr_un,sun_path) + strlen(svr_addr.sun_path);
  printf("len = %d\n",len);
 
  //connect
  int result = connect(sockfd, (struct sockaddr *)&svr_addr, len);  
  if(result == -1)  
  {  
    perror("connect failed: ");  
    exit(1);  
  }  
  
  printf(">> ");
  char buffer[1024];
  while(fgets (buffer, sizeof(buffer), stdin))
  {
    write(sockfd, buffer,sizeof(buffer));  
    memset(buffer,0,sizeof(buffer));
    read(sockfd, buffer,sizeof(buffer));  
    printf("recv=%s\n>>",buffer);
  }
    
  close(sockfd);  
  return 0; 
}  

Server

#include <sys/types.h>  
#include <sys/socket.h>  
#include <sys/un.h>  
#include <unistd.h>  
#include <stdlib.h>  
#include <stdio.h>  
#include <string.h>

#include <stddef.h>

//本地socket文件
#define UNIX_DOMAIN_SVR  "/tmp/UNIX.SVR"  
//如果使用抽象文件方式,可以使用如 @"/tmp/socket_server"
  
int main()  
{   
  unlink(UNIX_DOMAIN_SVR);
  //创建本地socket
  int server_sockfd = socket(AF_UNIX, SOCK_STREAM, 0);  
    
  struct sockaddr_un server_addr;  
  server_addr.sun_family = AF_UNIX;  
  memcpy(server_addr.sun_path,UNIX_DOMAIN_SVR,sizeof(server_addr.sun_path));  //本地socket文件地址
  
  //绑定socket到本地文件  
  socklen_t len = offsetof(struct sockaddr_un,sun_path) + strlen(server_addr.sun_path);
  bind(server_sockfd,(struct sockaddr*)&server_addr,len);
  printf("bind server_sockfd, len = %d\n",len);

  //listen
  listen(server_sockfd, 5);  
    
  int client_sockfd;  
  struct sockaddr_un client_addr;  

  //accept
  /*
	accept函数调用成功后
	client_addr / len 将会被赋值
	client_addr / len的值是由对方connect的参数决定的
	
  */
  client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_addr, &len);  

  printf("accept success, client's len = %d\n",len);
  //打印对方client绑定的文件名
  len -= offsetof(struct sockaddr_un,sun_path);
  client_addr.sun_path[len-1] = '\0';

  char buffer[1024];
  while(1)  
  {  
    printf("client[%s] say, >> ",client_addr.sun_path);

    //读取数据  
    read(client_sockfd, buffer, sizeof(buffer));  
    printf("%s",buffer);
    
    printf("response message, << %s",buffer);
    //发送数据
    write(client_sockfd, buffer, sizeof(buffer));  
  }  
    
  return 0;  
}  

猜你喜欢

转载自blog.csdn.net/weixin_36750623/article/details/83582409