Linux下的socket编程实践(六)Unix域协议和socketpair传递文件

UNIX域协议并不是一个实际的协议族,而是在单个主机上执行客户/服务器通信的一种方法,所用API与在不同主机上执行客户/服务器通信所使用的API相同。UNIX域协议可以视为IPC方法之一,Unix域协议主要用在同一台机子(仅能用于本地进程间的通信)的不同进程之间传递套接字。为什么不用TCP或者UDP套接字呢?

1)在同一台主机上,UNIX域套接字更有效率,几乎是TCP的两倍(由于UNIX域套接字不需要经过网络协议栈,不需要打包/拆包,计算校验和,维护序号和应答等,只是将应用层数据从一个进程拷贝到另一个进程,而且UNIX域协议机制本质上就是可靠的通讯,而网络协议是为不可靠的通讯设计的)。

2)UNIX域套接字可以在同一台主机上各进程之间传递文件描述符。

注意:UNIX域套接字也提供面向流和面向数据包两种API接口,类似于TCP和UDP,但是面向消息的UNIX套接字也是可靠的,消息既不会丢失也不会顺序错乱。Unix域协议表示协议地址的是路径名,而不是Internet域的IP地址和端口号。

UNIX域套接字地址结构:

#define UNIX_PATH_MAX 108 struct sockaddr_un {sa_family_t sun_family;/* AF_UNIX */charsun_path[UNIX_PATH_MAX]; /* pathname */ };至于通信程序的话,和使用TCP的通信并没有很大的区别,下面给出基于UNIX域套接字的server/client程序源码:

/**Server端**/ int main() {int listenfd = socket(AF_UNIX, SOCK_STREAM, 0); //使用AF_UNIX 或者AF_LOCALif (listenfd == -1)err_exit("socket error");char pathname[] = "/tmp/test_for_unix";unlink(pathname);//如果文件系统中已存在该路径名,bind将会失败。为此我们先调用unlink删除这个路径名,以防止它已经存在struct sockaddr_un servAddr;servAddr.sun_family = AF_UNIX;strcpy(servAddr.sun_path, pathname);if (bind(listenfd, (struct sockaddr *)&servAddr, sizeof(servAddr)) == -1)err_exit("bind error");if (listen(listenfd, SOMAXCONN) == -1)err_exit("listen error");while (1){int connfd = accept(listenfd, NULL, NULL);if (connfd == -1){if(connfd==EINTR)continue;err_exit("accept");}} return 0; }

/**Client端代码**/ int main() {int sockfd = socket(AF_UNIX, SOCK_STREAM, 0);if (sockfd == -1)err_exit("socket error");char pathname[] = "/tmp/test_for_unix";struct sockaddr_un servAddr;servAddr.sun_family = AF_UNIX;strcpy(servAddr.sun_path, pathname);if (connect(sockfd, (struct sockaddr *)&servAddr, sizeof(servAddr)) == -1)err_exit("connect error");return 0;}

UNIX域套接字编程注意点

1.bind成功将会创建一个文件,是一个套接字类型,使用ls -l可以查看到是s开头的文件,权限为0777&~umask

2.sun_path最好用一个/tmp目录下的文件的绝对路径,再次启动的时候最好使用unlink删除这个文件,否则会提示地址正在使用。

3.UNIX域协议支持流式套接口(需要处理粘包问题)与报式套接口(基于数据报)

4.UNIX域流式套接字connect发现监听队列满时,会立刻返回一个ECONNREFUSED,这和TCP不同,如果监听队列满,会忽略到来的SYN,这导致对方重传SYN

5.如果使用流式套接字的话,还是要处理粘包问题的。传递文件描述符socketpair#include <sys/types.h> #include <sys/socket.h> int socketpair(int domain, int type, int protocol, int sv[2]);

创建一个全双工的流管道

参数:

domain:协议家族,可以使用AF_UNIX(AF_LOCAL)UNIX域协议,而且在Linux上,该函数也就只支持这一种协议;

type:套接字类型,可以使用SOCK_STREAM

protocol:协议类型,一般填充为0,表示内核自动选择协议类型。

sv:返回套接字对sv[0],sv[1];

可以使用socketpair创建返回的套接字对进行父子进程通信,如下例:

int main() {int sockfds[2];if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockfds) == -1)err_exit("socketpair error");pid_t pid = fork();if (pid == -1)err_exit("fork error");// 父进程else if (pid > 0){close(sockfds[1]); //和pipe类似,先关闭一端int iVal = 0;while (true){cout << "value = " << iVal << endl;write(sockfds[0], &iVal, sizeof(iVal)); //发送给子进程read(sockfds[0], &iVal, sizeof(iVal));sleep(1);}}// 子进程else if (pid == 0){close(sockfds[0]);int iVal = 0;while (read(sockfds[1], &iVal, sizeof(iVal)) > 0){++ iVal;write(sockfds[1], &iVal, sizeof(iVal)); //发送给父进程}} } sendmsg/recvmsg

#include <sys/types.h> #include <sys/socket.h> ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags); ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags); 它们与sendto/send和recvfrom/recv函数类似,只不过可以传输更复杂的数据结构,功能更强大,不仅可以传输一般数据,还可以传输额外的数据,如文件描述符,但是只能是套接字不能是文件。

我们一直在旅行,一直在等待某个人可以成为我们旅途的伴侣,

Linux下的socket编程实践(六)Unix域协议和socketpair传递文件

相关文章:

你感兴趣的文章:

标签云: