将地址绑定到一个套接口在前面的章节中我们准备了足够的知识来创建套接,并且格式化套接口地址。这一章将会这些基础上进行扩展我们将会理解bind是如何工作的,并且如何来正确的使用。 在这一章,我们将会学到下列内容: bind函数如何将一个地址赋给一个套接口 如何由一个已经具有地址的套接口得到本地套接口地址 如何得到同等的套接口地址 bind如何选择用于通信的网络接口 bind函数的目的当我们用socket函数创建套接口时,他们是无名套接口。当演示socket函数时,这些套接口没有地址,但是也可以使用。然而,这些套接口可以工作只是因为他们是用这样的方法来创建的,在同一个Linux内核内。对于连接两个不同的主机的套接口而言,这是不可以的。 一个无名套接口是难于使用的。没有人可以向我们的无名套接口发送信息,因为这就像是一个没有电话号码的电话。因而,程序必须将一个名字绑定到套接口,从而可以通过其他的方法来访问。这就像将一个电话号码指定给某一个新电话,从而可以进行拨打。bind函数允许我们用同样的方式将一个地址赋给一个套接口。 在这一章中名字的内容与主机名没有任何关系。当讨论bind函数时,我们会经常用到名字这个单词,而这是指一个套接口地址。毕竟地址是一个名字的排列。为了避免混淆,在这一章我们会使用地址这个词。 使用bind函数bind函数的目的是将一个套接口地址赋给一个无名套接口。这个函数的概要如下: #include include int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
这个函数接受下列的三个输入参数: 1 由socket函数调用返回的sockfd文件描述符。 2 要赋给套接口的my_addr地址。 3 以字节表示的my_addr的地址长度(参数addrlen)。
如果成功,这个函数会返回0。如果失败则会返回-1,并且将错误号存放在errno变量中。
地址参数必须为一个指向地址结构的指针。我们将会注意到通常所用的地址类型为sockaddr结构类型。这就意味着我们必须使用C不应该的转换操作符来转换我们所传递的指针类型,从而来满足编译器的要求。下面的例子演示了一个建立网络地址的bind函数。在这里我们注意inet_aton以及bind函数的使用。 /* * af_inet.c * * Demonstrating the bind function * by establishing a Specific AF_INET * Socket address */
#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); }
int main(int argc,char **argv,char **envp) { int z; /* Status return code */ int sck_inet; /* Socket */ struct sockaddr_in adr_inet; /*AF_INET*/ int len_inet; /* length */
/* create and IPv4 Inter socket */ sck_inet = socket(AF_INET,SOCK_STREAM,0);
if(sck_inet == -1) { bail("socket()"); } /* create an AF_INET address */ memset(&adr_inet,0,sizeof adr_inet); adr_inet.sin_family = AF_INET; adr_inet.sin_port = htons(9000);
inet_aton("127.0.0.24",&adr_inet.sin_addr);
len_inet = sizeof adr_inet;
/* now bind the address to the socket */ z = bind(sck_inet,(struct sockaddr *)&adr_inet,len_inet);
if(z==-1) { bail("bind()"); } /* display all of our bound sockets */ system("netstat -pa --tcp 2>/dev/null |" "sed -n '1,/^Proto/p;/bind/p'"); close(sck_inet); return 0; } 这个程序的输出结果如下所示: Active Internet connections (servers and established) Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name tcp 0 0 127.0.0.24:9000 *:* CLOSE 934/bind
获得套接口地址 如果我们编写的C库函数接收一个套接口作为输入参数,这时我们并不知道这个套接口的地址是多少。这时我们的函数并没有创建这个套接口,除非这个套接口是作为输入传递给我们的函数的,否则我们并不会知道这个地址。函数getsockname(2)函数允许我们获得这个地址。
getsockname函数概要如下: int getsockname(int s, struct sockaddr *name, socklen_t *namelen)
这个函数接收下列的输入参数:
1 套接口s查询套接口地址 2 指向接收缓冲区的指针(参数name) 3 指向最大长度变量的指针。这个变量以字节方式提供了可以为缓冲区所接受的最大长度。这个值是由实际写入接收缓冲区的字节数来进行更新的。
注意,也bind函数相类似,getsockname使用通用地址结构sockaddr,这是因为他可以用于多种套接口类型。这就意味着我们可能需要在参数中提供的指针上执行C语言转换操作。
长度参数namelen指定了可以在参数中接收的最大字节长度。然而,在返回给调用者之前,namelen的值会被重写来指明有多少字节实际写入输入缓冲区。这会小于或是等于所提供的原始值。
如果函数调用成功则会返回0值。如果发生错误,则会返回-1,错误原因将会存放在变量errno中。
编写一个sock_addr()函数
为了演示getsockname的用法,下面提供一个小函数,这个函数接收一个套接口描述符作为输入。这个函数通过调用getsockname,然后向调用者提者返回一个格式化的字符串,从而可以用在printf调用中。
/* * sckname.c * * Demonstrate getsockname(2): */
#include #include #include #include #include #include #include #include #include
/* * this saves lines of code later: */ static void bail(const char *on_what) { perror(on_what); /* report error */ exit(1); /* exit programming */ }
/* * this function accepts as input a socket * for which a socket address must be * is converted into a string and returned * * if an error occurs,NULL is returned. */ char *sock_addr(int s,char *buf,size_t bufsize) { int z; /* status return code */ struct sockaddr_in adr_inet; /* AF_INET */ int len_inet; /* length */
/* * obtain the address of the socket: */ len_inet = sizeof adr_inet;
z = getsockname(s,(struct sockaddr *)&adr_inet,&len_inet);
if(z==-1) return NULL; /* failed */
/* * convert address into a string * form that can be displayer: */ snprintf(buf,bufsize,"%s:%u", inet_ntoa(adr_inet.sin_addr), (unsigned)ntohs(adr_inet.sin_port)); return buf; }
/* * main program */ int main(int argc,char **argv,char **envp) { int z; /* status return code */ int sck_inet; /* socket */ struct sockaddr_in adr_inet; /* AF_INET */ int len_inet; /* length */ char buf[64]; /* work buffer */
/* * create an IPv4 internet socket: */ sck_inet = socket(AF_INET,SOCK_STREAM,0); if(sck_inet == -1) bail("socket()");
/* * create an AF_INET address: */ memset(&adr_inet,0,sizeof adr_inet); adr_inet.sin_family = AF_INET; adr_inet.sin_port = htons(9000); inet_aton("127.0.0.24",&adr_inet.sin_addr); len_inet = sizeof adr_inet;
/* * now bind the address to the socket: */ z = bind(sck_inet,(struct sockaddr *)&adr_inet,len_inet);
if(z==-1) bail("bind()");
/* * now test our sock_addr() function: */ if(!sock_addr(sck_inet,buf,sizeof buf)) bail("sock_addr()"); printf("Address is '%s'\n",buf); close(sck_inet); return 0; }
这个函数的执行结果如下: $ ./sckname Address is '127.0.0.24:9000' $
获得点套接口地址 在最后的部分,我们将会看到函数getsockname在获得一个套接口地址是相当有用的。然而,当我们的代码希望确定我们的套接口连接到哪一个远程套接口地址时需要花费相当的时间。确定一个套接口的远程地址就像当我们接到一个电话时我们要查找出拨打电话人的电话号码一样。
来完成这个任务是getpeername(2)函数。当我们开始检测和编写一个服务器代码时这个函数是相当有用的。在这里进行介绍是因为他与getsockname相类似。getpeername函数概要如下: #include int getpeername(int s, struct sockaddr *name, socklen_t *namelen);
在这里我们可看到这个函数的参数与getsockname函数完全相同。
下面的代码定义了一个名为peer_addr()的函数。这个代码的设计与前面的sock_addr()函数相类似,但是这并不是一个完整的代码,因为这里只是显示了函数本身的代码,而没有主程序。
/* * getpeer.c * * Demonstrate getpeername(2): */
#include #include #include #include #include #include #include #include #include
/* * this function accepts as input a socket * for which a peer socket address must be * is converted into a string and returned * * if and error occurs,NULL is returned */ char *peer_addr(int s,char *buf,size_t bufsize) { int z; /* status return code */ struct sockaddr_in adr_inet; /* AF_INET */ int len_inet; /* length */
/* * obtain the address of the socket: */ len_inet = sizeof adr_inet;
z = getpeername(s,(struct sockaddr *)&adr_inet,&len_inet);
if(z==-1) bail("getpeername()");
/* * convert address into a string * form that can be displayed: */ z = snprintf(buf,bufsize,"%s:%u", inet_ntoa(adr_inet.sin_addr), (unsigned)ntohs(adr_inet.sin_port));
if(z==-1) return NULL;
return buf; }
接口与地址
在我们继续套接口编程的其他方面之前,有一个我们必须理解的与套接口地址相关的其他概念。这就是接口地址的概念。
使用我们所熟悉的电话作为例子,想像一个总统办公室,在他的桌子上放有两部电话。使用其中的一个他可以与他的妻子进行联系。另一方面,使用红色的电话,他可以与俄罗斯总统进行联系。在某种意义上说,这两部的电话的每一个,是两个不同网络的接口。他们是: 普通的国内电话网 通过安全线的私有网络
在这个例子中的关键点就在于我们必须使用正确的接口来访问正确的网络。例如,总统不可以使用红色的电话与他的妻子进行联系。同样的,国内电话网也不可以拨通俄罗斯总统的电话。
相类似的方式,当我们的套接口程序要指明当试图与远程套接口建立连接所用的接口时需要花费相当的时间。当我们知道只有一个接口可以访问上的网络时就会变得简单了。
绑定一个指定的接口地址
为了给我们的通信指定一个接口,我们需要执行下面的步骤: 1 使用socket创建我们的套接口 2 使用bind函数将我们希望接受连接的接口IP地址绑定到本地套接口
下面的例子演示了如何指定一个网络接口地址。这些工作必须在套接口通信开始之前完成。 nt z; int sck_inet; /* Socket */ struct sockaddr_in adr_inet; /* AF_INET */ int len_inet; /* length */ sck_inet = socket(AF_INET,SOCK_STREAM,0); if ( sck_inet == -1 ) abort(); /* Failed */ /* Establish address */ memset(&adr_inet,0,sizeof adr_inet); adr_inet.sin_family = AF_INET; adr_inet.sin_port = htons(9000); adr_inet.sin_addr.s_addr("192.168.0.1"); adr_inet.sin_addr.s_addr == INADDR_NONE ) abort(); /* Failed */ len_inet = sizeof adr_inet; z = bind(sck_inet, (struct sockaddr *)&adr_inet, len_inet);
绑定任何接口
我们如何接受任何接口的连接呢?我们可以执行下面的步骤: 1 使用socket创建一个套接口 2 使用bind函数将IP地址INADDR_ANY绑定到套接口
如下面的代码所示: int z; int sck_inet; /* Socket */ struct sockaddr_in adr_inet; /* AF_INET */ int len_inet; /* length */ sck_inet = socket(AF_INET,SOCK_STREAM,0); if ( sck_inet == -1 ) abort(); /* Failed */ /* Establish address */ memset(&adr_inet,0,sizeof adr_inet); adr_inet.sin_family = AF_INET; adr_inet.sin_port = htons(9000); adr_inet.sin_addr.s_addr = htonl(INADDR_ANY); if ( adr_inet.sin_addr.s_addr == INADDR_NONE ) abort(); /* Failed */ len_inet = sizeof adr_inet; z = bind(sck_inet, (struct sockaddr *)&adr_inet, len_inet);
| | | |