LinuxSocket学习

LinuxSocket学习
作者:kxj99 编辑:周荣茂

    (一)

    理解套接口

    在我们试着使用套接口之前理解套接口后面的一些内容是很重要的。这一部分描绘出围绕着套接口的一些高级内容。

    定义套接口

    要与使用电话的某人进行交流,我们必须拿起话筒,拨打对方的电话号码,并且等待对方的应答。当我们与对方通话时,就建立了两个通信端点。

    我们的电话,在我们的位置

    远方的对方电话,在他的位置。

    只要我们仍在通话,就我们之间调用两个端点,建立了一条通信线路。

    Linux下的套接口也与电话相类似。套接口代表通信线路中的两个端点。在这两个端点之间存在着数据通信网络。

    在另一个方式上,套接口也与电话类似。当我们要打给某人,我们拨打我们要联系的人的电话号码。套接口有网络地址而不是电话号码。通过指定远程套接口地址,我们的程序可以在我们的本地套接口与远程端点之间建立一条通信线路。

    由此我们可以推断,一个套接口是通信中的一个端点。有许多的Linux函数调用可以操作套接口,而我们将会慢慢的了解这些。

    使用套接口

    也许我们会认为Linux套接口很特殊,因为套接口有一个可以在其上进行操作的特定的函数集合。尽管套接口有一些特殊的属性,但是他与我们十分熟悉的文件描述十分相似。

    例如,当我们使用Linux的open调用打开一个文件,如果open函数调用成功,我们就会得到一个返回的文件描述符。在我们有了这个文件描述符以后,我们的程序可以使用文件描述符来read,write,lseek,以及close打开的指定文件。相似的,当创建了一个套接口时,他与文件描述符十分想似。我们可以使用相同的文件I/O函数来读,写以及关闭套接口。

    然而在套接口与打开的文件之间也存在一些不同。下面列出了其中的一些不同之处:

    我们不可以在套接口上执行lseek函数。

    套接口有与其相关联的地址。文件和管道没有网络地址。

    套接口有可以使用ioctl函数进行查询与设置的不同选项功能。

    为了执行输入或输出,套接口必须处理正确的状态。相反,打开的磁盘文件可以在任何时候进行读取或是写入。

    引用套接口

    当我们使用open函数调用来打开一个新文件时,Linux内核就会返回下一个可用的并且是最小的文件描述符。文件描述符,或者是常称之为文件单元数,是零或者正的整数值,用来引用打开的文件。这个句柄会用在在打开的文件上进行操作的所有函数中。现在我们就会知道文件单元数也可以引用特定的套接口。

    我们的程序已经打开了0,1和2三个文件单元(标准输入,标准输出,标准错误),接下来的程序操作将会被执行。注意内核是如何分配文件描述符的:

    1 调用open函数来打开一个文件

    2 返回文件单元3来引用打开的文件。因为这个单元数当前并没有用,并且是可用的最小的单元数,所以为文件选择了3作为文件单元数。

    3 通过一个合适的函数调用来创建一个新的套接口。

    4 返回文件单元4来引用这个新的套接口。

    5 通过调用open打开另一个文件。

    6 返回文件单元5来引用这个新打开的文件。

    注意:当分配单元数时,Linux内核在文件与套接口之间并没有区别。用一个文件描述符来引用一个打开的文件或是一个网络套接口。

    这就意味着,我们作为一个程序员,可以将套接口当作打开的文件一样来使用。通过文件单元数交互的来引用文件和套接口的能力提供给了我们极大的灵活性。这就意味着read和write函数可以同时在打开的文件和套接口上进行操作。

    套接口与管道的比较

    在我们介绍任何套接口函数之前,我们来回忆一下我们已经熟悉的pipe函数调用。让我们看一下他返回的文件描述符与套接口的不同。下面是由pipe的手册中得到的函数概要:

    #include

    int pipe(int filedes[2]);

    当这个调用成功时,pipe函数会返回两个文件描述符。数组元素filedes[0]包含管道读端的文件描述符。filedes[1]元素是管道写端的文件描述符。两个文件描述符的这种安排提示了在每一端使用文件描述符的通信连接。这与使用套接口有何不同呢?不同就在于pipe函数创建了一个单向的通信线。

    信息只可以写入filedes[1]中的文件单元数,并且只可以从filedes[0]中进行读取。任何向相反方向写入数据的尝试都会得到Linux内核返回的错误。

    另一个方面,套接口允许在两个方向处理通信。例如,一个进程可以使用在文件单元3上打开的套接口向远端进程发送数据。与使用管道不同,同一个本地进程也可以从文件单元3上接收到与其相通信的远端进程发送的数据。

    

    创建套接口

    在这一部分,我们将会看到创建套接口与创建管道一样的容易。虽然有一些我们将会了解到的函数参数。为了能创建成功,这些参数必须提供合适的值。

    socketpair函数概要如下:

    #include

    #include

    int socketpair(int domain, int type, int protocol, int sv[2]);

    sys/types.h文件需要用来定义一些C宏常量。sys/socket.h文件必须包含进来定义socketpair函数原型。

    socketpair函数需要四个参数。他们是:

    套接口的域

    套接口类型

    使用的协议

    指向存储文件描述符的指针

    domain参数直到第2单我们才会解释。对于socketpair函数而言,只需提供C宏AF_LOCAL。

    类型参数声明了我们希望创建哪种类型的套接口。socketpair函数的选择如下:

    SOCK_STREAM

    SOCK_DGRAM

    套接口类型的选择我们将会在第4章谈到。在这一章中,我们只需要简单的使用SOCK_STREAM套接口类型。

    对于socketpair函数,protocol参数必须提供为0。

    参数sv[2]是接收代表两个套接口的整数数组。每一个文件描述符代表一个套接口,并且与另一个并没有区别。

    如果函数成功,将会返回0值。否则将会返回-1表明创建失败,并且errno来表明特定的错误号。

    使用socketpair的例子

    为了演示如何使用socketpair函数,我们用下面的例子来进行演示。

    1: /* Listing 1.1:

    2: *

    3: * Example of socketpair(2) function:

    4: */

    5: #include

    6: #include

    7: #include

    8: #include

    9: #include

    10: #include

    11: #include

    12:

    13: int

    14: main(int argc,char **argv) {

    15: int z; /* Status return code */

    16: int s[2]; /* Pair of sockets */

    17:

    18: /*

    19: * Create a pair of local sockets:

    20: */

    21: z = socketpair(AF_LOCAL,SOCK_STREAM,0,s);

    22:

    23: if ( z == -1 ) {

    24: fprintf(stderr,

    25: "%s: socketpair(AF_LOCAL,SOCK_STREAM,0)\n",

    26: strerror(errno));

    27: return 1; /* Failed */

    28: }

    29:

    30: /*

    31: * Report the socket file descriptors returned:

    32: */

    33: printf("s[0] = %d;\n",s[0]);

    34: printf("s[1] = %d;\n",s[1]);

    35:

    36: system("netstat --unix -p");

    37:

    38: return 0;

    39: }

    演示程序的描述如下:

    1 在第16行声明数组s[2]用来存储用来引用两个新创建的套接口的文件描述符。

    2 在第21行调用socketpair函数。domain参数指定为AF_LOCAL,套接口类型参数指定为SOCK_STREAM,而协议指定为0。

    3 23行的if语句用来测试socketpair函数是否成功。如果z的值为-1,就会向标准错误发送报告,并且在27行退出程序。

    4 如果函数调用成功,控制语句就会转到33,并且在34行向标准输出报告返回的文件单元数。

    5 36行使用system函数来调用netstat命令。命令选项--unix表明只报告Unix套接口,-p选项则是要报告进程信息。

    使用提供的Makefile,我们可以用make命令来编译这个程序:

    $ make 01lst01

    gcc -c -D_GNU_SOURCE -Wall 01LST01.c

    gcc 01LST01.o -o 01lst01

    为了执行这个演示程序,我们可以执行下面的命令:

    $ ./01lst01

    程序的执行结果如下:

    1: $ ./01lst01

    2: s[0] = 3;

    3: s[1] = 4;

    4: (Not all processes could be identified, non-owned process info

    5: will not be shown, you would have to be root to see it all.)

    6: Active UNIX domain sockets (w/o servers)

    7: Proto RefCnt Flags Type . . . I-Node PID/Program name Path

    8: unix 1 [] STREAM . . . 406 - @00000019

    9: unix 1 [] STREAM . . . 490 - @0000001f

    10: unix 1 [] STREAM . . . 518 - @00000020

    11: unix 0 [] STREAM . . . 117 - @00000011

    12: unix 1 [] STREAM . . . 789 - @00000030

    13: unix 1 [] STREAM . . . 549 - @00000023

    14: unix 1 [] STREAM . . .1032 662/01lst01

    15: unix 1 [] STREAM . . .1031 662/01lst01

    16: unix 1 [] STREAM . . . 793 - /dev/log

    17: unix 1 [] STREAM . . . 582 - /dev/log

    18: unix 1 [] STREAM . . . 574 - /dev/log

    19: unix 1 [] STREAM . . . 572 - /dev/log

    20: unix 1 [] STREAM . . . 408 - /dev/log

    21: $

    在我们上面的输入显示中,在第1行调用可执行程序01LST01。第2行和第3行显示了我们在文件描述符3和4上打开套接口。接下来的4到20行是程序中netstat命令的输出。

    尽管这个程序并没有使用创建的套接口来做任何事情,但是他确实演示了套接口的创建。并且他演示了套接口单元数的分配与打开的文件的方式一样。

    在套接口上执行I/O操作

    我们在前面已经了解到套接口可以像任何打开的文件一样向其中写入或是从中读取。在这一部分将我们将会亲自演示这一功能。然而为了试都讨论的完整,我们先来看一下read,write,close的函数概要:

    #include

    ssize_t read(int fd, void *buf, size_t count);

    ssize_t write(int fd, const void *buf, size_t count);

    int close(int fd);

    这些应是我们已经熟悉的Linux的输入/输入函数。通过回顾我们可以看到,read函数返从文件描述符fd中返回最大count字节的输入,存放到buf缓冲区中。返回值代表实际读取的字节数。如果返回0则代表文件结束。

    write函数将我们指定的buf缓冲区中总计count的字节写入文件描述符fd中。返回值代表实际写入的字节数。通常这必须与指定的count参数相匹配。然而也会有一些情况,这个值要count小,但是我们没有必要担心这样的情况。

    最后,如果文件成功关闭close就会返回0。对于这些函数,如果返回-1则表明有错误发生,并且错误原因将会发送到外部变量errno中。为了可以访问这个变量,我们需要在源文件中包含errno.h头文件。

    下面的例子是在套接口的两个方向上执行读取与写入操作。

    /*****************************************

     *

     * Listing 1.2

     *

     * Example performing I/O on s socket pair:

     *

     * ******************************************/

    #include

    #include

    #include

    #include

    #include

    #include

    #include

    int main(int argc,char **argv)

    {

     int z; /* Status return code */

     int s[2]; /* Pair of sockets */

     char *cp; /* A work pointer */

     char buf[80]; /* work buffer */

     /*

     * Create a pair of local sockets:

     */

     z = socketpair(AF_LOCAL,SOCK_STREAM,0,s);

     if(z == -1)

     {

     fprintf(stderr,

     "%s:socketpair(AF_LOCAL,SOCK_STREAM,""0)\n",strerror(errno));

     return 1; /* Failed */

     }

     /*

     * Write a message to socket s[1]:

     */

     z = write(s[1],cp="Hello?",6);

     if(z关闭套接口

    在前面,我们看到如何来创建一对套接口,并且看到如何使用这些套接口来执行最基本的输入与输出操作。我们也可以看到套接口可以使用与通过调用close函数来关闭文件的方式来关闭。现在我们来了解一下关闭套接口所提供的函数。

    当从通过pipe函数创建的管道中读取时,当接收到一个文件结尾时,接收就会认为不会再有要接收的数据。当关闭管道的写端时,文件结束的条件是通过写进程发送的。

    同样的过程也可以用在套接口上。当另一个端点关闭时,接收端就会收到一个文件结束的标识。

    当本地进程希望通知远程端不再接收数据时就会出现问题。如果本地进程关闭了他的套接口,这是可以适用的。然而,如果他希望从远程端接收一个确认信息,这是不可能的,因为现在他的套接口已经关闭了。这样的情况需要一个半关闭套接口的方法。

    shutdown函数

    下面显示了shutdown函数的概要:

    #include

    int shutdown(int s, int how);

    shutdown函数需要两个参数。他们是:

    套接口描述符s指定了要部分关闭的套接口。

    参数how指定要如何关闭这个套接口中。

    如果函数成功则返回0。如果调用失败则会返回-1,错误原因将会发送到errno。

    how的可能值如下:

    值 宏 描述

    0 SHUT_RD 在指定的套接口上不再允许读操作。

    1 SHUT_WR 在指定的套接口上不再允许写操作。

    2 SHUT_RDWR 在指定的套接口上不再允许读写操作。

    注意当how值指定为2时,这个函数的调用与close函数调用相同。

    关闭向一个套接口的写

    下面的代码演示了如何指定在本地的套接口上不再执行写操作:

    int z;

    int s; /* Socket */

    z = shutdown(s, SHUT_WR);

    if ( z == -1 )

     perror("shutdown()");

    关闭套接口的写端解决了一系列难题。他们是:

    清空包含任何要发送的数据的内核缓冲区。通过内核网络软件来缓冲数据来改进性能。

    向远程套接口发送文件结束标识。这就通知远程读进程在这个套接口上不会再向他发送数据。

    保留半关闭套接口为读打开。这就使得在套接口上发送了文件结束标识以后还可以接收确认信息。

    丢弃在这个套接口上的打开引用计数。只有最后在这个套接口上的close函数将会发送一个文件结束标识。

    处理复制的套接口

    如果一个套接口文件描述符通过dup或者是dup2函数来调用进行复制,只有最后的close函数调用可以关闭这个套接口。这是因为另外复制的文件描述符仍处于使用状态。如下面的代码如演示的样子:

    int s; /* Existing socket */

    int d; /* Duplicated socket */

    d = dup(s); /* duplicate this socket */

    close(s); /* nothing happens yet */

    close(d); /* last close, so shutdown socket */

    在这个例子中,第一个close函数调用不会有任何效果。先关闭其中的任何一个都是一样的结果。关闭s或者d将会为同一个套接口保留一个文件描述符。只有通过close函数调用来关闭最后一个存在的文件描述符才会有效果。在这个例子中,关闭d文件描述符关闭了这个套接口。

    shutdown函数避免了这种区别。重复这个例子代码,通过使用shutdown函数解决了这个问题:

    int s; /* Existing socket */

    int d; /* Duplicated socket */

    d = dup(s); /* duplicate this socket */

    shutdown(s,SHUT_RDWR); /* immediate shutdown */

    尽管套接口s也在文件单元d上打开,shutdown函数立刻使得套接口执行关闭操作。这个操作在打开的文件描述符s和d都是同样的效果,因为他们指向同一个套接口。

    这个问题出现的另一个方式就是执行了fork函数调用。任何优先级高于fork操作的套接口都会在子进程中被复制。

    关闭从一个套接口读

    关闭套接口的读取端将会使得待读取的任何数据都会被忽略掉。如果从远程套接口发送来更多的数据,也同样会被忽略掉。然而任何试着从这个套接口进行读取的进程都会返回一个错误。这通常用来强制协议或是调试代码。

    shutdown函数的错误代码如下:

    错误 描述

    EBADF 指定的套接口不是一个可用的文件描述符

    ENOTSOCK 指定的文件描述符不是一个套接口

    ENOTCONN 指定的套接口并没有连接

    从这个表中我们可以看到,对于已连接的套接口应只调用shutdown函数,否则就会返回ENOTCONN错误代码。

    编写一个客户/服务器例子

    现在我们所了解的套接口API的集合已经足够让我们开始一些有趣的尝试了。在这一部分,我们会检测,编译并且测试一个简单的通过套接口进行通信的客户与服务器进程。

    为了使得这个程序尽可能的小,将会启动一个程序,然后复制为一个客户进程与一个服务器进程。子进程将会是客户端程序角色,而原始的父进程将会执行服务器的角色。下图显示了父进程与子进程的关系以及套接口的使用。

    

    父进程是最初启动的程序。他立刻通过调用socketpair函数来生成一对套接口,然后通过调用fork函数将自己复制为两个进程。

    服务器将会接收请求,执行请求,然后退出。类似的客户端将会执行请求,报告服务器响应,然后退出。

    请

    求将会采用strftime函数的第三个参数的格式。这是一个用来格式化日期与时间字符串的格式字符串。服务器将会在接收到请求时得到当前的日期与时间。

    服务器将会使用客户端的请求字符串来将其格式化为最终的字符串,然后发送给客户端。我们先来回顾一个strftime函数的概要:

    #include

    size_t strftime(char *buf,

     size_t max,

     const char *format,

     const struct tm *tm);

    参数buf与max分别指定了输出缓冲区以及最大长度。参数format是一个输入字符串,可以允许我们来格式化日期与时间字符串。最后参数tm用来指定必须来创建输出日期与时间字符串的日期与时间组件。

    /*****************************************

     *

     * Listing 1.3

     *

     * Client/Server Example Using socketpair

     * and fork:

     *

     * ******************************************/

    #include

    #include

    #include

    #include

    #include

    #include

    #include

    #include

    #include

    /*

     * As of RedHat 6.0,these are still not defined:

     */

    #ifndef SHUT_WR

    #define SHUT_RD 0

    #define SHUT_WR 1

    #define SHUT_RDWR 2

    #endif

    /*

     * Main program

     */

    int main(int argc,char **argv)

    {

     int z; /* Status return code */

     int s[2]; /* Pair of sockets */

     char *msgp; /* A message pointer */

     int mlen; /* Message length */

     char buf[80]; /* work buffer */

     pid_t chpid; /* Child PID */

     /*

     * Create a pair of local sockets:

     */

     z = socketpair(AF_LOCAL,SOCK_STREAM,0,s);

     if(z == -1)

     {

     fprintf(stderr,"%s:socketpair(2)\n",strerror(errno));

     exit(1);

     }

     /*

     * Now fork() into two processes:

     */

     if((chpid = fork()) == (pid_t)-1)

     {

     /*

     * Failed to fork into two processes:

     */

     fprintf(stderr,"%s:fork(2)\n",strerror(errno));

     exit(1);

     }

     else if(chpid == 0)

     {

     /*

     * This is child process(client)

     */

     char rxbuf[80]; /*Receive buffer*/

     printf ("Parent PID is %ld\n",(long)getppid());

     close(s[0]); /* Server uses s[1] */

     s[0] = -1; /*Forget this unit */

     /*

     * Form the message and its length:

     */

     msgp = "%A %d-%b-%Y %l:%M %p";

     mlen = strlen(msgp);

     printf("Child sending request '%s'\n",msgp);

     fflush(stdout);

     /*

     * Write a request to the server:

     */

     z = write(s[1],msgp,mlen);

     if(z无名套接口

    套接口并不总是需要有一个地址。例如,

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

    }

  &

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值