【问题标题】:"UDP datagrams only" socket in CC中的“仅UDP数据报”套接字
【发布时间】:2015-03-02 03:25:49
【问题描述】:

在 Linux、Ubuntu 14.04 中: 我正在编写一个实现套接字的代码来发送纯 UDP 数据报,其中包括 UDP 标头+有效负载,没有 IP 标头的任何部分。

我已经创建了套接字

sokt_fd=socket(AF_INET, SOCK_RAW, IPPROTO_UDP)

另外,我已经准备好了 UDP 标头。

  1. 我想把IP封装过程留给内核。

  2. 我想通过任何可用的 IP 接口发送数据报。 (我不想指定源IP,也把这个任务交给内核)。

  3. 发送数据报前是否需要指定目的IP地址。

  4. 我必须使用“sendto()”命令发送数据报;我必须如何填充“sockaddr”数据结构?

    #include <netinet/in.h>
    struct sockaddr
    {
        unsigned short    sa_family;// address family, AF_xxx
        char              sa_data[14];// 14 bytes of protocol address
    };
    

【问题讨论】:

  • 根据 Stevens UNIX 网络编程IPPROTO_UDP 不能与 SOCK_RAW 一起使用。如果您想创建自己的 UDP 数据包并让内核将它们封装在 IP 数据包中,那么您应该使用 IPPROTO_RAWSOCK_RAW
  • 我可以像这样创建套接字吗:sokt_fd=socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); ??
  • 是的。这将创建一个未绑定到本地地址的无连接 UDP 套接字。

标签: c linux network-programming


【解决方案1】:

不要使用sockaddr 结构。请改用 sockaddr_in 并在必须将 sockaddr* 传递给函数时强制转换它。

struct sockaddr_in myaddr;
int s;

myaddr.sin_family = AF_INET;
myaddr.sin_port = htons(3490);
inet_aton("63.161.169.137", &myaddr.sin_addr.s_addr);

s = socket(PF_INET, SOCK_STREAM, 0);
bind(s, (struct sockaddr*)myaddr, sizeof(myaddr));

socket API 是为不同的寻址系列设计的,其他的是红外线和蓝牙。由于 AF_INET 只是系列之一,API 函数在参数中使用通用的sockaddr 类型。

【讨论】:

  • 为什么?在 sendto 的手册页中,他们使用了 sokaddr 结构
  • 我发现一些代码示例使用了 sockaddr_in ,然后进行了转换,但我不知道为什么。
【解决方案2】:

Richard Stevens 等人着名的Unix Network Programming, The Sockets Networking API (Volume 1) 一书的第 3 章“Sockets Introduction”对此做了很好的解释。人。让我引用:

大多数套接字函数都需要一个指向套接字地址结构的指针 作为论据。每个支持的协议套件都定义了自己的套接字 地址结构。这些结构的名称以 sockaddr_ 并以每个协议套件的唯一后缀结尾。

对于 IP(Internet 协议)套件,结构为 sockaddr_in,因此,由于您的示例在创建套接字时指定了 AF_INET 地址系列,因此您将使用更具体的 sockaddr_in 结构而不是更通用的结构袜子地址。套接字 API 出于效率考虑,在签名原型中使用了更通用的 sockaddr 指针。

关于使用send()sendto(),我发现sendto() 更常用于UDP,send() 更常用于TCP 套接字。因此,要回答上面 #3 中的问题,对于 UDP,您不必预先指定目标地址,而是将其作为参数提供给 sendto()

对于给定的udp_datagramdatagram_length,您的代码可能如下所示:

uint32_t       address = inet_addr("1.2.3.4");   // can also provide hostname here
uint16_t       port    = 27890;
sockaddr_in_t  dest_addr;
memset(*dest_addr, 0, sizeof(dest_addr));

dest_addr.sin_family = AF_INET;
dest_addr.sin_port   = htons(port);
dest_addr.sin_addr.s_addr = htonl(address);

sendto(socket_fd, 
       (const char*)upd_datagram, 
       datagram_length,
       0,
       reinterpret_cast<sockaddr_t*>(&dest_addr),
       sizeof(dest_addr));

【讨论】:

    【解决方案3】:

    地址 API 确实希望是面向对象的,但必须处理 C 不是 OO 语言的事实。 sockaddr 可以看成是“基类”和bind、connect、sendto、recvfrom等需要地址时使用的参数类型。但是,您必须提供与您正在使用的 socket 域 匹配的“子类”地址。这是因为 Berkeley 套接字可用于广泛且可扩展的协议范围。 IPv4 和 IPv6 是最典型的,但基于 UNIX 的安装也支持将套接字作为文件系统对象(通过路径“寻址”),例如,管理程序驱动程序可以安装对特殊 VM 间或来宾到主机套接字的支持.有关概述,请参阅 man 7 socket

    如果使用 IPv4,则需要使用sockaddr_in。如果使用 IPv6,则需要使用sockaddr_in6。在这两种情况下,您都需要将指针转换为 sockaddr*

    要填写sockaddr_in,您需要执行以下操作:

    struct sockaddr_in inet_addr;
    inet_addr.sin_family = AF_INET;
    inet_addr.sin_port = htons(port);
    inet_addr.sin_addr.s_addr = htonl(ip_address_as_number);
    struct sockaddr* addr = (struct sockaddr*)&inet_addr;
    

    htonshtonl 分别代表“主机到网络(短)”和“主机到网络(长)”。你需要这个,因为曾经有一段时间网络驱动程序太笨了,无法抽象出机器的字节顺序,我们无法及时回去修复它们。 (网络字节序为大端。)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-01-30
      • 2010-12-22
      • 1970-01-01
      • 1970-01-01
      • 2014-05-14
      • 2015-11-26
      相关资源
      最近更新 更多