概述
Unix 域协议并不是一个实际的协议族,而是在单个主机上执行客户/服务通信的一种方式。是进程间通信(IPC)的一种方式。
它提供了两类套接字:字节流套接字 SOCK_STREAM(有点像 TCP)和数据报套接字 SOCK_DGRAM(有点像 UDP)
UNIX 域数据报服务是可靠的,不会丢失消息,也不会传递出错。
IP 协议标识客户服务器是通过 IP 地址和端口号实现的,UNIX 域协议中用于标识客户机和服务器的协议地址的是普通文件系统中的路径名。
UNIX 域协议的特点:
1.UNIX 域套接字域 TCP 套接字相比,在同一台主机的传输速度前者是后者的两倍 。UNIX 域套接字仅仅复制数据,并不执行协议处理,不需要添加或删除网络报头,无需计算校验和,不产
生顺序号,也不需要发送确认报文
2.UNIX 域套接字可以在同一台主机上各进程之间传递文件描述符
3.UNIX 域套接字域传统套接字的区别是用路径名表示协议族的描述
UNIX 域协议地址
cpp
#define UNIX_PATH_MAX 108
struct sockaddr_un
{
__kernel_sa_family_t sun_family; /* 通信协议 AF_UNIX / AF_LOCAL */
char sun_path[UNIX_PATH_MAX]; /* pathname */
//UNIX 域协议的地址,是以'\0'结束的本地文件系统中的绝对路径名
//会自动的创建不需要提前创建
};
UNIX 域协议用于本地进程间通信的编程流程和 TCP/UDP 一样,只不过具体的协议地址是使
用文件路径名
Server:先创建套接字 -> 绑定地址 -> 监听 -> accept 客户端连接 -> 连接成功开始通信 -> 关闭套接字
Client:先创建套接字 ->绑定地址-> 连接 server -> 开始通信 -> 关闭套接字。
UNIX 域协议之进程间通信方式一:socketpair 函数
【头文件】
cpp
#include <sys/types.h>
#include <sys/socket.h>
【函数原型】
cpp
int socketpair(int domain, int type, int protocol, int sv[2]);
【函数功能】
此函数只用于 Unix 域套接口,它的 domain 必须是 AF_LOCAL,protocol 参数必须为 0,type 可以是 SOCK_STREAM 也可以是 SOCK_DGRAM,它与调用 pipe 创建的普通管道类似,差别在 于普通管道只能进行单向读或单向写,而该管道是全双工的,可以单向读写。
【参数含义】
**[domain]:**表示协议族,在 Linux 下只能为 AF_LOCAL 或者 AF_UNIX 。(自从 Linux 2.6.27 后也支持 SOCK_NONBLOCK 和 SOCK_CLOEXEC)
**[type]:**表示协议,可以是 SOCK_STREAM 或者 SOCK_DGRAM。SOCK_STREAM 是基于TCP 的,而 SOCK_DGRAM 是基于 UDP 的
**[protocol]:**表示类型,只能为 0
**[sv]:**套节字柄对,该两个句柄作用相同,均能进行读写双向操作
【返回值】
0 为创建成功,-1 为创建失败,并且 errno 来表明特定的错误号,具体错误号如下所述:
**EAFNOSUPPORT:**本机上不支持指定的 address。 EFAULT: 地址 sv 无法指向有效的进程地址空间内。
**EMFILE:**已经达到了系统限制文件描述符,或者该进程使用过量的描述符。
**EOPNOTSUPP:**指定的协议不支持创建套接字对。
**EPROTONOSUPPORT:**本机不支持指定的协议。
【备注】
1、该函数只能用于 UNIX 域( LINUX )下。
2、只能用于有亲缘关系的进程(或线程)间通信。
3、所创建的套节字对作用是一样的,均能够可读可写(而管道 PIPE 只能进行单向读写)。
4、在读的时候,管道内必须有内容,否则将会阻塞;简而言之,该函数是阻塞的。
【示例代码】
cpp
/*socketpair1.c*/
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <stdio.h>
int main ()
{
int sv[2];
int result = socketpair(AF_UNIX, SOCK_STREAM, 0, sv);
if (result < 0){
exit(1);
}
printf("sv[0] is : %d \n", sv[0]); //这两个套节字句柄并不相同,但作用是一样的
printf("sv[1] is : %d \n", sv[1]);
if (fork())
{
/* 父进程 */
int val = 0;
pid_t pid = getpid();
close(sv[1]); //父进程关闭 sv[1]的读写权限
while (1)
{
++val;
printf("%d send message: %d\n", pid, val);
write(sv[0], &val, sizeof(val)); //父进程向管道里写数据
//也可以去读
// read(sv[0], &val, sizeof(val)); //如果字进程不写数据,将会导致此处堵塞
//printf("%d receive message: %d\n", pid, val);
sleep(1);
}
}
else
{
/*子进程*/
int val = 0;
close(sv[0]); //字进程关闭 sv[0]的读写权限
pid_t pid = getpid();
while(1)
{
read(sv[1], &val, sizeof(val)); //字进程从管道中取数据
printf("%d receive message: %d\n", pid, val);
// printf("%d receive message: %d\n", pid, val);
//也可以写
// write(sv[1], &val, sizeof(val));
}
}
}
UNIX 域协议之进程间通信方式二:套接字通信方式
UNIX 域协议进行本地通信的方式和 TCP、UDP 很类似,下面就以 UNIX 域协议的流式套接字来写一个客户端和服务器间通信的例子,使用流式套接字,则本地间的通信方式就要类似于 TCP,这样的通信方式同时也具有 TCP 通信的特点。
客户端示例程序:
cpp
#include<stdio.h>
#include<stdlib.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <sys/un.h>
#include <arpa/inet.h>
#include<netinet/in.h> //为了使用 IPV4 地址结构体
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#define UNIX_PATH_PS "/home/china/sockp1" //连接的套接字地址
#define UNIX_PATH_PC "/home/china/sockp2" //绑定的套接字地址
int main(int argc,char *argv[])
{
system("rm /home/china/sockp2"); //防止文件重名
//1.创建一个套接字
int sockfd = socket(AF_UNIX, SOCK_STREAM,0); //创建一个本地通信 UNIX 流式套接字
if(-1 == sockfd)
{
perror("create socket failed");
exit(-1);
}
//2.绑定一个通信地址(让服务器能看到我),可不绑定
struct sockaddr_un local;
memset(&local,0,sizeof(struct sockaddr_un)); //清空结构体
local.sun_family = AF_UNIX;
strcpy(local.sun_path,UNIX_PATH_PC);
int ret = bind(sockfd,(struct sockaddr*)&local,sizeof(local));
if(ret == -1)
{
perror("bind error");
exit(-1);
}
printf("bind success\n");
//连接目标服务器
struct sockaddr_un saddr; //保存目标服务器的地址(本地的文件名)
memset(&saddr,0,sizeof(struct sockaddr_un)); //清空结构体
saddr.sun_family = AF_UNIX;
strcpy(saddr.sun_path,UNIX_PATH_PS);
ret = connect(sockfd, (struct sockaddr*)&saddr,sizeof(saddr)); //请求连接目标服务器
if(ret == -1)
{
perror("connect error");
exit(-1);
}
printf("connect success\n");
char buf[1024] = {0};
//读取信息
ret = recv(sockfd,buf,1024, 0);
if(ret == -1)
{
perror("recv error");
}
printf("recv size:%d,recv data:%s\n",ret,buf);
//发送信息
ret = sendto(sockfd,"byebye",6,0,NULL,0);
if(ret == -1)
{
perror("sendto error");
}
printf("sendto size:%d\n",ret);
//关闭套接字
shutdown(sockfd,SHUT_RDWR);
close(sockfd);
return 0;
}
服务器程序示例:
cpp
#include<stdio.h>
#include<stdlib.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <sys/un.h>
#include <arpa/inet.h>
#include<netinet/in.h> //为了使用 IPV4 地址结构体
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#define UNIX_PATH_PS "/home/china/sockp1"
int main(int argc,char *argv[])
{
system("rm /home/china/sockp1"); //保证该文件没有被使用
//1.创建一个套接字
int sockfd = socket(AF_UNIX, SOCK_STREAM,0); //创建一个本地通信UNIX 流式套接字
if(-1 == sockfd)
{
perror("create socket failed");
exit(-1);
}
//2.绑定一个通信地址(作为服务器本身的地址)
struct sockaddr_un local; //保存服务器的地址(文件名)
memset(&local,0,sizeof(struct sockaddr_un)); //清空结构体
local.sun_family = AF_UNIX;
strcpy(local.sun_path,UNIX_PATH_PS);
int ret = bind(sockfd,(struct sockaddr*)&local,sizeof(local)); //绑定服务器的地址
if(ret == -1)
{
perror("bind error");
exit(-1);
}
printf("bind success\n");
//3.开启对一个套接字的监听
listen(sockfd,250);
//4.等待客户端的连接
while(1)
{
struct sockaddr_un caddr; //保存客户端的地址(文件名)
socklen_t len = sizeof(caddr);
int confd = accept(sockfd,(struct sockaddr*)&caddr,&len); //阻塞等待客户端连接
if(confd > 0) //客户端连接成功,返回一个连接套接字专门用来和客户端通信
{
//一个客户端连接成功.开一个进程/线程去处理这个连接
printf("client path:%s\n",caddr.sun_path);
ret = sendto(confd,"nishiliangzaima?",20,0,NULL,0); //发送消息
if(ret == -1)
{
perror("sendto error");
}
char buf[1024] = {0};
ret = read(confd,buf,1024); //接收消息
if(ret <= 0)
{
perror("recv error");
}
printf("recv size:%d,recv data:%s\n",ret,buf);
close(confd);//关闭连接套接字
}
}
//关闭套接字
shutdown(sockfd,SHUT_RDWR);
close(sockfd);
return 0;
}