Unix域协议并不是一个实际的协议族,而是在单个主机上执行客户/服务器通讯的一种方式,单个主机上执行通信,也就是所谓的进程间通信(IPC),所以Unix域套接字协议可以视作IPC方法之一。
Unix域提供两类套接字:字节流套接字(类似TCP)和数据报套接字(类似DUP)。
Unix域中用于标识客户和服务器的协议地址是普通文件系统中的路径名(但需要和Unix域套接字关联起来),否则无法读写这些文件。回忆一蛤,IPv4的协议地址由一个32位地址和16位端口号构成,IPv6协议地址则由一个128位地址和16位端口号组成。
1. Unix域套接字地址结构:
#include <sys/un.h>
struct sockaddr_un
{
sa_family_t sum_family;// AF_LOCAL或者AF_UNIX
char sun_path[104];//字符串指代路径(null终止)
};
sun_path表示与套接字关联的地址,以NULL结尾,如果未指定地址则通过以空字符串作为路径名指示,也就是说sun_path[0]值为0,这个效果就好像Ipv4的INADDR_ANY和IPv6的ADDR_ANY_INIT常值。
2. socketpair函数
这个是UNIX域套接字特有的函数,它创建两个随后连接起来的套接字。
#include <sys/socket.h>
int socketpair(int family , int type , int protocol ,int sockfd[2]);
//返回:成功则为0,出错则为-1
family: 为AF_LOCAL或者AF_UNIX。
type:既可以是SOCK_STREAM也可以是SOCK_DGRAM。
protocol:必须为0。
sockfd[2]:新创建的两个套接字别在sockfd[0]和sockfd[1]中返回。
当设置type参数为SOCK_STREAM时,得到的结果就叫做流管道这与pipe创建的普通UNIX管道类似,差别在于流管道是全双工的,也就是说,两个描述符既可读也可写。回忆一蛤,用pipe创建的匿名管道,pipefd[0]用于读操作,pipefd[1]用于写操作。
3. UNIX域套接字编程:Unix域字节流客户/服务器程序
当用于UNIX域套接口时,套接口函数中存在一些差异和限制:
(1)由bind创建的路径名缺省访问权限应为0777(属主用户、组用户和其他用户都可读、可写并可执行),并按照当前umask值进行修正。
(2)与UNIX域套接口关联的路径名应该是一个绝对路径名,而不是一个相对路径名。
(3)在connect调用中指定的路径名必须是一个当前捆绑在某个打开的UNIX域套接口上的路径名,而且它们的套接口类型(字节流或数据报)也必须一致。
(4)调用connect连接一个UNIX域套接口涉及的权
16位源都口号,16为目的端口号用于寻找发送端和接收端的应用进程,加上IP首部的源端IP及终端IP,唯一的确认一个TCP连接。
32位序号:标识发送的数据字节流,标识在这个报文段中的第一个数据字节,2^3 - 1后重新从0开始。包含该主机选择的连接的ISN(Initial Sequence Number),要发送的第一个数据字节序号为ISN+1.
32位确认序号:ACK为1时有效,上次成功收到的数据字节序号+1(如接收到的为1024--2048,则返回2049)。
4位首部长度:首部中32bits字的数目,TCP最多有60字节的长度,除去任选字段,正常为20字节。
6bits:URG紧急指针;ACK确认序号有效;PSH接收方应尽快将此报文段交给应用层;RST重建连接;SYN同步序号,用来发起一个新连接;FIN发端完成发送任务。
16位窗口大小:TCP流量控制,字节数,起始于确认序列号指明的值,接收端期望收到的字节,最大为65535.
16位检验和:包括计算TCP首部和数据综合的二进制反码和检验和。
16位紧急指针:URG为1时有效,正向的偏移量,加上序号字段值表示最后一个字节的序号。
可选字段:例:MSS.
限测试等同于调用open以只读方式访问相应的路径名。
(5)UNIX域字节流套接口类似于TCP套接口:它们都为进程提供一个无记录边界的字节流接口。
(6)如果对于某个UNIX域字节流套接口的connect调用发现这个监听套接口的队列已满,调用就立即返回一个ECONNREFUSED错误。这一点不同于TCP:如果TCP监听套接口的队列已满,TCP监听端就忽略新到达的SYN,而TCP连接发起端将数次发送SYN进行重试。
(7)UNIX域数据报套接口类似UDP套接口:它们都提供一个保留记录边界的不可靠的数据报服务。
(8)在一个未绑定的UNIX域套接口上发送数据报不会自动给这个套接口捆绑一个路径名,这一点不同于UDP套接口:在一个未绑定的UDP套接口上发送UDP数据报导致给这个套接口捆绑一个临时端口。这一点意味着除非数据报发送端已经捆绑一个路径名到它的套接口,否则数据报接收端无法发回应答数据报。类似地,对于某个UNIX域数据报套接口的connect调用不会给本套接口绑定一个路径名,这一点不同于TCP和UDP。
(一)字节流服务器:(5-12)
int main(int argc, char **argv)
{
int listenfd, connfd;
pid_t childpid;
socklen_t clilen;
struct sockaddr_un cliaddr, servaddr;//两个套接字地址结构的数据类型为sockaddr_un
void sig_chld(int);
//第一个参数是AF_LOCAL创建一个unix域字节流套接字
listenfd = Socket(AF_LOCAL, SOCK_STREAM, 0);
//UNIXSTR_PATH常值为/tmp/unix.str
unlink(UNIXSTR_PATH);//unlink该路径名,防止该路径名已存在
bzero(&servaddr, sizeof(servaddr));//初始化套接字地址结构
servaddr.sun_family = AF_LOCAL;//sun_family设置为AF_LOCAL
strcpy(servaddr.sun_path, UNIXSTR_PATH);//路径名复制到sun_path
Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));//bind(),地质结构总大小
Listen(listenfd, LISTENQ);
Signal(SIGCHLD, sig_chld);
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 *///与前面的str_echo相同(第五章)
exit(0);
}
Close(connfd); /* parent closes connected socket */
}
}
````
<div class="se-preview-section-delimiter"></div>
#(二)字节流客户端:(5-4)
<div class="se-preview-section-delimiter"></div>
```c
int main(int argc, char **argv)
{
int sockfd;
struct sockaddr_un servaddr;//sockaddr_un含有服务器地址的套接字地址结构
sockfd = Socket(AF_LOCAL, SOCK_STREAM, 0);//参数AF_LOCAL
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 *///与前面的str_cli相同(第五章)
exit(0);
}
(三)数据报服务器:(8-3)
int main(int argc, char **argv)
{
int sockfd;
struct sockaddr_un servaddr, cliaddr;//两个套接字地址结构的数据类型为sockaddr_un
//第一个参数是AF_LOCAL创建一个unix域数据报套接字
sockfd = Socket(AF_LOCAL, SOCK_DGRAM, 0);
//UNIXSTR_PATH常值为/tmp/unix.str
unlink(UNIXDG_PATH);//unlink该路径名,防止该路径名已存在
bzero(&servaddr, sizeof(servaddr));//初始化套接字地址结构
servaddr.sun_family = AF_LOCAL;
strcpy(servaddr.sun_path, UNIXDG_PATH);
Bind(sockfd, (SA *) &servaddr, sizeof(servaddr));//bind()
dg_echo(sockfd, (SA *) &cliaddr, sizeof(cliaddr));//8-4.dg_echo()函数
}
(四)数据报客户端:(8-7)
int main(int argc, char **argv)
{
int sockfd;
struct sockaddr_un cliaddr, servaddr;
sockfd = Socket(AF_LOCAL, SOCK_DGRAM, 0);
/*-------------------------------------------------------------------------------------*/
//必须显式bind一个路径名到我们的套接字,这样服务器才会回射应答的路径名
bzero(&cliaddr, sizeof(cliaddr)); /* bind an address for us */
cliaddr.sun_family = AF_LOCAL;
strcpy(cliaddr.sun_path, tmpnam(NULL));//tmpnam赋值一个唯一的路径名,bind到该套接字
//没有下面的bind,dg_echo的recvfrom调用会返回一个空的路径名,导致服务器sendto时发生错误
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));//8-7.dg_cli()函数
exit(0);
}