IPv4 仅支持40亿个独特的地址,并且由于它们分配得不够高效,我们现在正在耗尽这些地址。IPv6 支持
$3.4 * 10^{38}$ 个可能的地址。IPv6 提供了许多其他改进(比如增加了scope和lifetime等属性),但这是我们网络编程直接受到影响的一个。
有时候,这些地址会匹配,但并不总是如此。如果你处于一个私有的 IPv4 网络中,那么你的路由器很可能执行了网络地址转换(NAT)。在这种情况下,远程服务器看到的是转换后的地址。如果你拥有一个公开可路由的 IPv4 或 IPv6 地址,那么远程服务器看到的地址将与你通过 ipconfig 和 ifconfig 命令报告的地址相匹配。
IPv4的环回地址是127.0.0.1,它允许在同台机器上执行的网络程序相互通信。
IPv6的环回地址是::1。它的工作原理与IPv4的环回地址相同。
DNS用于将域名解析成IP地址。这个协议在第5章“主机名解析和DNS”中有详细说明。
最简单的方法是访问一个能为你报告IP地址的网站。例如:
每个IP数据包都有一个本地地址、远程地址、本地端口号、远程端口号和协议类型。操作系统通过记忆这五个属性来确定哪个应用程序应该处理任何给定的传入数据包。
套接字是一个抽象概念,它代表了系统之间通信链路的一个端点。
面向连接的协议在更大的数据流的上下文中发送数据包。无连接协议独立地发送每个数据包,与之前的或之后的任何数据包无关。
UDP被认为是一个无连接协议。每个消息都是独立于它之前或之后的消息发送的。
TCP被认为是一个面向连接的协议。数据是按照顺序作为流发送和接收的。
UDP应用程序在牺牲可靠性的同时,能够获得更好的实时性能。它们还能够利用IP多播的优势。
需要可靠数据流传输的应用程序会从TCP协议中受益。
TCP提供了一些关于可靠性的保证,但没有什么是能够真正保证数据成功传输的。例如,如果有人拔掉了你的调制解调器,没有任何协议能够克服这种情况。
头文件是不同的。套接字本身被表示为有符号整数与无符号整数。当socket()或accept()调用失败时,返回值是不同的。伯克利套接字也是标准文件描述符。这并不总是适用于WinSock。错误代码是不同的,并且以不同的方式检索。还有其他的区别,但这些是我们程序受影响的主要区别。
bind()函数将一个套接字与特定的本地网络地址和端口号关联起来。它几乎总是需要在服务器上使用,而客户端通常不需要。
accept()函数会阻塞,直到新的TCP客户端连接。然后它返回这个新连接的套接字。
客户端或服务器都可以先发送数据。它们甚至可以同时发送数据。在实践中,许多客户端-服务器协议(如HTTP)的工作方式是客户端先发送请求,然后服务器发送响应。
我们使用select()函数来指示哪些套接字准备好了可以被读取,而不会阻塞。
你可以给select()传递一个超时参数。
HTTP,即Web服务器的协议,期望一个空行来表示请求的结束。如果没有这个空行,服务器就不会知道客户端是否会继续发送额外的请求头。
是的。你可以使用select()来确定何时套接字准备好了可以被写入而不会发生阻塞。或者,可以将套接字设置为非阻塞模式。
recv()的返回值可以表明一个套接字是否已经被断开连接。
不。TCP是一个流式协议。没有办法知道从一次recv()调用返回的数据是通过一次还是多次send()调用发送的。
recv(socket_peer, buffer, 4096, 0);
printf(buffer);
这段代码有什么问题吗?另外,看看这段代码有什么问题:
recv(socket_peer, buffer, 4096, 0);
printf("%s", buffer);
由recv()返回的数据不是以空字符终止的!上述两个代码片段可能会导致printf()读取超出recv()返回的数据末尾。此外,在第一个代码示例中,接收到的数据可能包含格式说明符(例如%d),这将导致额外的内存访问违规。
getaddrinfo() 是用于此目的的函数。
getnameinfo() 函数可以用来将地址转换回名称。
有时你会得到相同的名称,但并不总是这样。这是因为正向和反向查找使用的是独立的记录。同时,许多名称可能指向同一个地址,但那个地址只能有一个记录指向一个单一的名称。
A记录类型返回IPv4地址,而AAAA记录类型返回IPv6地址。
MX记录类型用于返回邮件服务器信息。
如果getaddrinfo()正在进行名称查找,它通常会阻塞。在最坏的情况下,可能需要向不同的DNS服务器发送许多UDP消息,因此这可能会造成明显的延迟。这就是为什么DNS缓存很重要的一个原因。如果你只是使用getaddrinfo()将文本IP地址转换,那么它不应该阻塞。
DNS响应的头部会设置TC位。这表示消息被截断了。查询应该使用TCP重新发送。