什么叫抽象路径名,这其实是Linux特有的一个特性,它允许将一个Unix域套接字绑定到一个名字上,且不会在文件系统中创建这个名字的文件。如果要创建一个抽象名字空间的绑定,必须要将sun_path字段的第一个字节设置成NULL('\0'),而且和普通的文件系统名字空间不同的是,系统会用sun_path除第一个字节之后余下的所有字节当做抽象名字。也就是说在解析抽象路径名时需要用到sun_path字段当中所有的字节,而不是像解析普通路径名一样,解析到第一个NULL就可以停止了。因为不会再在文件系统中创建文件了,所以对于抽象路径名来说,就不需要担心与文件系统中已存在的文件产生名字冲突的问题了,也不需要在使用完套接字之后删除附带产生的这个文件了,当套接字被关闭之后会自动删除这个抽象名。
抽象路径名还可以解决路径权限的问题。如果在软件中使用绝对路径,为了方便,最好就是使用系统目录下的文件,这样的话执行程序就只能使用root权限。如果使用相对路径或者用户目录下的路径,那又会存在软件更改运行环境之后可能无法运行的问题。采用抽象路径就不会有这种问题。
本例采用类似UDP的数据包形式:
服务端:
#include "../common.h"
#define FILE_PATH_SER "my_socket_s"
#define FILE_PATH_CLI "my_socket_c"
int main()
{
int serFd,cliFd;
struct sockaddr_un s_addr,c_addr;
char recvbuf[1024] = {0};
char sendbuf[1024] = {0};
int ret = -1;
int addrLen = sizeof(s_addr);
if((serFd = socket(AF_UNIX, SOCK_DGRAM, 0)) == -1)
{
printf("socket serFd fail\n");
return 0;
}
if((cliFd = socket(AF_UNIX, SOCK_DGRAM, 0)) == -1)
{
printf("socket cliFd fail\n");
return 0;
}
//unlink(FILE_PATH_SER); //删除文件,保证文件不存在
memset(&s_addr, 0, sizeof(struct sockaddr_un));
s_addr.sun_family = AF_UNIX;
//strcpy(s_addr.sun_path,FILE_PATH_SER);
s_addr.sun_path[0] = '\0';
memcpy(s_addr.sun_path+1,FILE_PATH_SER,sizeof(FILE_PATH_SER));
c_addr.sun_family = AF_UNIX;
//strcpy(c_addr.sun_path,FILE_PATH_CLI);
c_addr.sun_path[0] = '\0';
memcpy(c_addr.sun_path+1,FILE_PATH_CLI,sizeof(FILE_PATH_CLI));
//必须绑定
if(bind(serFd, (struct sockaddr *)&s_addr, sizeof(struct sockaddr)) == -1)
{
printf("bind fail\n");
return 0;
}
while(1)
{
ret = recvfrom(serFd, recvbuf, 1024, 0,(struct sockaddr *)&s_addr, (socklen_t*)&addrLen);
if(ret < 0)
{
char *errorMsg = strerror(errno);
printf("recv from cli fail:%s\n", errorMsg);
break;
}
printf("recv '%s' from cli ...\n",recvbuf);
sprintf(sendbuf,"ser recv '%s'",recvbuf);
ret = sendto(cliFd,sendbuf, 1024, 0,(struct sockaddr *)&c_addr, sizeof(struct sockaddr));
if(ret < 0)
{
char *errorMsg = strerror(errno);
printf("send to cli fail:%s\n", errorMsg);
break;
}
else
{
printf("send '%s' to cli success ...\n\n",sendbuf);
}
}
close(serFd);
close(cliFd);
}
客户端:
#include "../common.h"
#define FILE_PATH_SER "my_socket_s"
#define FILE_PATH_CLI "my_socket_c"
/*
按照普通的包套接字创建和连接的流程,只是在服务器端调用bind()函数绑定了一个地址,而客户端并没有地址。
这在流式套接字中没有问题,内核已经在服务器端调用accept()函数接收一个客户端连接时创建了一个新的套接字,
从而将一一对应关系绑定到了这个新的套接字上了。所以,对于包套接字来说,
在客户端还需要再调用bind()函数绑定一次,人为的创建一个客户端地址,且这个客户端路径名地址显然不能和服务器端的路径名相同。
这样看来,并没有实际的客户端与服务器之分.
*/
int main()
{
int serFd,cliFd;
struct sockaddr_un s_addr,c_addr;
int ret = -1;
char sendbuf[1024];
char recvbuf[1024];
int addrLen = sizeof(s_addr);
if((serFd = socket(AF_UNIX, SOCK_DGRAM, 0)) == -1)
{
printf("socket serFd fail\n");
return 0;
}
if((cliFd = socket(AF_UNIX, SOCK_DGRAM, 0)) == -1)
{
printf("socket cliFd fail\n");
return 0;
}
memset(&s_addr, 0, sizeof(struct sockaddr_un));
memset(&c_addr, 0, sizeof(struct sockaddr_un));
s_addr.sun_family = AF_UNIX;
//strcpy(s_addr.sun_path,FILE_PATH_SER);
s_addr.sun_path[0] = '\0';
memcpy(s_addr.sun_path+1,FILE_PATH_SER,sizeof(FILE_PATH_SER));
c_addr.sun_family = AF_UNIX;
//strcpy(c_addr.sun_path,FILE_PATH_CLI);
c_addr.sun_path[0] = '\0';
memcpy(c_addr.sun_path+1,FILE_PATH_CLI,sizeof(FILE_PATH_CLI));
//unlink(FILE_PATH_CLI);
if(bind(cliFd, (struct sockaddr *)&c_addr, sizeof(struct sockaddr)) == -1)
{
printf("bind fail\n");
return 0;
}
while(1)
{
printf("please input send message:");
scanf("%s",sendbuf);
getchar();
if((ret = sendto(serFd, sendbuf, 1024, 0,(struct sockaddr *)&s_addr, sizeof(struct sockaddr))) < 0)
{
char *errorMsg = strerror(errno);
printf("send to ser fail:%s\n", errorMsg);
break;
}
printf("send '%s' to ser success ...\n",sendbuf);
ret = recvfrom(cliFd, recvbuf, 1024, 0,(struct sockaddr *)&c_addr, (socklen_t*)&addrLen);
if(ret < 0)
{
char *errorMsg = strerror(errno);
printf("recv from ser fail:%s\n", errorMsg);
break;
}
else
{
printf("recv (%s) from ser ...\n\n",recvbuf);
}
}
close(serFd);
close(cliFd);
}
这样的话,即使服务端和客户端不在同一个目录下,使用相对路径也是可以相互通信的,这在域套接字简介及范例(二)————数据包形式中是无法实现的。同样的,我们在其他任何位置运行相同的程序,都无法再次使用bind进行绑定,可见抽象路径名本身对于内核而言是全局同步的。