linux本地套接字

本地套接字实现的是同一主机的不同进程间的通信,且建立的通信是双向的通信。socket本地通信与网络通信使用的是统一套接口,只是地址结构中的参数不同。

1、socket流程

(1)创建socket

创建套接字需要使用 socket 系统调用,其原型如下:

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

参数:

domain :指定协议族,对于本地套接字来说,其值须被置为 AF_UNIX 枚举值;

type :套接字类型,type 参数可被设置为 SOCK_STREAM(流式套接字)或 SOCK_DGRAM(数据报式套接字)

protocol 参数指定具体协议,protocol 字段应被设置为 0;

返回:生成的套接字描述符

套接字类型:

流式套接字(SOCK_STREAM)是一个有顺序的、可靠的双向字节流,相当于在本地进程之间建立起一条数据通道;

数据报式套接字(SOCK_DGRAM)相当于单纯的发送消息,在进程通信过程中,理论上可能会有信息丢失、复制或者不按先后次序到达的情况,但由于其在本地通信,不通过外界网络,这些情况出现的概率很小。

//创建本地socket
  int server_sockfd = socket(PF_UNIX, SOCK_STREAM, 0);  

(2)绑定和连接地址

1)绑定地址

通过绑定地址来指定服务器端的地址(struct sockaddr_un),本地socket的地址是本地文件的地址。

 bind 绑定定函数:

int bind(int socket, const struct sockaddr *address, size_t address_len);

参数:

 socket表示服务器端的套接字描述符

address 表示需要绑定的本地地址,是一个 struct sockaddr_un 类型的变量

address_len 表示该本地地址的字节长度

本地socket绑定,监听该地址的网络事件

struct sockaddr_un server_addr;  
server_addr.sun_family = AF_UNIX;  
strncpy(server_addr.sun_path, UNIX_DOMAIN,sizeof(server_addr.sun_path)-1);  
bind(server_sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));  

2)连接地址

注册需要连接的服务器地址信息到socket

struct sockaddr_un address;  
address.sun_family = AF_UNIX;  
strncpy(address.sun_path, UNIX_DOMAIN,sizeof(address.sun_path)-1);  
connect(sockfd, (struct sockaddr *)&address, sizeof(address));  

2、socket命名

本地套接字的通信双方均需要具有本地地址,指定方式是使用 struct sockaddr_un 的变量sun_path。

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

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

1)具体文件命名

2)抽象命名空间

(1)具体文件命名  

服务器绑定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);  

缺点:

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

(2)抽象命名空间

这种方式不需要创建socket文件,只需要命名一个全局名字,即可让客户端根据此名字进行连接。

后者的实现过程与前者的差别是,后者在对地址结构成员sun_path数组赋值的时候,必须把第一个字节置0,即sun_path[0] = 0,代码如:

因第二种方式会对首字节置0,我们可以在命名字符串SERVER_NAME前添加一个占位符@,例如:

#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函数在#include 头文件中定义,表示sun_path在socketaddr_un 结构中的字节偏移。

正常情况下,sun_path指定了需要bind(或者send/connect等)的路径,若使用abstract_namespace,那么sun_path[0]必须为'\0'(零字符)。同时在使用这个地址时,必须在addresslen参数中指定正确的长度,这个长度是sizeof(sun_family) + 1 + strlen(path)。地址长度不包括path后面的'\0',Linux的系统调用也不会去寻找这个零字符。

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

实质上,Linux在内存中维护了一个虚拟的"文件系统",这个文件在close之后会自动消失,并且文件系统中看不到bind的文件,用netstat -an却有记录。

若使用netstat查询端口情况,通过abstract_namespace的方式创建的unix域socket bind之后,路径前有一个@ 

~  05:17:32 $ netstat -an
unix  2      [ ]         DGRAM                    7260     @/tmp/cli_path
unix  2      [ ]         DGRAM                    7259     @/tmp/svr_path

缺点:

移植性较差。


3、本地套接字通信示例

服务器:

#include   
#include   
#include   
#include   
#include   
#include   
//本地socket文件
#define UNIX_DOMAIN "/tmp/UNIX.domain"  
//如果使用抽象文件方式,可以使用如 @"/tmp/socket_server"
  
int main()  
{  
  //如果有本地socket文件,删除本地文件
  unlink(UNIX_DOMAIN);  
    
  //创建本地socket
  int server_sockfd = socket(PF_UNIX, SOCK_STREAM, 0);  
    
  struct sockaddr_un server_addr;  
  server_addr.sun_family = AF_UNIX;  
  strncpy(server_addr.sun_path, UNIX_DOMAIN,sizeof(server_addr.sun_path)-1);  //本地socket文件地址
 
  //绑定socket到本地文件  
  bind(server_sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));  
    
  listen(server_sockfd, 5);  
    
  char ch;  
  int client_sockfd;  
  struct sockaddr_un client_addr;  
  socklen_t len = sizeof(client_addr);  
  char buffer[1024];
  while(1)  
  {  
    printf("server waiting:\n");  
    //接收客户端连接  
    client_sockfd = accept(server_sockfd, (struct sockaddr *)&client_addr, &len);  
    //读取数据  
    read(client_sockfd, buffer, sizeof(buffer));  
    printf("get char from client: %c\n", ch);  
    //发送数据
    write(client_sockfd, buffer, sizeof(buffer));  
      
    close(client_sockfd);  
  }  
    
  return 0;  
}  


客户端:

#include   
#include   
#include   
#include   
#include   
#include   

#define UNIX_DOMAIN "/tmp/UNIX.domain"  
//或者本文件夹下的文件  "server_socket"

int main()  
{  
  //创建本地socket
  int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);  
  
  struct sockaddr_un address;  
  address.sun_family = AF_UNIX;  
  strncpy(address.sun_path, UNIX_DOMAIN,sizeof(address.sun_path)-1);  
    
  //连接本地socket到本地文件(本物理服务器的) 
  int result = connect(sockfd, (struct sockaddr *)&address, sizeof(address));  
  if(result == -1)  
  {  
    perror("connect failed: ");  
    exit(1);  
  }  
  char buffer[1024];
  //输入数据
  fgets (buffer, sizeof(buffer), stdin)
  //发送数据
  write(sockfd, buffer,sizeof(buffer));  
  memset(buffer,0,sizeof(buffer));
  //阻塞读取服务器返回数据
  read(sockfd, buffer,sizeof(buffer));  
  printf("get char from server: %c\n", ch);  
    
  close(sockfd);  
  return 0;  
}  

  



   


你可能感兴趣的:(并发)