Linux Socket学习(三)

  无名套接口
套接口并不总是需要有一个地址。例如, socketpair函数创建了两个彼此相连的两个套接口,但是却没有地址。实际上,他们是无名套接口。想像一下冷战期间美国总统与苏联之间的红色电话。 他们任何一端并不需要电话号码,因为他们是直接相连的。同样,socketpair函数也是直接相连的,也并不需要地址。
匿名调用
有时在实际上,连接中的两个套接口中的一个也没有地址。对于要连接的远程套接口,他必须要有一个地址来标识。然而,本地套接口是匿名的。建立起来的连接具有一个有地址的远程套接口和另一个无地址的套接口。

生成地址
有 时我们并不会介意我们的本地址是什么,但是我们需要一个来进行通信。这对于需要连接到一个服务器(例如一个RDBMS数据服务)的程序来说通常是正确的。 他们的本地地址仅为持续的连接所需要。分配确定的地址也可以完成,但是这增加了网络管理的工作。相应的,当地址可用时才会生成地址。
理解域
当Berkeley开发组正在构思BSD套接口接口时,TCP/IP仍在开发之中。与此同时,有一些其他的即将完成的协议正在为不同的组织所使用,例如X.25协议。其他的协议也正在研究之中。
我 们在上一章所见的socketpair函数,以及我们将会看到的socket函数,很明智的允许了其他协议需不是TCP/IP也许会用到的可能性。 socketpair函数的domain参数允许这种约束。为了讨论的方便,让我们先来回顾一下socketpair函数的概要:
#include
#include
int socketpair(int domain, int type, int protocol, int sv[2]);
通常,protocol参数指定为0。0允许操作系统选择我们所选择的domain的所用的默认协议。对于这些规则有一些例外,但是这超出了我们讨论的范围。
现在我们要来解释一下domain参数。对于socketpair函数,这个值必须为AF_LOCAL或者AF_UNIX。在上一章,我们已经指出AF_UNIX宏与旧版的AF_LOCAL等同。然而AF_LOCAL意味着什么?他选择了什么呢?
常量的AF_前缘指明了地址族。domain参数选择要使用的地址族。

格式化套接口地址
每 一个通信协议指明了他自己的网络地址的格式。相应的,地址族用来指明要使用哪种类型的地址。常量AF_LOCAL(AF_UNIX)指明了地址将会按照本 地(UNIX)地址规则来格式化。常量AF_INET指明了地址将会符合IP地址规则。在一个地址族中,可以有多种类型。
在下面的部分中,我们将会检测各种地址族的格式以及物理布局。这是需要掌握的重要的一部分。人们使用BSD套接口接口时所遇到的困难,很多与地址初始化相关。

检测通常的套接口地址
因为BSD套接口地址的开发早于ANSI C标准,所以没有(void *)数据指针来接受任何结构地址。相应的BSD的解决选择是定义一个通用的地址结构。通用的地址结构是用下面的C语言语句来定义的:
#include
struct sockaddr {
    sa_family_t sa_family; /* Address Family */
    char        sa_data[14]; /* Address data. */
};
这里的sa_family_t数据类型是一个无符号短整数,在Linux下为两个字节。整个结构为16个字节。结构元素的sa_data[14]代表了地址信息的其余14个字节。
下图显示了通用地址结构的物理布局:

通用套接口地址结构对于程序而言并不是那样有用。然而,他确实提供了其他地址结构必须适合的引用模型。例如,我们将会了解到所有地址必须在结构中的同样的位置定义一个sa_family成员,因为这个元素决定了地址结构的剩余字节数。

格式化本地地址
这个地址结构用在我们的本地套接口中(我们的运行Linux的PC)。例如,当我们使用lpr命令排除要打印的文件时,他使用一个本地套接口与我们的PC上假脱机服务器进行通信。虽然也可以用TCP/IP协议来进行本地通信,但是事实证明这是低效的。
传统上,本地地址族已经被称这为AF_UNIX域。这是因为这些地址使用本地UNIX文件来作为套接口名字。
AF_LOCAL或者AF_UNIX的地址结构名为sockaddr_un。这个结构是通过在我们的C程序中包含下面的语句来定义的:
#include
sockaddr_un的地址结构:
struct sockaddr_un {
    sa_family_t sun_family;/* Address Family */
    char         sun_path[108]; /* Pathname */
};
结构成员sun_family的值必须为AF_LOCAL或者AF_UNIX。这个值表明这个结构是通过sockaddr_un结构规则来进行格式化的。
结构成员sun_path[108]包含一个可用的UNIX路径名。这个字符数组并不需要结尾的null字节。
在下面的部分中,我们将会了解到如何来初始化一个AF_LOCAL地址与定义他的长度。

格式化传统本地地址
传统本地地址的地址名空间为文件系统路径名。一个进程也许会用任何可用的路径名来命名他的本地套接口。然则为了可用,命名套接口的进程必须可以访问路径名的所有目录组件,并且有权限来在指定的目录中创建最终的套接口对象。
一些程序员喜欢在填充地址结构之前将其全部初始化为0。这通常是通过memset函数来做到的,并且这是一个不错的主意。
struct sockaddr_un uaddr;
memset(&uaddr,0,sizeof uaddr);
这个函数会为我们将这个地址结构的所有字节设置为0。
下面的例子演示了一个简单的初始化sockaddr_un结构的C程序,然后调用netstat命令来证明他起到了作用。在这里我们要先记住在socket与bind上的程序调用,这是两个我们还没有涉及到的函数。
/*****************************************
 *
 * af_unix.c
 *
 * AF_UNIX Socket Example:
 *
 * ******************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include

/*
 * This function reports the error and
 * exits back to the shell:
 */
static void bail(const char *on_what)
{
    perror(on_what);
    exit(1);
}

int main(int argc,char **argv,char **envp)
{
    int z;        /* Status return code */
    int sck_unix;    /* Socket */
    struct sockaddr_un adr_unix;    /* AF_UNIX */
    int len_unix;    /* length */
    const char pth_unix[] = "/tmp/my_sock";    /* pathname */

    /*
     * Create a AF_UNIX (aka AF_LOCAL) socket:
     */
    sck_unix = socket(AF_UNIX,SOCK_STREAM,0);

    if(sck_unix == -1)
        bail("socket()");

    /*
     * Here we remove the pathname for the
     * socket,in case it existed from a
     * prior run.Ignore errors (it maight
     * not exist).
     */
    unlink(pth_unix);

    /*
     * Form an AF_UNIX Address:
     */
    memset(&adr_unix,0,sizeof adr_unix);

    adr_unix.sun_family = AF_LOCAL;
    strncpy(adr_unix.sun_path,pth_unix,
            sizeof adr_unix.sun_path-1)
        [sizeof adr_unix.sun_path-1] = 0;
    len_unix = SUN_LEN(&adr_unix);

    /*
     * Now bind the address to the socket:
     */
    z = bind(sck_unix,
            (struct sockaddr *)&adr_unix,
            len_unix);
    if(z == -1)
        bail("bind()");
    /*
     * Display all of our bound sockets
     */
    system("netstat -pa --unix 2>/dev/null |"
            "sed -n '/^Active UNIX/,/^Proto/P;"
            "/af_unix/P'");
    /*
     * Close and unlink our socket path:
     */
    close(sck_unix);
    unlink(pth_unix);

    return 0;
}
上面的这个例子的步骤如下:
1 在第28行定义了sck_unix来存放创建的套接口文件描述符。
2 在第29行定义了本地地址结构并且命名为adr_unix。这个程序将会用一个AF_LOCAL套接口地址来处理这个结构。
3 通过调用socket函数来在第37行创建了一个套接口。在第39行检测错误并报告。
4 在第48行调用unlink函数。因为AF_UNIX地址将会创建一个文件系统对象,如果不再需要必须进行删除。如果这个程序最后一次运行时没有删除,这条语句会试着进行删除。
5 在第53行adr_unix的地址结构被清0。
6 在第55行将地址族初始化为AF_UNIX。
7 第57行到第59行向地址结构中拷贝路径名"/tmp/my_sock"。在这里使用代码在结构中添加了一个null字节,因为在第61行Linux提供了宏SUN_LEN()需要他。
8 在第61行计算地址的长度。这里的程序使用了Linux提供的宏。然而这个宏依赖于adr_unix.sun_path[]结构成员的一个结束字符。
9 在第66行到68行调用bind函数,将格式化的地址赋值给第37行创建的套接口。
10 在第76行调用netstat命令来证明我们的地址已绑定到了套接口。
11 在第83 行关闭套接口。
12 当调用bind函数时为套接口所创建的UNIX路径名在第66行被删除。
在 第61行将长度赋值给len_unix,在这里使用了SUN_LEN()宏,但是并不会计算拷贝到adr_unix.sun_path[]字符数组中的空 字节。然而放置一个空字节是必要的,因为SUN_LEN()宏会调用strlen函数来计算UNIX路径名的字符串长度。
程序的执行结果如下:
$ ./af_unix
Active UNIX domain sockets (servers and established)
Proto RefCnt Flags    Type     State I-Node PID/Program  name Path
unix 0      []        STREAM          104129 800/af_unix      /tmp/my_sock
$

格式化抽象本地地址
传统AF_UNIX套接口名字的麻烦之一就在于总是调用文件系统对象。这不是必须的,而且也不方便。如果原始的文件系统对象并没有删除,而在bind调用时使用相同的文件名,名字赋值就会失败。
Linux 2.2内核使得为本地套接口创建一个抽象名了成为可能。他的方法就是使得路径名的第一个字节为一个空字节。在路径名中空字节之后的字节才会成为抽象名字的一部分。下面的这个程序是上一个例子程序的修改版本。这个程序采用了一些不同的方法来创建一个抽象的名字。
/*****************************************
 * af_unix2.c
 *
 * AF_UNIX Socket Example
 * Create Abstract Named AF_UNIX/AF_LOCAL
 * ******************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include

/*
 * This function reports the error and
 * exits back to the shell:
 */
static void bail(const char *on_what)
{
    perror(on_what);
    exit(1);
}

int main(int argc,char **argv,char **envp)
{
    int z;        /* Status return code */
    int sck_unix;    /* Socket */
    struct sockaddr_un adr_unix;    /* AF_UNIX */
    int len_unix;    /* length */
    const char pth_unix[]    /* Abs .Name */
        = "Z*MY-SOCKET*";

    /*
     * Create an AF_UNIX (aka AF_UNIX) socket:
     */
    sck_unix = socket(AF_UNIX,SOCK_STREAM,0);
    if(sck_unix == -1)
        bail("socket()");

    /*
     * Form an AF_UNIX Address
     */
    memset(&adr_unix,0,sizeof adr_unix);
    adr_unix.sun_family = AF_UNIX;
    strncpy(adr_unix.sun_path,pth_unix,
            sizeof adr_unix.sun_path-1)
        [sizeof adr_unix.sun_path-1] = 0;
    len_unix = SUN_LEN(&adr_unix);

    /*
     * Now make first byte null
     */
    adr_unix.sun_path[0] = 0;
    
    z = bind(sck_unix,(struct sockaddr *)&adr_unix,len_unix);
    if(z == -1)
        bail("bind()");
    /*
     * Display all of our bound sockets:
     */
    system("netstat -pa --unix 2>/dev/null |"
            "sed -n '/^Active UNIX/,/^Proto/P;"
            "/af_unix/P'");
    /*
     * Close and unlink our socket path:
     */
    close(sck_unix);
    return 0;
    /*
     * Now bind the address to the socket:
     */
}
这个程序的运行结果如下:
$ ./af_unix2
Active UNIX domain sockets (servers and established)
Proto RefCnt Flags    Type     State I-Node PID/Program name Path
unix 0       []       STREAM         104143 5186/af_unix2    @*MY- SOCKET*
$
从这个输出结果中我们可以看到,套接口地址是以 @*MYSOCKET*的名字出现的。开头的@标志是为netstat命令用来标识抽象UNIX套接口名字。其余的字符是拷贝到字符数组剩余位置的字符。注意@字符出现在我们的Z字符应出现的地方。
整个程序的步骤与前一个程序的相同。然而,地址的初始化步骤有一些不同。这些步骤描述如下:
1 在第31行和第32行定义了套接口抽象名字的字符串。注意字符串的第一个字符为Z。在这个字符串这个多余的字符只是起到占位的作用,因为实际上他会在第6步被一个空字节代替。
2 在第45行通过调用memset函数将整个结构初始经为0。
3 在第47行将地址族设置为AF_UNIX。
4 在第49行使用strncpy函数将抽象名字拷贝到adr_unix.sun_path中。在这里要注意,为了SUN_LEN()宏的使用在目的字符数组的放置了一个结束的空字节。否则就不需要这个结束的空字节。
5 在第53通过Linux所提供的SUN_LEN() C 宏来计算地址的长度。这个宏会在sun_path[]上调用strlen函数,所以需要提供了一个结束字符。
6 这一步是新的:sun_path[]数组的第一个字节被设置为空字节。如果使用SUN_LEN()宏,必须最后执行这一步。
在这一部分,我们了解了如何来创建AF_LOCAL和AF_UNIX的套接口地址。为了计算套接口地址的长度,我们使用SUN_LEN()宏。然而,当计算抽象套接口名字时,我们要十分注意。
 

你可能感兴趣的:(Linux,Socket)