欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

UNP卷一chapter15 UNIX协议

程序员文章站 2024-03-04 10:12:29
...

    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进程,含两个描述符。如下图所示

UNP卷一chapter15 UNIX协议

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