3、Unix域套接字
UNIX域套接字用于和运行在同一台机器上面的进程进行通信。尽管因特网域的套接字也可以用于同样的目的,但是UNIX域的套接字的效率更高。UNIX域的套接字只拷贝数据,它门没有对协议的相关处理,没有网络头的添加和移除,没有校验和的计算,没有顺序号码的生成,也没有对发送的确认。
UNIX域套接字同时提供了流和数据报的接口。但是UNIX域数据报服务是可靠的,消息不会丢失也不会乱序。UNIX域套接字类似套接字和pipes的综合。你可以使用面向网络的套接字接口来使用它们或者你也可以使用socketpair函数创建一对匿名的,连接的UNIX域套接字。
匿名UNIX域套接字
#include <sys/socket.h>
int socketpair(int domain, int type, int protocol, int sockfd[2]);
返回:如果成功返回0,如果错误返回1。
尽管这个接口足够通用可以允许socketpair在任何域中使用,但是操作系统一般只支持对UNIX域的支持。
举例:使用UNIX域套接字的s_pipe函数
下面的代码展示了基于套接字的s_pipe函数实现。这个函数创建一对面向连接的UNIX域流的套接字。
有些基于BSD的系统使用UNIX 域套接字来实现管道,但是当pipe被调用的时候,管道第一个描述符号的读端以及第二个描述符号的写端都是关闭的。为了获得一个全双工的管道,我们必须直接调用socketpair。
#include "apue.h"
#include <sys/socket.h>
/*
* Returns a full-duplex "stream" pipe (a UNIX domain socket)
* with the two file descriptors returned in fd[0] and fd[1].
*/
int s_pipe(int fd[2])
{
return(socketpair(AF_UNIX, SOCK_STREAM, 0, fd));
}
有名UNIX域套接字
尽管socketpair函数创建了互相连接的套接字,但是它们并没有名字。也就是说,它们不能通过没有亲属关系的进程被访问到。在前面,我们学到了如何将一个地址绑定到一个因特网的套接字上面。如同因特网的套接字一样,UNIX域套接字可以有名称并且用来做为某些服务的通知。然而,UNIX域的套接字和因特网的套接字有所不同。
在前面我们知道, 套接字的地址格式对于每个系统实现来说有所不同 。一个用于UNIX域套接字的地址可以使用sockaddr_un结构来进行表示。
在Linux 2.4.22和Solaris 9中,sockaddr_un结构如下,并定义在<sys/un.h>中:
struct sockaddr_un {
sa_family_t sun_family; /* AF_UNIX */
char sun_path[108]; /* pathname */
};
在FreeBSD 5.2.1和Mac OS X 10.3中,sockaddr_un结构的定义如下:
struct sockaddr_un {
unsigned char sun_len; /* length including null */
sa_family_t sun_family; /* AF_UNIX */
char sun_path[104]; /* pathname */
};
sockaddr_un结构中的sun_path成员包含了一个路径。当我们将一个地址绑定到一个UNIX域的套接字上面的时候,系统以同样的名字创建了一个S_IFSOCK类型的文件。这个文件只用来通知客户程序套接字的名称。这个文件不能被打开只能用于应用程序之间的通信。
如果这个文件在我们想要绑定同样名称的地址的时候已经存在了,这个绑定的请求将会失败。当我们关闭套接字的时候,文件不会被自动地删除,所以我们需要确保当我们程序退出的时候将这个文件unlink。
举例:绑定一个地址到UNIX域套接字
后面的代码展示了绑定一个地址到UNIX域套接字的例子。
#include "apue.h"
#include <sys/socket.h>
#include <sys/un.h>
int main(void)
{
int fd, size;
struct sockaddr_un un;
un.sun_family = AF_UNIX;
strcpy(un.sun_path, "foo.socket");
if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
err_sys("socket failed");
size = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);
if (bind(fd, (struct sockaddr *)&un, size) < 0)
err_sys("bind failed");
printf("UNIX domain socket bound\n");
exit(0);
}
当我们运行这个程序的时候,绑定请求成功,但是如果我们再次运行这个程序,我们会得到一个错误。因为,这个文件已经存在了,如果我们不删除这个文件那么这个程序不会再次成功运行。
$ ./a.out run the program
UNIX domain socket bound
$ ls -l foo.socket look at the socket file
srwxrwxr-x 1 sar 0 Aug 22 12:43 foo.socket
$ ./a.out try to run the program again
bind failed: Address already in use
$ rm foo.socket remove the socket file
$ ./a.out run the program a third time
UNIX domain socket bound now it succeeds
这里,我们确定bind的地址的大小的方法是使用offsetof函数来确定sun_path成员的偏移再加上其大小,并 没有包含其中的NULL字符串结束字节 。因为不同的系统实现sun_path成员前面都有什么是不一样的,我们使用<stddef.h>中的offsetof宏来从结构的最开始进行计算。这个offsetof宏的定义如下:
#define offsetof(TYPE, MEMBER) ((int)&((TYPE *)0)->MEMBER)
这个表达式会计算一个整数,这个整数代表一个成员的起始地址,并且假定整个结构的起始地址是0。
网友评论