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),刚开始学习网络编程,如有不正确之处请大家多多指正。

猜你喜欢

转载自blog.csdn.net/tt_love9527/article/details/80380806