TCP/IPv1 老矣! Richard 的去世, 使得这部经典一直无人更新, 虽然书中对IPv4下TCP/IP协议有着清晰的描述, 但是互联网技术日新月异, 一些老的技术不断被改善, 更新以及被取代, 但是书本留在94年成书后就没有任何改变了, 完全没有涉及LINUX和IPv6等等.
回归正题.
在Linux下socket API中的socket选项SO_REUSEADDR完全不同于TCP/IPv1中描述(那么就应该不同与大部分UNIX下的功能).
首先说明本文中IP相同的定义: 除了一般意义上的相同, 通配IP(也就是INADDR_ANY, 或者*)和任何IP相同.
在UDP下:
1. 在LINUX下, 绑定相同端口(port)不同IP不需要任何额外的操作; 而在书中指出, 绑定相同端口不同IP需要在除了第一次绑定外的所有后续绑定前声明SO_REUSEADDR, 并且在这种情况下可以绑定通配IP地址(例如绑定了192.168.1.2:12345 后使用SO_REUSEADDR就可以绑定*:12345, 而在linux这是不可行的).
2. 在LINUX下, 绑定相同端口相同IP需要在每一次绑定(包括第一次)前声明SO_REUSEADDR, 绑定后如果收到多播或者广播的数据报, 那么每一个绑定该IP和端口的socket都可以收到这个数据报, 如果是收到单播的话就只有一个程序收到这个数据报(据说是最后一个绑定该IP:port的socket); 这和书中指出的做法基本相同, 除了某些unix系统(4.4BSD)使用的是SO_REUSEPORT, 而LINUX下无此选项, 另外如果linux需要绑定某个接口的IP和通配IP和相同端口(如192.168.1.2:12345和*:12345), 那么应该每次绑定前都声明SO_REUSEADDR这个选项.
在TCP下:
1. TCP要求正在使用的IP:port(处于TIME_WAIT状态的除外)不能通过任何形式来重用, 以防止端口盗用(TCP是面向链接的协议, 通过一个socket pair四元组确定一个链接, 如果链接的一端两个socket都能绑定同一个IP:port, 那么就破坏了四元组关系, 到底哪个socket应该收到数据就无法确定). 虽然某些系统能够这样做, 但是我们不应该违反约定而去做一些不兼容的coding. LINUX不支持这种操作.
2. 对于绑定不同IP相同端口的情况, LINUX也不需要任何操作, 这种情况和UDP的第一种完全一样, 和TCP/IPv1中的区别也和UDP的第一种完全一样, 因此, 在linux下TCP不能同时绑定某个接口的IP和通配地址的相同端口.
3. 对于处于TIME_WAIT的链接的复用, TCP要求的是可以复用构成TIME_WAIT socket pair中的本机IP:port, 当不可使用这个IP:port去与TIME_WAIT socket pair 的另外一端的IP':port'建立链接. 而在linux下, 这么做是可行的, 只要在每次绑定之前声明SO_REUSEADDR, 那么就可以重用正在TIME_WAIT的socket pair, 反之如果没有每次绑定前都声明好SO_REUSEADDR, 那么无论如何都无法重用正在TIME_WAIT的本机IP:port(即使这个IP:port和另外一端的IP:port构成的socket pair不是TIME_WAIT的socket pair, 并且必须每次都声明SO_REUSEADDR, 即使除了第一次之外都声明了SO_REUSEADDR, 都会绑定失败); 而在TCP/IPv1中描述的是, 只要在后续的绑定前声明好SO_REUSEADDR, 那么就可以绑定正在处于TIME_WAIT的本机IP:port, 只要不要再去connect那个和本机IP:port构成TIME_WAIT socket pair的就好(如果这么做connect会失败).
综合来说, linux下对SO_REUSEADDR的实现应该不同unix下的实现, 在设计网络程序时, 如果我们不想要让别的程序分享我们使用的IP:port, 那么就不要在绑定前设置SO_REUSEADDR, 反之, 如果我们愿意这么做, 那么最后在每次绑定前都先设置好这个值.