我在我的linux机器上通过C中的原始套接字发送一些ping数据包。
int sock_fd = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
这意味着我在写入套接字时指定了IP数据包标头(隐含了IP_HDRINCL
)。
使用send
写入套接字失败,告诉我需要指定一个地址。
如果我使用sendto
则可行。对于sendto
,我必须指定要使用的sockaddr_in
结构,其中包括字段sin_family
,sin_port
和sin_addr
。
但是,我注意到了一些事情:
sin_family
是AF_INET
- 已在创建套接字时指定。 sin_port
自然未使用(端口不是IP的概念)。sin_addr
指定1.1.1.1)。似乎sendto
中的任何额外字段都没有在很大程度上被使用。那么,是否有技术原因导致我必须使用sendto
而不是send
,或者它只是API的疏忽?
答案 0 :(得分:9)
使用
send
写入套接字失败,告诉我需要指定一个地址。
失败,因为send()
函数只能用于连接的套接字(如here所述)。通常,您将send()
用于TCP通信(面向连接),sendto()
可用于发送UDP数据报(无连接)。
因为你想发送" ping"数据包,或者更准确的ICMP数据报,它们是无连接的,你必须使用sendto()
函数。
似乎
sendto
中的额外字段实际上并不是很好用 程度。那么,是否有技术原因我必须使用sendto
而不是send
或者它只是API的疏忽?
简答:
如果您不允许使用send()
,则只剩下一个选项,名为sendto()
。
答案很长:
这不仅仅是API的疏忽。如果要使用普通套接字(例如SOCK_DGRAM
)发送UDP数据报,sendto()
需要您在结构sockaddr_in
中提供的有关目标地址和端口的信息, ?内核会将该信息插入到生成的IP头中,因为struct sockaddr_in
是 only 的位置,您可以在其中指定接收者的身份。换句话说:在这种情况下,内核必须从您的结构中获取目标信息,因为您不提供额外的IP标头。
因为sendto()
不仅用于UDP而且还用于原始套接字,因此它必须是或多或少的"泛型"功能可以覆盖所有不同的用例,即使端口号等参数最终不相关/使用。
例如,通过使用IPPROTO_RAW
(自动隐含IP_HDRINCL
),您可以表明您想要自己创建IP标头。因此sendto()
的最后两个参数实际上是冗余信息,因为它们已经包含在作为第二个参数传递给sendto()
的数据缓冲区中。请注意,即使您对原始套接字使用IP_HDRINCL
,如果将相应字段设置为0
,内核也会填写IP数据报的源地址和校验和。
如果你想编写自己的ping程序,你也可以将socket()
函数中的最后一个参数从IPPROTO_RAW
更改为IPPROTO_ICMP
,让内核为你创建IP头,所以你有一点不用担心。现在,您可以轻松查看两个sendto()
- 参数*dest_addr
和addrlen
如何再次显着,因为它是您提供目标地址的唯一位置。
语言和API非常陈旧,并且随着时间的推移而增长。从今天的角度来看,有些API看起来很奇怪,但是你不能在不破坏大量现有代码的情况下改变旧的接口。有时你只需要习惯几年或几十年前定义/设计的东西。
希望能回答你的问题。
答案 1 :(得分:2)
当套接字处于TCP send()
连接状态时,将使用SOCK_STREAM
调用。
从手册页:
只有当套接字处于连接状态时,才可以使用send()调用 状态(以便知道预期的接收者)。
由于您的应用程序显然无法与任何其他套接字连接,因此我们不能指望send()
能够正常工作。
答案 2 :(得分:0)
除了InvertedHeli的答案,内核还将使用dest_addr
中传递的sendto()
来确定要使用哪个网络接口。
例如,如果dest_addr
的IP为127.0.0.1
,而原始数据包的目标地址为8.8.8.8
,则您的数据包仍将被路由到lo
接口。