使用
send
函数向套接字写入数据失败,提示需要指定地址。
这是因为
send()
函数只能用于已连接的套接字(如
此处所述)。通常情况下,您会使用
send()
进行TCP通信(面向连接),而
sendto()
可用于发送UDP数据报(无连接)。
由于您想发送“ping”数据包或更正确地说是ICMP数据报,这些明显是无连接的。因此,您必须使用
sendto()
函数。
在
sendto
中似乎没有额外的字段被广泛使用。那么,为什么我必须使用
sendto
而不是
send
,是技术原因还是API设计上的疏忽?
简短的回答是:当不允许使用
send()
时,只有一种选择,即使用
sendto()
。
详细的回答是:这不仅仅是API设计上的疏忽。如果您想通过使用普通套接字(例如
SOCK_DGRAM
)发送UDP数据报,则
sendto()
需要有关目标地址和端口的信息,这些信息在
sockaddr_in
结构中提供,对吧?内核将把该信息插入到生成的IP头中,因为
sockaddr_in
结构是您指定接收方的
唯一位置。换句话说:在这种情况下,由于您没有提供额外的IP头,内核必须从您的结构中取出目标信息。因为
sendto()
函数不仅适用于UDP,还适用于原始套接字(raw sockets),所以它必须是一个更或多或少"通用"的函数,可以覆盖所有不同的用例,即使一些参数(如端口号)在最终中并没有被使用,也需要涵盖它们。
例如,通过使用
IPPROTO_RAW
(这自动意味着
IP_HDRINCL
),您表明您想要自己创建IP头。因此,
sendto()
的最后两个参数实际上是多余的信息,因为它们已经包含在您作为第二个参数传递给
sendto()
的数据缓冲区中了。请注意,即使您在原始套接字中使用
IP_HDRINCL
,如果将相应字段设置为
0
,内核也会填充您的IP数据报的源地址和校验和字段。
如果您想编写自己的ping程序,还可以将
socket()
函数中的最后一个参数从
IPPROTO_RAW
更改为
IPPROTO_ICMP
,让内核为您创建IP头,这样您就可以减轻一些负担。现在您可以很容易地看到两个
sendto()
参数
*dest_addr
和
addrlen
变得重要了,因为这是您提供目标地址的唯一地方。
语言和API非常古老,并且随着时间的推移不断发展。有些API从今天的角度看可能会很奇怪,但是您不能更改旧接口而不破坏大量现有代码。有时候您只能习惯很多年甚至几十年前就定义/设计的东西。
希望这回答了您的问题。