跳转至
阅读量:

socket 地址和端口复用

socket 概念

TCP/UDP socket 是由以下五元组唯一地识别的:

{<protocol>, <src addr>, <src port>, <dest addr>, <dest port>}

对于任意连接,五元组组成不能完全相同。否则操作系统无法区别这些连接。

socket 的协议是在用 socket() 初始化的时候就设置好的。

源地址(source address)和源端口(source port)是在调用 bind() 的时候设置。

目的地址(destination address)和目的端口(destination port)是在调用 connect() 的时候设置。

其中 UDP 是无连接的,UDP socket 可以在未与目的端口连接的情况下使用。但 UDP 也可以在某些情况下先与目的地址和端口建立连接后使用。在使用无连接 UDP 发送数据的情况下,如果没有显式地调用bind(),操作系统会在第一次发送数据时自动将 UDP socket 与本机的地址和某个端口绑定(否则的话程序无法接受任何远程主机回复的数据)。同样的,一个没有绑定地址的 TCP socket 也会在建立连接时被自动绑定一个本机地址和端口。

如果我们手动绑定一个端口,我们可以将socket绑定至端口0,绑定至端口0的意思是让系统自己决定使用哪个端口(一般是从一组操作系统特定的提前决定的端口数范围中),所以也就是任何端口的意思。同样的,我们也可以使用一个通配符来让系统决定绑定哪个源地址(ipv4通配符为0.0.0.0,ipv6通配符为::)。而与端口不同的是,一个socket可以被绑定到主机上所有接口所对应的地址中的任意一个。基于连接在本socket的目的地址和路由表中对应的信息,操作系统将会选择合适的地址来绑定这个socket,并用这个地址来取代之前的通配符IP地址。

在默认情况下,任意两个socket不能被绑定在同一个源地址和源端口组合上。比如说我们将 socketA 绑定在 A:X 地址,将 socketB 绑定在 B:Y 地址,其中A和B是IP地址,X和Y是端口。那么在A==B的情况下X!=Y必须满足,在X==Y的情况下A!=B必须满足。需要注意的是,如果某一个socket被绑定在通配符IP地址下,那么事实上本机所有IP都会被系统认为与其绑定了。例如一个socket绑定了0.0.0.0:21,在这种情况下,任何其他socket不论选择哪一个具体的IP地址,其都不能再绑定在21端口下。因为通配符IP0.0.0.0与所有本地IP都冲突。

SO_REUSEADDR

如果在一个socket绑定到某一地址和端口之前设置了其SO_REUSEADDR的属性,那么除非本socket与产生了尝试与另一个socket绑定到完全相同的源地址和源端口组合的冲突,否则的话这个socket就可以成功的绑定这个地址端口对。这听起来似乎和之前一样。但是其中的关键字是完全。SO_REUSEADDR主要改变了系统对待通配符IP地址冲突的方式。

如果不用SO_REUSEADDR的话,如果我们将socketA绑定到0.0.0.0:21,那么任何将本机其他socket绑定到端口21的举动(如绑定到192.168.1.1:21)都会导致EADDRINUSE错误。因为0.0.0.0是一个通配符IP地址,意味着任意一个IP地址,所以任何其他本机上的IP地址都被系统认为已被占用。如果设置了SO_REUSEADDR选项,因为0.0.0.0:21和192.168.1.1:21并不是完全相同的地址端口对(其中一个是通配符IP地址,另一个是一个本机的具体IP地址),所以这样的绑定是可以成功的。需要注意的是,无论socketA和socketB初始化的顺序如何,只要设置了SO_REUSEADDR,绑定都会成功;而只要没有设置SO_REUSEADDR,绑定都不会成功。

评论