UNP-UNIX网络编程 第十五章:域协议

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

猜你喜欢

转载自blog.csdn.net/qiangzhenyi1207/article/details/79079313
今日推荐