2016-09-25 67 views
5

我正在发送一些ping数据包,通过我的linux机器上的一个原始套接字。C原始套接字发送地址的目的是什么?

int sock_fd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); 

这意味着,我指定的IP数据包报头时,我写入套接字(IP_HDRINCL被隐含)。

写入与send插座失败,告诉我我需要指定一个地址。

如果我使用sendto那么它工作。对于sendto我必须指定一个sockaddr_in结构使用,其中包括字段sin_family,sin_portsin_addr

不过,我已经注意到了几件事:

  • sin_familyAF_INET - 创建插座时已经指定。
  • sin_port自然未被使用(端口不是IP的概念)。
  • 只要它是外部地址(IP数据包指定为8.8.8.8,sin_addr指定为1.1.1.1),我使用哪个地址并不重要。

看来sendto的额外字段实际上在很大程度上没有任何用处。那么,是否有一个技术原因,我必须使用sendto而不是send,还是仅仅是API中的疏忽?

回答

6

send写入套接字失败,告诉我我需要指定一个地址。

它失败,因为send()函数只能在连接插座中使用(如所陈述here)。通常,您将使用send()进行TCP通信(面向连接),并且sendto()可用于发送UDP数据报(无连接)。

由于您想发送“ping”数据包,或者更正确的ICMP数据报,它们显然是无连接的,所以您必须使用sendto()函数。

看起来sendto的额外字段实际上用于很大的 程度。那么,是否有技术上的原因,我必须使用sendto 而不是send或者它只是API中的疏忽?

简短的回答:

当您不允许使用send(),那么就只剩下一种选择,叫sendto()

龙答:

它不只是在API中的监督。如果你想用一个普通的套接字发送一个UDP数据报(例如SOCK_DGRAM),sendto()需要你在结构sockaddr_in中提供的有关目标地址和端口的信息,对不对?内核会将该信息插入到结果IP头中,因为结构sockaddr_in只有您指定接收者的位置。换句话说:在这种情况下,内核必须从结构中获取目标信息,因为您不提供额外的IP标头。

由于sendto()不仅用于UDP,还用于原始套接字,它必须是或多或少的“通用”功能,可以覆盖所有不同的使用情况,即使某些参数(如端口号)不相关/最终使用。

例如,通过使用IPPROTO_RAW(它自动暗示IP_HDRINCL),表明您希望自己创建IP标头。因此,sendto()的最后两个参数实际上是冗余信息,因为它们已经包含在您作为第二个参数传递给sendto()的数据缓冲区中。请注意,即使将IP_HDRINCL用于原始套接字,如果将相应的字段设置为0,内核也会填入IP数据报的源地址和校验和。

如果你想编写自己的ping程序,你也可以在你的socket()功能从IPPROTO_RAW改变的最后一个参数IPPROTO_ICMP并让内核创建IP报头的你,让你有一个东西少操心。现在您可以轻松了解两个sendto()-参数*dest_addraddrlen如何再次变得有意义,因为它是唯一提供目标地址的地方。

语言和API非常陈旧,并随着时间的推移而增长。从今天的角度来看,一些API看起来很怪异,但是如果不破坏大量的现有代码,您就无法更改旧界面。有时你必须习惯于多年或几十年前定义/设计的东西。

希望能回答你的问题。

1

当套接字处于TCP SOCK_STREAM连接状态时,使用send()调用。

从手册页:

在send()调用可用于只有当插座连接器处于连接状态 (使预期的接收者是已知的)。

由于您的应用程序显然不与任何其他套接字连接,我们不能指望send()工作。