说在前面
- 环境: ubuntu16.04
- 参考: UNIX网络编程、linux manual page
基本说明
bind函数把一个本地协议地址(32位IPv4地址或128位IPv6地址与16位的TCP/UDP端口号的组合-网际协议)赋予一个套接字。
-
定义
#include <sys/socket.h> int bind(int sockfd, const struct sockaddr *address, socklen_t address_len);
参数说明:
栗子:
struct sockaddr_in servaddr; /* ... */ bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); servaddr.sin_port = htons(13); if(bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr)) < 0) err_sys("bind error");
返回值:
- 成功则返回0;否则返回-1并设置errno;常见的错误为EADDRINUSE,即“Address already in use”,地址已被使用。
-
IP地址和端口号
在调用bind函数时可以同时指定IP地址和端口号(例如上面栗子中的sin_addr和sin_port),也可以仅指定IP地址、或者仅指定端口号,还可以都不指定。
-
若TCP客户端/服务器调用bind时未指定端口,那么调用listen或者connect时,内核将选择一个临时端口。对于TCP客户端,这种情况十分常见;对于服务器,我们通常需要指定端口,客户端获取该端口号后进行连接。
例外:远程过程调用(Remote Procedure Call)服务器。这种服务器由内核选定临时端口,然后该端口通过RPC端口映射器注册。客户在connect前必须通过端口映射器获取临时端口。
-
进程可以把一个特定的IP地址捆绑到对应的套接字上,该IP地址必须为主机的某个网络接口(比如某块网卡,使用ifconfig/ipconfig命令)的IP。
对于TCP客户端,这样的操作其实是指定了IP报文的源IP地址(通过哪个网络接口发送出去);但是客户端通常不绑定IP(一般不调用bind函数),内核会根据外出网络接口来选定源IP地址(比如要连接外网服务器,就不会选择环回地址)
对于TCP服务器,其实就是指定了接收哪一个网络接口的数据(IP数据报的目的IP必须是我们指定的IP)。如果服务器未绑定IP,那么内核会将客户发送的SYN报文的目的IP作为服务器的源IP地址(即服务器发送的数据报的源IP地址)进程指定 结果 IP地址 端口 通配地址 0 内核选择IP地址和端口 通配地址 非0 内核选择IP地址,进程指定端口 本地IP地址 0 进程指定IP地址,内核选择端口 本地IP地址 非0 进程指定IP地址和端口 注意:若让内核选定端口号,bind函数并不会返回内核选定的端口号值。为了得到这个值,我们需要使用getsockname函数
-
实际使用
- 对于IPv4地址,通配地址通常为INADDR_ANY,其值一般为0。
INADDR_ANY是由主机序定义的,需要使用htonl进行转换(即使其值为0)。struct sockaddr_in servaddr; servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
- 对于IPv6地址,
in6addr_any为系统预先分配的变量,已经使用常值IN6ADDR_ANY_INIT对其进行了初始化。#include <netinet/in.h> struct sockaddr_in6 servaddr; servaddr.sin6_addr = in6addr_any;
其他
- 进程捆绑非通配IP地址到套接字的常见栗子
某个web服务器为多个组织提供服务。不同组织IP地址不同,但在同一个子网中(例如组织1为192.69.10.128,组织2为192.69.10.129);然后所有组织的IP都定义为单个网络接口的别名,这样IP层将接收到所有目的地址为其任何一个别名的数据报。每个组织仅需要绑定对应的IP地址即可。
- 另一种实现:捆绑通配地址的单个服务器。当一个连接到达时,通过getsockname获取客户的目的IP地址,服务器根据目的IP地址进行不同的处理。