socket的bind函数
转载自 https://blog.csdn.net/tianxiawuzhei/article/details/51078371
-
在最开始接触bind的时候,只是在写基于tcp的server端的时候,知道在listen之前需要先bind一下,用来确保socket能在某个固定的端口监听。而bind的时候,函数参数中的端口填自己将要绑定的端口就行;而IP地址,需要填本机的IP,但是也可以用一个宏INADDR_ANY代替,用这个宏就可以不用查找本机的IP,它就可以代替本机的IP。当时只觉得这个INADDR_ANY比较神奇,但是由于当时觉得用起来很方便,也没出啥问题,也就没有再深究。
但是最近在做RTSP服务器的时候,有种特殊的应用,导致我不得不对bind这个函数仔细地看一下。
我们知道无论是UDP还是TCP,socket都会与一个本地的IP和端口想对应,我们往往把这个IP和端口称之为socket的源地址和源端口。当我们作为客户端利用socket去发送数据时,很少会去考虑这个源地址和源端口到底是什么,我们更关心的是它的目的地址和端口。我们往往只有在监听的时候,才去考虑这个源端口,所以我们在监听的时候会去用bind。当我们bind之后,内核就会将这个socket的源端口锁定到我们设定的端口上。但是这就有一个问题,这个bind绑定端口,是将本来没有源端口的socket绑定到我们指定的端口上,还是将一个已经分配了端口的socket重定向到我们指定的端口上呢?
在《UNIX网络编程》这本书中提到:“如果一个TCP客户或者服务器未曾调用bind捆绑一个端口,当调用connect或listen时,内核就要为相应的套接字选择一个临时接口。”从这句话中可以判断出,其实在调用socket函数创建socket时,内核还并未给socket分配源地址和源端口。而对于UDP,我猜测在调用sendto发送数据时,在未捆绑端口的情况下,内核也会随机分配端口。
而我遇到的特殊应用要求我在用UDP发送数据之前要告诉对方我的发送端口,这也就意味着我在sendto之前必须要捆绑端口,因此我在发送数据之前就得调用bind函数绑定一下端口了。但是我就在想内核既然有随机分配端口的能力,而我需要的也只是让它绑定一下而不用绑定在固定端口的业务,socket中应该能够提供这种业务。然后果然我发现bind就具备这种能力,当bind的参数中端口地址为0的时候,这时候就是由内核分配端口。这样我就不用考虑端口地址重复的问题,而放心的把这个问题交给内核处理了。
就在发现bind的这个机制的同时,我发现其实bind对于源地址也同样具备这种处理方式,当系统具有多IP(多网卡)的情况,当我们把bind函数中的ip参数置0时,就是由内核自己选择分配IP。而之前一直觉得很神奇的INADDR_ANY其实一点也不神奇,它的值其实就是0。所以当我们只有单一IP的时候,我们就可以用INADDR_ANY去代替那个单一的IP,因为内核分配的时候只能选择这一个IP。从而造成了INADDR_ANY就是本机IP的现象。 -
无连接的socket的客户端和服务端以及面向连接socket的服务端通过调用bind函数来配置本地信息。使用bind函数时,通过将my_addr.sin_port置为0,函数会自动为你选择一个未占用的端口来使用。
Bind()函数在成功被调用时返回0;出现错误时返回”-1”并将errno置为相应的错误号。需要注意的是,在调用bind函数时一般不要将端口号置为小于1024的值,因为1到1024是保留端口号,你可以选择大于1024中的任何一个没有被占用的端口号。有连接的socket客户端通过调用Connect函数在socket数据结构中保存本地和远端信息,无须调用bind(),因为这种情况下只需知道目的机器的IP地址,而客户通过哪个端口与服务器建立连接并不需要关心,socket执行体为你的程序自动选择一个未被占用的端口,并通知你的程序数据什么时候打开端口。(当然也有特殊情况,linux系统中rlogin命令应当调用bind函数绑定一个未用的保留端口号,还有当客户端需要用指定的网络设备接口和端口号进行通信等等)
总之:
1.需要在建连前就知道端口的话,需要 bind
2.需要通过指定的端口来通讯的话,需要 bind具体到上面那两个程序,本来用的是TCP,客户端就不用绑定端口了,绑定之后只能运行一个client的程序属于自己人为设定的障碍,而从服务器那边得到的客户机连接端口号(是系统自动分配的)与这边客户机绑定的端口号根本是不相关的,所以客户端绑定也就失去了意义。
首先,服务器和客户端都可以bind,bind并不是服务器的专利。
客户端进程bind端口: 由进程选择一个端口去连服务器,(如果默认情况下,调用bind函数时,内核指定的端口是同一个,那么调用多个调用了bind()的client程序,会出现端口被占用的错误)注意这里的端口是客户端的端口。如果不分配就表示交给内核去选择一个可用端口。
客户端进程bind IP地址:相当于为发送出去的IP数据报分配了源IP地址,但交给进程分配IP地址的时候(就是这样写明了bind IP地址的时候)这个IP地址必须是主机的一个接口,不能分配一个不存在的IP。如果不分配就表示由内核根据所用的输出接口来选择源IP地址。一般情况下客户端是不用调用bind函数的,一切都交给内核搞定,YES!
服务端进程bind端口:基本是必须要做的事情,比如一个服务器启动时(比如freebsd),它会一个一个的捆绑众所周知的端口来提供服务,同样,如果bind了一个端口就表示我这个服务器会在这个端口提供一些“特殊服务”。
服务端进程bind IP地址:目的是限制了服务端进程创建的socket只接受那些目的地为此IP地址的客户链接,一般一个服务器程序里都有
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 只是针对IP4,IP6代码不太一样
这样一句话,意思就是:我不指定客户端的IP,随便连,来者不拒!总之只要你bind时候没有指定哪一项(置为0),内核会帮你选择。
#define FINGER_PORT 79
#define bzero(ptr, size) memset (ptr, 0, size)
/* Create a TCP connection to host and port. Returns a file
* descriptor on success, -1 on error. */
int
tcpconnect (char *host, int port)
{
struct hostent *h;
struct sockaddr_in sa;
int s;
/* Get the address of the host at which to finger from the
* hostname. */
h = gethostbyname (host);
if (!h || h->h_length != sizeof (struct in_addr)) {
fprintf (stderr, "%s: no such host\n", host);
return -1;
}
/* Create a TCP socket. */
s = socket (AF_INET, SOCK_STREAM, 0);
//注意这里的bind,这个是客户端代码
/* Use bind to set an address and port number for our end of the
* finger TCP connection. */
bzero (&sa, sizeof (sa));
sa.sin_family = AF_INET;
sa.sin_port = htons (0); /* tells OS to choose a port */
sa.sin_addr.s_addr = htonl (INADDR_ANY); /* tells OS to choose IP addr */
if (bind (s, (struct sockaddr *) &sa, sizeof (sa)) < 0) {
perror ("bind");
close (s);
return -1;
}
/* Now use h to set set the destination address. */
sa.sin_port = htons (port);
sa.sin_addr = *(struct in_addr *) h->h_addr;
/* And connect to the server */
if (connect (s, (struct sockaddr *) &sa, sizeof (sa)) < 0) {
perror (host);
close (s);
return -1;
}
return s;
}