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_DGRAM 或 SOCK_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进程通信是一个可靠的通信方式