UNP卷一chapter15 UNIX协议
Unix域协议(也叫做本地IPC)并不是一个实际的协议族,而是在单主机上执行客户/服务器通信的一种方法,所用API就是在不同主机上执行客户/服务器通信所用的API(套接字API)。
Unix域中用于标识客户和服务器的协议地址是谱通文件系统中的路径名(但需要与Unix域socket关联起来)。这点与IP地址协议由地址和端口号构成有很大的不同。
1、Unix域套接字地址结构、bind调用及socketpair函数
i、地址结构
#include<sys/un.h>
struct sockaddr_un {
sa_family sun_family;//AF_LOCAL
char sun_path[104];//null-terminated pathname
};
ii、Unix域套接字的bind调用#include "unp.h"
int
main(int argc, char **argv)
{
int sockfd;
socklen_t len;
struct sockaddr_un addr1, addr2;
if (argc != 2)
err_quit("usage: unixbind <pathname>");
sockfd = Socket(AF_LOCAL, SOCK_STREAM, 0);
//unlink:删除文件系统中已存在的路径名,如果已存在而不删除,bind将会失败,即使出错也可以忽略。
unlink(argv[1]); /* OK if this fails */
bzero(&addr1, sizeof(addr1));
addr1.sun_family = AF_LOCAL;
strncpy(addr1.sun_path, argv[1], sizeof(addr1.sun_path) - 1);
Bind(sockfd, (SA *)&addr1, SUN_LEN(&addr1));//SUN_LEN宏以一个指向sockaddr_un结构的针指为参数,并返回该结构长度
len = sizeof(addr2);
Getsockname(sockfd, (SA *)&addr2, &len);//输出绑定的路径名
printf("bound name = %s, returned len = %d\n", addr2.sun_path, len);
exit(0);
}
iii、socketpair函数(创建两个随后连接起来的套接字,本函数仅适用于Unix域套接字)
#include<sys/socket.h>
int socketpair(int family, int type, int protocol, int sockfd[2]);//返回:若成功则为非0,若出错则为-1
//family参数必须为AF_LOCAL,protocol参数必须为0,type参数既可心是SOCK_STREAM也可以是SOCK_DGRAM。
2、套接字函数(与TCP、UDP所用函数的区别)
i、由bind创建的路径名默认访问权限为0777,并按当前的umask值进行修正;
ii、与Unix域套接字关联的路径名应该是绝对路径名;
iii、在connect调用中指定的路径名必须是一个当前绑定在某个打开的Unix域套接字上的路径名,而且它们的套接字类型(字节流或数据报)也必须一致,比如说,客户端要连上服务器,其connect的第二个参数就要是服务器地址,其已被绑定了Unix域套接字上的路径名;
iv、调用connect连接一个Unix域套接字涉及的权限测试等同于调用open以只写方式访问相应的路径名;
v、Unix域字节流套接字类似tcp套接字:为进程提供一个无记录边界的字节流接口;
vi、对于某个Unix域字节流套接字的connect调用发现这个监听套接字的队列已满,调用就立即返回一个ECONNREFUSED错误。这与tcp(尽可能尝试去连接上,通过数次重发SYN)有很大区别;
vii、Unix域数据报套接字类似udp套接字:为进程提供一个保留记录边界的不可靠数据报服务;
viii、在一个未绑定的Unix域套接字上发送数据报不会自动给这个套接字捆绑一个路径名(导致服务器利用sendto回送信息失败)。有别于UDP(会给这个套接字捆绑一个临时端口)。类似,对于某个Unix域数据报套接字的connect调用不会给本套接字捆绑一个路径名。
3、Unix域字节流客户与服务器程序(数据报程序参考书上P329,不再码出)
i、服务器回射程序
#include "unp.h"
int
main(int argc, char **argv)
{
int listenfd, connfd;
pid_t childpid;
socklen_t clilen;
struct sockaddr_un cliaddr, servaddr;
void sig_chld(int);
listenfd = Socket(AF_LOCAL, SOCK_STREAM, 0);
unlink(UNIXSTR_PATH);//UNIXSTR_PATH为/tmp/unix.str,在unp.h中宏定义
bzero(&servaddr, sizeof(servaddr));
servaddr.sun_family = AF_LOCAL;
strcpy(servaddr.sun_path, UNIXSTR_PATH);
Bind(listenfd, (SA *)&servaddr, sizeof(servaddr));//给listenfd绑定地址族和绝对路径名
Listen(listenfd, LISTENQ);
Signal(SIGCHLD, sig_chld);//信号捕获,然后交给sig_chld函数,处理zoombie进程
for (; ; ) {
clilen = sizeof(cliaddr);
if ((connfd = accept(listenfd, (SA *)&cliaddr, &clilen)) < 0) {
if (errno == EINTR)
continue; /* back to for() */
else
err_sys("accept error");
}
if ((childpid = Fork()) == 0) { /* child process */
Close(listenfd); /* close listening socket */
str_echo(connfd); /* process request */
exit(0);
}
Close(connfd); /* parent closes connected socket */
}
}
ii、客户程序#include "unp.h"
int
main(int argc, char **argv)
{
int sockfd;
struct sockaddr_un servaddr;
sockfd = Socket(AF_LOCAL, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sun_family = AF_LOCAL;
strcpy(servaddr.sun_path, UNIXSTR_PATH);
Connect(sockfd, (SA *) &servaddr, sizeof(servaddr));
str_cli(stdin, sockfd); /* do it all */
exit(0);
}
3、描述符传递
从一个进程到另一个进程传递打开的描述符,步骤如下:
i、fork调用返回之后,子进程共享父进程的所有打开的描述符;
ii、exec调用执行之后,所有描述符通常保持打开状态不变(实现方案:首先在这两个进程之间创建一个Unix域套接字,然后使用sendmsg跨这个套接字发送一个特殊消息。这个消息内内核来专门处理,会把打开的描述符从发送进程传递到接收进程)。
一个描述符传递的例子(通过执行另一个程序来打开文件到输出流中)
首先通过命令行参数取得一个路径名,打开这个文件,再把文件的内容复制到标准输出。该程序调用名为my_open的函数。my_open创建一个流管道,并调用fork和exec启动执行另一个程序(./openfile)。期待输出的文件由这个程序(./openfile)打开。该程序随后必须打开的描述符通过流管道传递回父进程。
实际步骤及代码见下:
a、通过调用socketpair创建一个流管首后的mycat进程,含两个描述符。如下图所示
b、mycat进程接着调用fork,子进程再调用exec执行openfile程序。父进程关闭[1]描述符,子进程关闭[0]描述符。如上图所示
c、父进程给openfile程序传递三条信息:(1)待打开文件的路径名,(2)打开方式(只读,读写或只写),(3)流管道本进程端(上图标为[1])对应的描述符号。此三条信息作为命令行参数在调用exec时进行传递。openfile程序在通过流管道发送回打开的描述符后便终止。程序的退出状态告父进程文件能否打开,若不能则同时告知发生了什么类型的错误。
上述程序代码见下
#include "unp.h"
int my_open(const char *, int);
int
main(int argc, char **argv)
{
int fd, n;
char buff[BUFFSIZE];
if (argc != 2)
err_quit("usage: mycat <pathname>");
if ((fd = my_open(argv[1], O_RDONLY)) < 0)
err_sys("cannot open %s", argv[1]);
while ((n = Read(fd, buff, BUFFSIZE)) > 0)
Write(STDOUT_FILENO, buff, n);
exit(0);
}
int
my_open(const char *pathname, int mode)
{
int fd, sockfd[2], status;
pid_t childpid;
char c, argsockfd[10], argmode[10];
Socketpair(AF_LOCAL, SOCK_STREAM, 0, sockfd);
if ((childpid = Fork()) == 0) { /* child process */
Close(sockfd[0]);
snprintf(argsockfd, sizeof(argsockfd), "%d", sockfd[1]);
snprintf(argmode, sizeof(argmode), "%d", mode);
execl("./openfile", "openfile", argsockfd, pathname, argmode,
(char *)NULL);
err_sys("execl error");
}
/* parent process - wait for the child to terminate */
Close(sockfd[1]); /* close the end we don't use */
Waitpid(childpid, &status, 0);
if (WIFEXITED(status) == 0)
err_quit("child did not terminate");
if ((status = WEXITSTATUS(status)) == 0)
Read_fd(sockfd[0], &c, 1, &fd);//通过流管道接收描述符
else {
errno = status; /* set errno value from child's status */
fd = -1;
}
Close(sockfd[0]);
return(fd);
}
openfile函数:打开一个文件并传递回其描述符,代码如下#include "unp.h"
int
main(int argc, char **argv)
{
int fd;
if (argc != 4)
err_quit("openfile <sockfd#> <filename> <mode>");
if ( (fd = open(argv[2], atoi(argv[3]))) < 0)
exit( (errno > 0) ? errno : 255 );
if (write_fd(atoi(argv[1]), "", 1, fd) < 0)
exit( (errno > 0) ? errno : 255 );
exit(0);
}
上述代码中,open_fd()函数及write_fd()函数,不再码出,见书上P335和P336
以上知识点来均来自steven先生所著UNP卷一(version3),刚开始学习网络编程,如有不正确之处请大家多多指正。
上一篇: Android开发方式之Java+html+javascript混合开发
下一篇: 基本编程能力练习