Unxi域套接字并不是一个实际的协议族,而是在单个主机上执行客户/服务器通信的一种方法,所用API就是在不同主机上执行客户/服务器通信所用的API。可以视为IPC方法之一。
UNix域提供两类套接字:字节流套接字(类似TCP),数据报套接字(类似UDP)
1.Unix域套接字往往比通信两端位于同一主机的TCP套接字快出一倍。X Window System发挥了Unix域套接字的这个优势。当一个X11客户打开到一个X11服务器的连接时,该客户检查DISPLAY环境变量的值(其中指定服务器的主机名,窗口和屏幕)。如果服务器和客户处于同一主机,客户就打开一个到服务器的Unix域套接字字节流连接,否则打开一个到服务器的TCP连接。
2.Unix域套接字可用于在同一主机上的不同进程之间传递描述符
3.unix域套接字较新的实现把客户的凭证提供给服务器,从而能提供额外的安全检查措施。
4.Unix域套接字与传统套接字的区别是用路径名来表示协议族的描述。
UNIX域套接字用于同一台pc上运行的进程之间通信,它仅仅复制数据,不执行协议处理,不需要增加删除网络报头,无需计算校验和,不产生顺序号,无需发送确认报文。
struct sockaddr_un{
sa_family sun_family; //AF_LOCAL,地址族
char sun_path[104]; //必须以空字符结尾,通过这样一个路径描述地址
};
服务端
#include
#include
#include
#include
#include
#include
#include
#include
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
}while(0)
void echo_srv(int conn)
{
char recvbuf[1024];
int n;
while(1)
{
memset(recvbuf,0,sizeof(recvbuf));
//不断接收一行数据到recvbuf中
n=read(conn,recvbuf,sizeof(recvbuf));
if(n==-1)
{
if(n==EINTR)
continue;
ERR_EXIT("read");
}
//客户端关闭
else if(n==0)
{
printf("client close\n");
break;
}
fputs(recvbuf,stdout);
write(conn,recvbuf,strlen(recvbuf));
}
close(conn);
}
int main(void)
{
int listenfd;
//创建一个监听套接字
//它的协议家族是PF_UNIX,用流式套接字SOCK_STREAM
if((listenfd=socket(PF_UNIX,SOCK_STREAM,0))<0)
ERR_EXIT("socket");
//unlink表示删除这个文件,先删除,再绑定,重新创造了一个文件
unlink("/tmp/test_socket");
//初始化一个地址绑定监听
struct sockaddr_un servaddr;
memset(&servaddr,0,sizeof(servaddr));
servaddr.sun_family=AF_UNIX;
strcpy(servaddr.sun_path,"/tmp/test_socket");
//绑定
//绑定的时候会产生test_socket文件
if(bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
ERR_EXIT("bind");
//监听
//监听队列的最大值SOMAXCONN
if(listen(listenfd,SOMAXCONN)<0)
ERR_EXIT("listen");
int conn;
pid_t pid;
//接受客户端的连接
while(1)
{
//返回一个已连接套接字
conn=accept(listenfd,NULL,NULL);
if(conn==-1)
{
if(conn==EINTR)
continue;
ERR_EXIT("accept");
}
pid=fork();
if(pid==-1)
ERR_EXIT("fork");
//pid==0(子进程)说明是客户端,执行回射
if(pid==0)
{
//子进程不需要处理监听
close(listenfd);
echo_srv(conn);
exit(EXIT_SUCCESS);
}
//父进程不需要处理连接
close(conn);
}
return 0;
客户端
#include
#include
#include
#include
#include
#include
#include
#include
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
}while(0)
void echo_cli(int sock)
{
char sendbuf[1024]={0};
char recvbuf[1024]={0};
//不停地从标准输入获取一行数据到一个缓冲区当中
while(fgets(sendbuf,sizeof(sendbuf),stdin)!=NULL)
{
//发送给服务器端
write(sock,sendbuf,strlen(sendbuf));
//接收回来
read(sock,recvbuf,sizeof(recvbuf));
fputs(recvbuf,stdout);
memset(sendbuf,0,sizeof(sendbuf));
memset(recvbuf,0,sizeof(recvbuf));
}
close(sock);
}
int main(void)
{
int sock;
if((sock=socket(PF_UNIX,SOCK_STREAM,0))<0)
ERR_EXIT("socket");
struct sockaddr_un servaddr;
memset(&servaddr,0,sizeof(servaddr));
servaddr.sun_family=AF_UNIX;
strcpy(servaddr.sun_path,"/tmp/test_socket");
if(connect(sock,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)
ERR_EXIT("connect");
//连接成功后,执行回射客户端的函数
echo_cli(sock);
return 0;
}
1.bind成功后会创建一个文件,权限为0777 & ~umask
2.sun_path最好用一个绝对路径,如果是相对路径的话,如果客户端和服务端不在同一个目录,就会找不到路径。
3.UNIX域流式套接字connect发现监听队列满时,会立刻返回一个ECONNREFUSED,这和TCP不同,如果监听队列满,会忽略到来的SYN,这导致对方重传SYN
功能:创建一个全双工的流管道,也是只能用于亲缘关系的进程间通信。而有名管道和匿名管道是半双工的
原型:int socketpair(int domain,int type,int protocol,int sv[2]);
参数:
domain:协议家族
type:套接字类型
protocol:协议类型
sv:返回套接字对
返回值:成功返回0;失败返回-1
#include
#include
#include
#include
#include
#include
#include
#include
#define ERR_EXIT(m) \
do \
{ \
perror(m); \
exit(EXIT_FAILURE); \
}while(0)
//父子进程间实现全双工通信
int main(void)
{
//定义一个数组用来接收套接字对
int sockfds[2];
//全双工通信的流管道,sockfds[0]和sockfds[1]都是即可读又可写
if(socketpair(PF_UNIX,SOCK_STREAM,0,sockfds)<0)
ERR_EXIT("socketpair");
pid_t pid;
//在父子进程间实现全双工的通信
pid=fork();
if(pid==-1)
ERR_EXIT("fork");
//父进程
if(pid>0)
{
int val=0;
close(sockfds[1]);
while(1)
{
++val;
printf("sending data: %d\n",val);
//父进程写给子进程
write(sockfds[0],&val,sizeof(val));
//sockfds既可读又可写,用于接收回来
read(sockfds[0],&val,sizeof(val));
printf("data received: %d\n",val);
}
}
//子进程
else if(pid==0)
{
int val;
close(sockfds[0]);
while(1)
{
//sockfds[1]既能读又能写
read(sockfds[1],&val,sizeof(val));
++val;
write(sockfds[1],&val,sizeof(val));
}
}
return 0;
}
ssize_t recvmsg(int sockfd, struct msghdr * msg, int flags);
ssize_t sendmsg(int sockfd, struct msghdr * msg, int flags);
成功时候返回读写字节数,出错时候返回-1.
这2个函数只用于套接口,不能用于普通的I/O读写,参数sockfd则是指明要读写的套接口。
flags用于传入控制信息,一般包括以下几个
MSG_DONTROUTE send可用
MSG_DONWAIT send与recv都可用
MSG_PEEK recv可用
MSG_WAITALL recv可用
MSG_OOB send可用
MSG_EOR send recv可用
返回信息都记录在struct msghdr * msg中。
struct msghdr {
void * msg_name;//协议地址和套接口信息,在非连接的UDP中,发送者要指定对方地址端口,接受方
//用于的到数据来源,如果不需要的话可以设置为NULL(在TCP或者连接的UDP
//中,一般设置为NULL)。
socklen_t msg_namelen;//上面的长度
struct iovec * msg_lov;//指向真正要发送数据的缓冲区
ssize_t msg_lovlen;//缓冲区的个数
void * msg_control;//辅助数据的指针
socklen_t msg_controllen;//辅助数据的长度
int msg_flags; //用于返回之前flags的控制信息
}
#include
struct iovec{
void *iov_base; /* Pointer to data. */
size_t iov_len; /* Length of data. */
};
void send_fd(int sock_fd, int fd)
{
int ret;
struct msghdr msg;
struct cmsghdr *p_cmsg;
struct iovec vec;
char cmsgbuf[CMSG_SPACE(sizeof(fd))];
int *p_fds;
char sendchar = 0;
msg.msg_control = cmsgbuf;
msg.msg_controllen = sizeof(cmsgbuf);
p_cmsg = CMSG_FIRSTHDR(&msg);
p_cmsg->cmsg_level = SOL_SOCKET;
p_cmsg->cmsg_type = SCM_RIGHTS;
p_cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
p_fds = (int*)CMSG_DATA(p_cmsg);
*p_fds = fd;
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_iov = &vec;
msg.msg_iovlen = 1;
msg.msg_flags = 0;
vec.iov_base = &sendchar;
vec.iov_len = sizeof(sendchar);
ret = sendmsg(sock_fd, &msg, 0);
if (ret != 1)
ERR_EXIT("sendmsg");
}
int recv_fd(const int sock_fd)
{
int ret;
struct msghdr msg;
char recvchar;
struct iovec vec;
int recv_fd;
char cmsgbuf[CMSG_SPACE(sizeof(recv_fd))];
struct cmsghdr *p_cmsg;
int *p_fd;
vec.iov_base = &recvchar;
vec.iov_len = sizeof(recvchar);
msg.msg_name = NULL;
msg.msg_namelen = 0;
msg.msg_iov = &vec;
msg.msg_iovlen = 1;
msg.msg_control = cmsgbuf;
msg.msg_controllen = sizeof(cmsgbuf);
msg.msg_flags = 0;
p_fd = (int*)CMSG_DATA(CMSG_FIRSTHDR(&msg));
*p_fd = -1;
ret = recvmsg(sock_fd, &msg, 0);
if (ret != 1)
ERR_EXIT("recvmsg");
p_cmsg = CMSG_FIRSTHDR(&msg);
if (p_cmsg == NULL)
ERR_EXIT("no passed fd");
p_fd = (int*)CMSG_DATA(p_cmsg);
recv_fd = *p_fd;
if (recv_fd == -1)
ERR_EXIT("no passed fd");
return recv_fd;
}
文件描述符的传递只能通过UNIX域协议套接字,当前使用的是sockpair,实现了父子进程间文件描述字的传递,如果是不相干的进程之间文件描述字的传递,就不能用sockpair,就要用UNIX域协议套接字进行传递。普通的tcp套接字、udp套接字是不能传递文件描述字。