"UDP datagrams only" C 中的套接字

"UDP datagrams only" socket in C

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

的任何部分

我已经创建了套接字

sokt_fd=socket(AF_INET, SOCK_RAW, IPPROTO_UDP)

此外,我准备了UDP header。

  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
    };
    

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

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));

插座API专为不同的寻址系列而设计,其他是红外线和蓝牙。由于 AF_INET 只是家族之一,因此 API 函数在参数中使用一般 sockaddr 类型。

在著名书籍 Unix 网络编程,套接字网络 API 的第 3 章 "Sockets Introduction" 中对此有很好的解释(理查德·史蒂文斯 (Richard Stevens) 等人的第 1 卷。阿尔。让我引用:

Most socket functions require a pointer to a socket address structure as an argument. Each supported protocol suite defines its own socket address structure. The names of these structures begin with sockaddr_ and end with a unique suffix for each protocol suite.

对于 IP(Internet 协议)套件,结构是 sockaddr_in 因此,由于您的示例在创建套接字时指定了 AF_INET 地址系列,因此您将使用更多具体 sockaddr_in 结构而不是更通用的 sockaddr。套接字 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));

地址 API 确实想成为面向对象的,但不得不处理 C 不是 OO 语言的事实。 sockaddr 可以看作是 "base class" 和 bind、connect、sendto、recvfrom 等需要地址时使用的参数类型。但是,您必须提供与您正在使用的 套接字域 相匹配的 "subclassed" 地址。这是因为 Berkeley 套接字可用于范围广泛且可扩展的协议。 IPv4 和 IPv6 是最典型的,但基于 UNIX 的安装也支持套接字作为文件系统对象(按路径 "addressed"),例如,管理程序驱动程序可以安装对特殊 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分别代表"host to network (short)"和"host to network (long)"。你需要这个,因为曾经有一段时间网络驱动程序太笨了,无法抽象出机器的字节顺序,我们无法及时回过头来修复它们。 (网络字节序为大端。)