Linux间进程通信(五)--- UNIX套接字方式

UNIX套接字方式

Unix Socket是一种Socket方式实现进程间通信(IPC)的功能,与普通的网络socket相比,不需要进行复杂的数据打包拆包,校验和计算验证,不需要走网络协议栈,而且安全可靠。UNIX Domain Socket是全双工的,即允许双向通信。

一、使用
1、头文件
  • #include <stddef.h>
  • #include <sys/socket.h>
  • #include <sys/un.h>
2、使用流程

与TCP/IP套接字大致相同,在参数上略微有些不同,因此这里就简单介绍一下。

  • 创建socket 文件描述符:int socket (int domain, int type, int protocol);
    (1)domain 指定为 AF_UNIX,使用 AF_UNIX 会在系统上创建一个 socket 文件,不同进程通过读写这个文件来实现通信。
    (2)type 可以选择 SOCK_DGRAMSOCK_STREAM。SOCK_STREAM 意味着会提供按顺序的、可靠、双向、面向连接的比特流。SOCK_DGRAM 意味着会提供定长的、不可靠、无连接的通信。但由于进程间通信都是在本机通过内核通信,所以SOCK_STREAM和SOCK_DGRAM都是可靠的,不会丢包也不会出现发送包的次序和接收包的次序不一致的问题。它们的区别仅仅是,SOCK_STREAM无论发送多大的数据都不会被截断,而对于SOCK_DGRAM来说,如果发送的数据超过了一个报文的最大长度,则数据会被截断
    (3)protocol 参数表示协议,对于Unix域套接字来说,其一定是被设置成0

  • 绑定创建的socket文件路径(服务端):int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    (1)sockfd:第一步创建的socked文件描述符,即socket()函数的返回值
    (2)addr:在Unix域套接字中,套接字的地址是以sockaddr_un结构体来表示的,其结构如下:

struct sockaddr_un {
    sa_family_t sun_family;
    char sun_path[108];
}

其中sun_family设置为AF_UNIX,sun_path设置为socket文件路径
这个 socket 文件由 bind() 调用创建,如果调用 bind() 时该文件已存在,则 bind() 错误返回。因此,一般在调用 bind() 前会检查 socket 文件是否存在,如果存在就删除掉。

  • 监听连接(服务端):int listen(int sockfd, int backlog);
    backlog:一个常量,表示允许的最大等待队列长度
  • 初始化连接(服务端):int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    与普通的TCP/IP套接字不同,Unix域套接字不存在客户端地址的问题(都在一台机器上),因此这里的addr和addrlen参数都直接设置成NULL即可
  • 连接服务端(客户端):int connect(int sockfd, struct sockaddr *addr,int addrlen);
3、演示程序

test6-1-1与test6-1-2双向通信

//test6-1-1
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <stddef.h>//offsetof求出struct给定成员de字节偏移值
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define BUFSIZE 2048
#define SOCKET_PATH "test6.socket"
#define QUEUE 5
int main()
{
	
	struct sockaddr_un serun,cliun;
	socklen_t cliun_len;
	int listenfd,connfd,size;
	char recvBuf[BUFSIZE];
	char sendBuf[]="I am test6-1-1!Hello world!";

	int i,n;
	if((listenfd=socket(AF_UNIX,SOCK_STREAM,0))<0)
	{
		perror("Listenfd create fail!");
		exit(-1);
	}

	memset(&serun,0,sizeof(serun));
	serun.sun_family=AF_UNIX;//UNIX本地通信协议
	strcpy(serun.sun_path,SOCKET_PATH);
	size=offsetof(struct sockaddr_un,sun_path)+strlen(serun.sun_path);
	unlink(SOCKET_PATH);//先删除
	if(bind(listenfd,(struct sockaddr *)&serun,size)<0)
	{
		perror("bind error");
		exit(-1);
	}
	printf("UNIX domain socket bound!\n");

	if(listen(listenfd,QUEUE)<0)
	{
		perror("listen error");
		exit(-1);
	}
	printf("Accepting connections...\n");

	while(1)
	{
		cliun_len=sizeof(cliun);
		if((connfd=accept(listenfd,(struct sockaddr*)&cliun,&cliun_len))<0)
		{
			perror("accept error");
			continue;
		}
		else
			break;
	}
	printf("Accept success\n");

	if(read(connfd,recvBuf,BUFSIZE)<0)
	{
		perror("read error!");
		exit(-1);
	}
	printf("[test6-1-1]Read data:%s\n",recvBuf);

	sleep(1);

	if(write(connfd,sendBuf,BUFSIZE)<0)
	{
		perror("[test6-1-1]Write error");
		exit(-1);
	}
	printf("[test6-1-1]Write data:%s\n",sendBuf);
	
	close(connfd);
	close(listenfd);
	return 0;
}
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <stddef.h>//offsetof求出struct给定成员de字节偏移值
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define BUFSIZE 2048
#define SOCKET_PATH "test6.socket"
#define SOCKET_PATH2 "test6_2.socket"


int main()
{
	struct sockaddr_un serun;
	int len;
	char sendBuf[]="I am test6-1-2!Hello world!";
	char recvBuf[BUFSIZE];
	int sockfd;

	if((sockfd=socket(AF_UNIX,SOCK_STREAM,0))<0)
	{
		perror("client socket error");
		exit(-1);
	}

	memset(&serun,0,sizeof(serun));
	serun.sun_family=AF_UNIX;
	strcpy(serun.sun_path,SOCKET_PATH);
	len=offsetof(struct sockaddr_un,sun_path)+strlen(serun.sun_path);
	if(connect(sockfd,(struct sockaddr *)&serun,len)<0)
	{
		perror("connect error");
		exit(-1);
	}

	write(sockfd,sendBuf,strlen(sendBuf));
	printf("[test6-1-2]Send data:%s\n",sendBuf);
	

	if(read(sockfd,recvBuf,BUFSIZE)<0)
		printf("The other side has been closed!\n");
	else
		printf("[test6-1-2]Read data:%s\n",recvBuf);
	sleep(1);

	close(sockfd);
	return 0;
}

运行结果:
在这里插入图片描述在这里插入图片描述
在这里插入图片描述

4、与TCP/IP的socket比较

相同点:流程基本一样

不同点

  • 创建socket时,UNIX Socket用的是AF_UNIX协议,而TCP_Socket用的是AF_INET协议
  • Bind()函数UNIX Socket绑定给的socket文件的路径,而TCP Socket绑定给的是IP地址和端口
5、非阻塞读写

通过select读写

//test6-2-1
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <stddef.h>//offsetof求出struct给定成员de字节偏移值
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#define BUFSIZE 16
#define SOCKET_PATH "test6.socket"
#define QUEUE 5
int main()
{
	
	struct sockaddr_un serun,cliun;
	socklen_t cliun_len;
	int listenfd,connfd,size;
	char recvBuf[BUFSIZE];

	int i,len;
	if((listenfd=socket(AF_UNIX,SOCK_STREAM,0))<0)
	{
		perror("Listenfd create fail!");
		exit(-1);
	}

	memset(&serun,0,sizeof(serun));
	serun.sun_family=AF_UNIX;//UNIX本地通信协议
	strcpy(serun.sun_path,SOCKET_PATH);
	size=offsetof(struct sockaddr_un,sun_path)+strlen(serun.sun_path);
	unlink(SOCKET_PATH);//先删除
	if(bind(listenfd,(struct sockaddr *)&serun,size)<0)
	{
		perror("bind error");
		exit(-1);
	}
	printf("UNIX domain socket bound!\n");

	if(listen(listenfd,QUEUE)<0)
	{
		perror("listen error");
		exit(-1);
	}
	printf("Accepting connections...\n");

	while(1)
	{
		cliun_len=sizeof(cliun);
		if((connfd=accept(listenfd,(struct sockaddr*)&cliun,&cliun_len))<0)
		{
			perror("accept error");
			continue;
		}
		else
			break;
	}
	printf("Accept success\n");

	/*设置非阻塞*/
	int flags = fcntl(connfd, F_GETFL, 0);        
    fcntl(connfd, F_SETFL, flags | O_NONBLOCK);   

	fd_set fds;
	int maxfdp=connfd+1;
	FD_ZERO(&fds);//清空存放文件描述符的集合
	FD_SET(connfd,&fds);//添加描述符,表示对conn监听
	printf("hhahaahh\n");
	while(1)
	{
		switch(select(maxfdp,&fds,NULL,NULL,NULL))//等待有可读的数据
		{
			
			case -1:
				perror("select error!");
				return 1;
			case 0:
				sleep(1);
				printf("Timeout\n");
				break;
			default://缓冲区中有数据可以读取
				memset(recvBuf,0,BUFSIZE);
				//len=read(connfd,recvBuf,BUFSIZE);
				//printf("[test6-2-1]Read data:%s,length:%d\n",recvBuf,len);
				break;
		}
		sleep(1);
	}
	
	close(connfd);
	close(listenfd);
	return 0;
}
//test6-2-2
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/un.h>
#include <stddef.h>//offsetof求出struct给定成员de字节偏移值
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#define BUFSIZE 32
#define SOCKET_PATH "test6.socket"
#define SOCKET_PATH2 "test6_2.socket"


int main()
{
	struct sockaddr_un serun;
	int len,i;

	char sendBuf[]="12345678123456781234567812345678";
	//char recvBuf[BUFSIZE];
	int sockfd;

	if((sockfd=socket(AF_UNIX,SOCK_STREAM,0))<0)
	{
		perror("client socket error");
		exit(-1);
	}

	memset(&serun,0,sizeof(serun));
	serun.sun_family=AF_UNIX;
	strcpy(serun.sun_path,SOCKET_PATH);
	len=offsetof(struct sockaddr_un,sun_path)+strlen(serun.sun_path);
	if(connect(sockfd,(struct sockaddr *)&serun,len)<0)
	{
		perror("connect error");
		exit(-1);
	}
	
	/*设置非阻塞*/
	int flags = fcntl(sockfd, F_GETFL, 0);        //获取文件的flags2值。
    fcntl(sockfd, F_SETFL, flags | O_NONBLOCK);   //设置成非阻塞模式;

	fd_set fds;
	int maxfdp=sockfd+1;
	FD_ZERO(&fds);//清空存放文件描述符的集合
	FD_SET(sockfd,&fds);//添加描述符,表示对conn监听
	int total=0;
	while(1)
	{
		switch(select(maxfdp,NULL,&fds,NULL,NULL))//停下来等待有可读的数据
		{
			
			case -1:
				perror("select error!");
				return 1;
			case 0:
				sleep(1);
				printf("Timeout\n");
				break;
			default://缓冲区中有数据可以读取
				len=write(sockfd,sendBuf,BUFSIZE);
				if(len>0)
					total+=len;
				printf("[test-2-2]length:%d\n",total);
				break;
		}
	}
	
	
	close(sockfd);
	return 0;
}

运行结果:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
为了测试写满后的执行情况,设置test6-2-1端不读,仅是test6-2-2端写
在这里插入图片描述
在这里插入图片描述
结果显示,写满后停下而不是继续写导致数据丢失。UNIX Socket进程通信是一个可靠的通信方式

猜你喜欢

转载自blog.csdn.net/Tong_jy/article/details/86624914