Unix域协议

Unix域协议并不是一个实际的协议族,而是在单个主机上执行客户/服务器通信的一种方法。

Unix域提供两类套接字:字节流套接字和数据报套接字。

使用Unix域套接字的理由:

1.Unix域套接字往往比通信两端位于同一个主机的TCP套接字快出一倍。

2.Unix域套接字可用于在同一个主机上的不同进程之间传递描述符。

3.Unix域套接字较新的实现把客户的凭证(用户ID和组ID)提供给服务器,从而能够提供额外的安全检查措施。

 

Unix域用于标识客户和服务器的协议地址是普通文件系统中的路径名。

 

Unix域套接字地址结构

#include<sys/un.h>
struct sockaddr_un {
    sa_family_t  sun_family;         //AF_LOCAL
    char         sun_path[104];      //路径名
}

存放在sun_path数组中的路径名必须以空字符结尾。

实现提供的SUN_LEN宏以一个指向sockaddr_un结构的指针为参数并返回该结构的长度,其中包含路径名中非空字节数。

 

Unix域套接字的bind调用

创建一个Unix域套接字,往其上bind一个路径名,再调用getsockname输出这个绑定的路径名

#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(argv[1]);            //使用bind绑定命令行参数的路径名,先用unlink删除要绑定的路径名,以防已经存在。
    
    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));
    
    len = sizeof(addr2);
    getsockname(sockfd, (SA *) &addr2, &len);   //调用getsockname取得刚绑定的路径名到addr2中。
    printf("bound name = %s, returned len = %d \n",addr2.sun_path, len);
    
    exit(0);
}


socketpair函数

socketpair函数创建两个随后连接起来的套接字。

int socketpair(int family, int type, int protocol, int sockfd[2]);

family参数必须为AF_LOCAL,protocol参数必须为0.type参数可以是SOCK_STREAM,也可以是SOCK_DGRAM。

新创建的两个套接字描述符作为sockfd[0]和sockfd[1]返回。

type参数为SOCK_STREAM创建得到的为流管道,它与调用pipe创建的普通UNIX管道类似。

 

 

使用Unix域TCP协议的回射客户/服务器程序

先定义两个使用的路径名:

#define UNIXSTR_PATH    "/tmp/unix.str" /* Unix domain stream */  
#define UNIXDG_PATH     "/tmp/unix.dg"  /* Unix domain datagram */  

服务器端:

/* echo_localserv.c */
#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);
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sun_family = AF_LOCAL;
	strcpy(servaddr.sun_path, UNIXSTR_PATH);

	bind(listenfd, (SA *) &servaddr, sizeof(servaddr));

	listen(listenfd,LISTENQ);

	signal(SIGCHLD, sig_chld);

	while(1) {
		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) {
			close(listenfd);
			str_echo(connfd);
			exit(0);
		}
		close(connfd);
	}
}

void sig_chld(int signo)  
{  
    pid_t pid;  
    int stat;  
  
    /*等待所有子进程终止*/  
    while ((pid = waitpid(-1, &stat, WNOHANG)) > 0)  
        printf("child %d terminated\n", pid);  
    return;  
}  

客户端: 

/* echo_localclnt.c */
#include "unp.h"

int main(int argc, char *argv[])
{
	int sockfd;
	struct sockaddr_in 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);
}

 

使用Unix域UDP协议的客户/服务器程序

服务器端:

/* echo_unpserv.c */
#include "unp.h"

int main(int argc, char *argv[])
{
	int sockfd;
	struct sockaddr_un servaddr, cliaddr;

	sockfd = socket(AF_LOCAL, SOCK_DGRAM, 0);

	unlink(UNIXD_PATH);
	bzero(&servaddr, sizeof(servaddr));
	servaddr.sun_family = AF_LOCAL;
	strcpy(servaddr.sun_path, UNIXDG_PATH);

	bind(sockfd, (SA *) &servaddr, sizeof(servaddr));

	dg_echo(sockfd, (SA *) &cliaddr, sizeof(cliaddr));
}

客户端:

/* echo_unpclnt.c */
#include "unp.h"

int main(int argc, char *argv[])
{
	int sockfd;
	struct sockaddr_un cliaddr, servaddr;

	sockfd = socket(AF_LOCAL, SOCK_DGRAM, 0 );

	bzero(&cliaddr, sizeof(cliaddr));
	cliaddr.sun_family = AF_LOCAL;
	strcpy(cliaddr.sun_path, tmpnam(NULL));

/*与TCP客户不同的是,当使用UNIX域数据报协议时,必须显式bind一个路径名*/  
	bind(sockfd, (SA *) &cliaddr, sizeof(cliaddr));

	bzero(&servaddr, sizeof(servaddr));
	servaddr.sun_family = AF_LOCAL;
	strcpy(servaddr.sun_path, UNIXDG_PATH);

	dg_cli(stdin, sockfd, (SA *) &servaddr, sizeof(servaddr));

	exit(0);
}

 

描述符传递的例子

一个例子:mycat

通过命令行参数取得一个路径名,打开这个文件,再把文件的内容复制到标准输出。

调用 my_open函数,创建一个流管道,并调用fork和exec启动执行另一个程序,期待输出的文件由这个程序打开。该程序随后必须把打开的描述符通过流管道传递回父进程。

 

下图:调用socketpair创建一个流管道后的mycat进程。我们以[0]和[1]标识socketpair返回的两个描述符。

                                                         

mycat进程接着调用fork,子进程再调用exec执行openfile程序。父进程关闭[1]描述符,子进程关闭[0]描述符。

                                     

mycat.c:

#include "unp.h"

int myopen(const char *, int);

int main(int argc, char *argv[])
{
	int fd, n;
	char buf[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);
}

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);1

	if ( (childpid = fork()) == 0) {
		close(sockfd[0]);
		snprintf(argcsockfd, sizeof(argsockfd), "%d", sockfd[1]);
		snprintf(argmode, sizeof(argmode), "%d", mode);
		excel("./openfile","openfile",argsockfd,pathname,argmode,(char *) NULL);
		err_sys("excel error");
	}

	/* parent process - wait for the child to terminate */
	close(sockfd[1]);

	waitpid(childpid, *status, 0);
	if (WIFEXITED(status) == 0)
		err_quit("child did not terminate");
	if (( status = WEXITSTATUS(status)) == 0)
		read_fd(sock[0], &c, 1, &fd);
	else {
		errno = status;
		fd = -1;
	}

	close(sockfd[0]);
	return(fd);
}

猜你喜欢

转载自blog.csdn.net/amoscykl/article/details/80454790