对于寻址,TCP 和 UDP 使用 4 元组:
本地 IP 地址
本地端口号
外部 IP 地址
外部端口号
TCP 要求这些 4 元组具有唯一性。UDP 则不要求。用户程序并非总是了解用于本地地址和本地端口的正确值,因为主机可能驻留在多个网络中。用户不能直接访问已分配的端口号集合。要避免这些问题,请不要指定地址部分,让系统在需要时适当指定这些部分。这些元组的不同部分可以由套接字 API 的不同部分指定:
本地地址和/或本地端口
外部地址和外部端口
调用 accept(3SOCKET) 可以从外部客户机检索连接信息。这样,即使 accept(3SOCKET) 调用方并未进行任何指定,也会为系统指定本地地址和端口。将返回外部地址和外部端口。
调用 listen(3SOCKET) 可以导致选择本地端口。 如果尚未完成显式 bind(3SOCKET) 以指定本地信息,则 listen(3SOCKET) 会指定暂时端口号。
驻留在特定端口的服务可以使用 bind(3SOCKET) 绑定到此端口。如果服务不需要本地地址信息,则此类服务可以不指定本地地址。将本地地址设置为 in6addr_any(<netinet/in.h> 中具有常量值的一个变量)。如果不需要固定本地端口,则调用 listen(3SOCKET) 可以选择端口。 指定地址 in6addr_any 或端口号 0 的过程称为设置通配符。对于 AF_INET,使用 INADDR_ANY 替代 in6addr_any。
通配符地址简化了 Internet 系列中的本地地址绑定。以下样例代码将通过调用 getaddrinfo(3SOCKET) 所返回的特定端口号绑定到套接字,并且未指定本地地址:
#include <sys/types.h> #include <netinet/in.h> ... struct addrinfo *aip; ... if (bind(sock, aip->ai_addr, aip->ai_addrlen) == -1) { perror("bind"); (void) close(sock); return (-1); }
主机上的每个网络接口通常都具有唯一的 IP 地址。带有通配符本地地址的套接字可以接收定向到指定端口号的消息。带有通配符本地地址的套接字还可以接收发送到指定给主机的任何可能地址的消息。要仅允许特定网络中的主机连接到服务器,服务器应绑定相应网络中接口的地址。
同样,也可以不指定本地端口号,此时系统将选择一个端口号。例如,要将特定本地地址绑定到套接字,但是不指定本地端口号,可以按如下方式使用 bind:
bzero (&sin, sizeof (sin)); (void) inet_pton (AF_INET6, "::ffff:127.0.0.1", sin.sin6_addr.s6_addr); sin.sin6_family = AF_INET6; sin.sin6_port = htons(0); bind(s, (struct sockaddr *) &sin, sizeof sin);
系统使用两个条件来选择本地端口号:
小于 1024 (IPPORT_RESERVED) 的 Internet 端口号保留供特权用户使用。非特权用户可以使用大于 1024 的任何 Internet 端口号。最大的 Internet 端口号是 65535。
当前并未将此端口号绑定到其他套接字。
可以通过 accept(3SOCKET) 或 getpeername(3SOCKET) 查找客户机的端口号和 IP 地址。
在某些情况下,由于关联的创建过程分为两个步骤,因此系统用来选择端口号的算法不适用于应用程序。例如,Internet 文件传输协议会指定数据连接必须始终源于同一本地端口。但是,连接到不同的外部端口可以避免重复关联。在这种情况下,如果先前数据连接的套接字仍然存在,则系统会禁止将相同的本地地址和本地端口号绑定到套接字。
要覆盖缺省的端口选择算法,必须在绑定地址之前执行选项调用。
... int on = 1; ... setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &on, sizeof on); bind(s, (struct sockaddr *) &sin, sizeof sin);
通过此调用,可以绑定已处于使用状态的本地地址。此绑定并不违反唯一性要求。在连接时,系统仍会验证任何其他具有相同本地地址和本地端口的套接字不具有相同的外部地址和外部端口。如果关联已经存在,则会返回错误 EADDRINUSE。