UNIX域协议是在单个主机上执行客户/服务器通信的一种方法

使用UNIX域套接字有以下3个理由:

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

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

3.UNIX域套接字较新的实现把客户的凭证提供给服务器,从而能够提供额外的安全检查措施

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

除非他们和UNIX域套接字关联起来,否则无法读写这些文件。

可以查看之前apue的学习笔记  http://www.cnblogs.com/runnyu/p/4650843.html

UNIX域套接字的地址结构

struct sockaddr_un
{
    sa_family_t sun_family; /* AF_LOCAL */
    char sun_path[]; /* pathname */
};

实现提供了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 != )
err_quit("usage: unixbind <pathname>"); sockfd = Socket(AF_LOCAL, SOCK_STREAM, ); unlink(argv[]); /* OK if this fails */ bzero(&addr1, sizeof(addr1));
addr1.sun_family = AF_LOCAL;
strncpy(addr1.sun_path, argv[], sizeof(addr1.sun_path)-);
Bind(sockfd, (SA *) &addr1, SUN_LEN(&addr1)); len = sizeof(addr2);
Getsockname(sockfd, (SA *) &addr2, &len);
printf("bound name = %s, returned len = %d\n", addr2.sun_path, len); exit();
}

先调用unlink删除这个路径名,以防bind失败

socketpair函数

socketpair函数创建两个随后连接起来的套接字。本函数仅适用于UNIX套接字

#include <sys/socket.h>
int socketpair(int domain,int type,int protocol,int sockfd[]);

family参数必须为AF_LOCAL,protocol参数必须为0,type参数既可以是SOCK_STREAM,也可以是SOCK_DGRAM。新创建的两个套接字描述符作为sockfd[0]和sockfd[1]返回

UNIX域字节流客户/服务器程序

下面将第五章的TCP回射客户/服务器程序重新编写成使用UNIX域套接字

服务器程序。unp.h定义的UNIXSTR_PATH常值为/tmp/unix.str

 #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, ); 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); for ( ; ; ) {
clilen = sizeof(cliaddr);
if ( (connfd = accept(listenfd, (SA *) &cliaddr, &clilen)) < ) {
if (errno == EINTR)
continue; /* back to for() */
else
err_sys("accept error");
} if ( (childpid = Fork()) == ) { /* child process */
Close(listenfd); /* close listening socket */
str_echo(connfd); /* process request */
exit();
}
Close(connfd); /* parent closes connected socket */
}
}

客户程序

 #include    "unp.h"

 int
main(int argc, char **argv)
{
int sockfd;
struct sockaddr_un servaddr; sockfd = Socket(AF_LOCAL, SOCK_STREAM, ); 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();
}

UNIX域数据报客户/服务器程序

下面将第八章UDP回射客户/服务器程序重新编写成使用UNIX域数据报套接字。

服务器程序。unp.h定义的UNIXDG_PATH常值为/tmp/unix.dg

 #include    "unp.h"

 int
main(int argc, char **argv)
{
int sockfd;
struct sockaddr_un servaddr, cliaddr; sockfd = Socket(AF_LOCAL, SOCK_DGRAM, ); unlink(UNIXDG_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));
}

客户程序

 #include    "unp.h"

 int
main(int argc, char **argv)
{
int sockfd;
struct sockaddr_un cliaddr, servaddr; sockfd = Socket(AF_LOCAL, SOCK_DGRAM, ); bzero(&cliaddr, sizeof(cliaddr)); /* bind an address for us */
cliaddr.sun_family = AF_LOCAL;
strcpy(cliaddr.sun_path, tmpnam(NULL)); Bind(sockfd, (SA *) &cliaddr, sizeof(cliaddr)); bzero(&servaddr, sizeof(servaddr)); /* fill in server's address */
servaddr.sun_family = AF_LOCAL;
strcpy(servaddr.sun_path, UNIXDG_PATH); dg_cli(stdin, sockfd, (SA *) &servaddr, sizeof(servaddr)); exit();
}

与UDP客户不同的是:使用UNIX域数据报协议时,我们必须显式bind一个路径名到我们的套接字,这样服务器才会有能回射应答的路径名。

描述符传递

下面两种方法可以把一个描述符从一个进程传递到另一个进程

1.fork调用返回之后,子进程共享父进程的所有打开的描述符

2.exec调用执行之后,所有描述符通常保持打开状态不变

第一种方法中:进程先打开一个描述符,再调用fork,然后父进程关闭这个描述符,子进程则处理这个描述符。

这样一个打开的描述符就从父进程传递到子进程

当前UNIX系统提供了用于从一个进程向任一其他进程传递任一打开的描述符的方法,这两个进程无需存在亲缘关系

这种技术要求首先在这两个进程之间创建一个UNIX域套接字,然后使用sendmsg跨这个套接字发送一个特殊消息。

这个消息由内核来专门处理,会把打开的描述符从发送进程传递到接收进程。

在两个进程之间传递描述符涉及的步骤如下:

1.创建一个字节流或数据报流的UNIX域套接字

2.发送进程通过调用返回描述符的任一UNIX函数打开一个描述符,这些函数的例子有:open、pipe、mkfifo、socket和accept

3.发送进程创建一个msghdr结构,其中含有待传递的描述符

4.接收进程调用recvmsg在来自步骤1的UNIX域套接字上接收这个描述符

描述符传递的例子

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

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

下面展示上述步骤:

1.通过调用socketpair创建一个流管道后的mycat进程。

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

3.父进程给openfile程序传递三条信息(调用exec时进行传递):待打开文件的路径名、打开方式、流管道本进程端对应的描述符号

通过执行另一个程序打开文件的优势在于:另一个程序可以是一个setuid到root的程序,能够打开我们通常没有打开权限的文件。

下面是mycat程序(使用别的程序打开,然后把描述符传递过来进行读取该文件)

 #include    "unp.h"

 int        my_open(const char *, int);

 int
main(int argc, char **argv)
{
int fd, n;
char buff[BUFFSIZE]; if (argc != )
err_quit("usage: mycat <pathname>"); if ( (fd = my_open(argv[], O_RDONLY)) < )
err_sys("cannot open %s", argv[]); while ( (n = Read(fd, buff, BUFFSIZE)) > )
Write(STDOUT_FILENO, buff, n); exit();
}

下面是my_open函数

 #include    "unp.h"

 int
my_open(const char *pathname, int mode)
{
int fd, sockfd[], status;
pid_t childpid;
char c, argsockfd[], argmode[]; Socketpair(AF_LOCAL, SOCK_STREAM, , sockfd); if ( (childpid = Fork()) == ) { /* child process */
Close(sockfd[]);
snprintf(argsockfd, sizeof(argsockfd), "%d", sockfd[]);
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[]); /* close the end we don't use */ Waitpid(childpid, &status, );
if (WIFEXITED(status) == )
err_quit("child did not terminate");
if ( (status = WEXITSTATUS(status)) == )
Read_fd(sockfd[], &c, , &fd);
else {
errno = status; /* set errno value from child's status */
fd = -;
} Close(sockfd[]);
return(fd);
}

my_open函数返回的描述符是调用read_fd返回的描述符(调用recvmsg接收一个描述符)

 /* include read_fd */
#include "unp.h" ssize_t
read_fd(int fd, void *ptr, size_t nbytes, int *recvfd)
{
struct msghdr msg;
struct iovec iov[];
ssize_t n; #ifdef HAVE_MSGHDR_MSG_CONTROL
union {
struct cmsghdr cm;
char control[CMSG_SPACE(sizeof(int))];
} control_un;
struct cmsghdr *cmptr; msg.msg_control = control_un.control;
msg.msg_controllen = sizeof(control_un.control);
#else
int newfd; msg.msg_accrights = (caddr_t) &newfd;
msg.msg_accrightslen = sizeof(int);
#endif msg.msg_name = NULL;
msg.msg_namelen = ; iov[].iov_base = ptr;
iov[].iov_len = nbytes;
msg.msg_iov = iov;
msg.msg_iovlen = ; if ( (n = recvmsg(fd, &msg, )) <= )
return(n); #ifdef HAVE_MSGHDR_MSG_CONTROL
if ( (cmptr = CMSG_FIRSTHDR(&msg)) != NULL &&
cmptr->cmsg_len == CMSG_LEN(sizeof(int))) {
if (cmptr->cmsg_level != SOL_SOCKET)
err_quit("control level != SOL_SOCKET");
if (cmptr->cmsg_type != SCM_RIGHTS)
err_quit("control type != SCM_RIGHTS");
*recvfd = *((int *) CMSG_DATA(cmptr));
} else
*recvfd = -; /* descriptor was not passed */
#else
/* *INDENT-OFF* */
if (msg.msg_accrightslen == sizeof(int))
*recvfd = newfd;
else
*recvfd = -; /* descriptor was not passed */
/* *INDENT-ON* */
#endif return(n);
}
/* end read_fd */ ssize_t
Read_fd(int fd, void *ptr, size_t nbytes, int *recvfd)
{
ssize_t n; if ( (n = read_fd(fd, ptr, nbytes, recvfd)) < )
err_sys("read_fd error"); return(n);
}

下面给出openfile程序:打开一个文件并传递回其描述符

 #include    "unp.h"

 int
main(int argc, char **argv)
{
int fd; if (argc != )
err_quit("openfile <sockfd#> <filename> <mode>"); if ( (fd = open(argv[], atoi(argv[]))) < )
exit( (errno > ) ? errno : ); if (write_fd(atoi(argv[]), "", , fd) < )
exit( (errno > ) ? errno : ); exit();
}

它调用write_fd调用sendmsg跨一个UNIX域套接字发送一个描述符

 /* include write_fd */
#include "unp.h" ssize_t
write_fd(int fd, void *ptr, size_t nbytes, int sendfd)
{
struct msghdr msg;
struct iovec iov[]; #ifdef HAVE_MSGHDR_MSG_CONTROL
union {
struct cmsghdr cm;
char control[CMSG_SPACE(sizeof(int))];
} control_un;
struct cmsghdr *cmptr; msg.msg_control = control_un.control;
msg.msg_controllen = sizeof(control_un.control); cmptr = CMSG_FIRSTHDR(&msg);
cmptr->cmsg_len = CMSG_LEN(sizeof(int));
cmptr->cmsg_level = SOL_SOCKET;
cmptr->cmsg_type = SCM_RIGHTS;
*((int *) CMSG_DATA(cmptr)) = sendfd;
#else
msg.msg_accrights = (caddr_t) &sendfd;
msg.msg_accrightslen = sizeof(int);
#endif msg.msg_name = NULL;
msg.msg_namelen = ; iov[].iov_base = ptr;
iov[].iov_len = nbytes;
msg.msg_iov = iov;
msg.msg_iovlen = ; return(sendmsg(fd, &msg, ));
}
/* end write_fd */

UNP学习笔记(第十五章 UNIX域协议)的更多相关文章

  1. UNP学习笔记(第五章 TCP客户/服务程序实例)

    我们将在本章使用前一章中介绍的基本函数编写一个完整的TCP客户/服务器程序实例 这个简单得例子是执行如下步骤的一个回射服务器: TCP回射服务器程序 #include "unp.h" ...

  2. 学习笔记 第十五章 JavaScript基础

    第15章   JavaScript基础 [学习重点] 了解JavaScript基础知识 熟悉常量和变量 能够使用表达式和运算符 正确使用语句 能够掌握数据类型和转换的基本方法 正确使用函数.对象.数组 ...

  3. JavaScript高级程序设计学习笔记第十五章--使用Canvas绘图

    一.基本用法 1.要使用<canvas>元素,必须先设置其 width 和 height 属性,指定可以绘图的区域大小.能通过 CSS 为该元素添加样式,如果不添加任何样式或者不绘制任何图 ...

  4. VSTO学习笔记(十五)Office 2013 初体验

    原文:VSTO学习笔记(十五)Office 2013 初体验 Office 2013 近期发布了首个面向消费者的预览版本,我也于第一时间进行了更新试用.从此开始VSTO系列全面转向Office 201 ...

  5. Nodejs学习笔记(十五)--- Node.js + Koa2 构建网站简单示例

    目录 前言 搭建项目及其它准备工作 创建数据库 创建Koa2项目 安装项目其它需要包 清除冗余文件并重新规划项目目录 配置文件 规划示例路由,并新建相关文件 实现数据访问和业务逻辑相关方法 编写mys ...

  6. [转]Nodejs学习笔记(十五)--- Node.js + Koa2 构建网站简单示例

    本文转自:https://www.cnblogs.com/zhongweiv/p/nodejs_koa2_webapp.html 目录 前言 搭建项目及其它准备工作 创建数据库 创建Koa2项目 安装 ...

  7. python3.4学习笔记(二十五) Python 调用mysql redis实例代码

    python3.4学习笔记(二十五) Python 调用mysql redis实例代码 #coding: utf-8 __author__ = 'zdz8207' #python2.7 import ...

  8. Nodejs学习笔记(十五)—Node.js + Koa2 构建网站简单示例

    前言 前面一有写到一篇Node.js+Express构建网站简单示例:http://www.cnblogs.com/zhongweiv/p/nodejs_express_webapp.html 这篇还 ...

  9. Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第五章:渲染流水线

    原文:Introduction to 3D Game Programming with DirectX 12 学习笔记之 --- 第五章:渲染流水线 学习目标 了解几个用以表达真实场景的标志和2D图像 ...

随机推荐

  1. ReentrantLock学习笔记

    参考:https://www.jianshu.com/p/4358b1466ec9 前言: 先来想象一个场景:手把手的进行锁获取和释放,先获得锁A,然后再获取锁B,当获取锁B后释放锁A同时获取锁C,当 ...

  2. BZOJ 1057:[ZJOI2007]棋盘制作(最大01子矩阵+奇偶性)

    [ZJOI2007]棋盘制作                                          时间限制: 20 Sec 内存限制: 162 MB[题目描述]国际象棋是世界上最古老的博 ...

  3. proteus仿真 引脚显示电平变化但不能显示波形

    proteus仿真 引脚显示电平变化但不能显示波形 原来是没有选择通道问题,proteus默认优先使用A通道才会显示波形,如果优先使用B,C,D通道,需要选择...

  4. TortoiseGit保存用户名和密码的方法

    TortoiseGit在提交或者pull时总会提示你输入用户名密码,非常麻烦,那如何解决呢? 1. 对于TortoiseGit 1.8.1.2及其后的版本,右键选择settings ——> Gi ...

  5. BZOJ 4070 [Apio2015]雅加达的摩天楼 ——分块 SPFA

    挺有趣的分块的题目. 直接暴力建边SPFA貌似是$O(nm)$的. 然后考虑分块,$\sqrt n$一下用虚拟节点辅助连边, 以上的直接暴力连边即可. 然后卡卡时间,卡卡空间. 终于在UOJ上T掉辣. ...

  6. 使用iview如何使左上的添加按钮和右上的搜索框和边框对齐

    使用iview如何使左上的添加按钮和右上的搜索框和边框对齐呢? 效果如下: 使用iview自带的Grid 栅格进行布局,但是由于按钮和搜索框的大小不正好是一个栅格的宽度,所以不是很好跳转,且栅格也不支 ...

  7. C# Log日志工具类

    using System; using System.Collections.Generic; using System.Text; using System.IO; public class Log ...

  8. Spy++使用方法

    原文转自 http://jingyan.baidu.com/article/3a2f7c2e76584a26aed61174.html 1.Spy++ 是Visual Studio 自带的工具(菜单& ...

  9. Scrapy学习-24-集成elasticsearch

    elasticsearch简单集成到scrapy中 使用elasticsearch的python接口处理数据  https://github.com/elastic/elasticsearch-dsl ...

  10. smtp发送邮件记得结尾发送"\r\n.\r\n"

    前段时间老板安排我修复一个邮件服务器后台C程序的bug,这个功能是邮件强制发送功能,从邮件管理后台将垃圾邮件发送出去. 因为服务器是debian系统,所以我用dbg配合日志大致跟踪后,追踪到了读取邮件 ...