简体   繁体   English

C中的“仅UDP数据报”套接字

[英]“UDP datagrams only” socket in C

In Linux, Ubuntu 14.04: I'm writing a code that implements socket to send pure UDP datagrams which includes UDP header+payload, without any part of IP header. 在Linux中,Ubuntu 14.04:我正在编写一个代码,该代码实现套接字以发送纯UDP数据报,其中包括UDP标头+有效载荷,而没有IP标头的任何部分。

I have created the socket 我已经创建了套接字

sokt_fd=socket(AF_INET, SOCK_RAW, IPPROTO_UDP)

Also, I have prepared the UDP header. 另外,我还准备了UDP标头。

  1. I want to leave the IP encapsulation process to the kernel. 我想将IP封装过程留给内核。

  2. I want to send the datagram over any available IP interface. 我想通过任何可用的IP接口发送数据报。 (I do not want to specify the source IP, and also leave this task to the kernel). (我不想指定源IP,也不要将此任务留给内核)。

  3. Do I need to specify the destination IP address before sending the datagram. 发送数据报之前是否需要指定目标IP地址。

  4. I must use "sendto()" command to send the datagram; 我必须使用“ sendto()”命令发送数据报; how I must fill the "sockaddr" data structure? 我必须如何填充“ sockaddr”数据结构?

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

Don't use the sockaddr structure. 不要使用sockaddr结构。 Use sockaddr_in instead and cast it when you have to pass a sockaddr* to a function. 改用sockaddr_in并在必须将sockaddr*传递给函数时进行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));

The socket API is designed for different addressing families, others are Infrared and Bluetooth. 套接字API专为不同的寻址系列而设计,其他是红外和蓝牙。 Since AF_INET is only one of the families the API functions use the general sockaddr type in the parameters. 由于AF_INET只是其中一个系列,因此API函数在参数中使用常规的sockaddr类型。

There is a nice explanation of this in Chapter 3, "Sockets Introduction" in the well-known book Unix Network Programming, The Sockets Networking API (Volume 1) by Richard Stevens et. 理查德·史蒂文斯(Richard Stevens)等人着名的《 Unix网络编程》,《套接字网络API》 (第1卷),在第3章“套接字介绍”中对此进行了很好的解释。 al. 人。 Let me quote: 让我引用:

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. 这些结构的名称以sockaddr_开头,并以每个协议套件的唯一后缀结尾。

For the IP (Internet protocol) suite, the structure is sockaddr_in so it follows that since your example is specifying the AF_INET address family when you created the socket that you would use the more specific sockaddr_in structure instead of the more generic sockaddr. 对于IP(Internet协议)套件,结构为sockaddr_in因此可以理解,由于您的示例在创建套接字时指定了AF_INET地址族,因此您将使用更具体的sockaddr_in结构而不是更通用的sockaddr。 The socket API, as a matter of efficiency uses the more generic sockaddr pointer in the signature prototype. 为了提高效率,套接字API在签名原型中使用了更通用的sockaddr指针。

With regard to using send() versus sendto() , I have found that sendto() is used more commonly with UDP and send() with TCP sockets. 关于使用send()sendto() ,我发现sendto()与UDP一起使用,而send()与TCP套接字一起使用。 Therefore, to answer your question in #3 above, with UDP you don't have to specify the destination address up front, but instead it is supplied as an argument to sendto() . 因此,要回答上面#3中的问题,使用UDP不必预先指定目标地址,而是将其作为sendto()的参数提供。

For a given udp_datagram and datagram_length , your code might look something like this: 对于给定的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));

The address API really wanted to be object-oriented, but had to deal with the fact that C isn't an OO language. 地址API确实希望是面向对象的,但必须处理C不是OO语言这一事实​​。 sockaddr can be seen as the "base class" and the parameter type that bind, connect, sendto, recvfrom, etc. use when they need an address. sockaddr可以看作是“基类”和绑定,连接,sendto,recvfrom等在需要地址时使用的参数类型。 However, you must provide a "subclassed" address matching the socket domain that you're using. 但是,您必须提供一个与您使用的套接字域匹配的“子类”地址。 This is because Berkeley sockets can be used for a wide and extensible range of protocols. 这是因为Berkeley套接字可用于广泛且可扩展的协议。 IPv4 and IPv6 are the most typical, but UNIX-based installs also support sockets as filesystem objects ("addressed" by path), and, for instance, a hypervisor driver can install support for special inter-VM or guest-to-host sockets. IPv4和IPv6是最典型的,但是基于UNIX的安装还支持将套接字作为文件系统对象(按路径“寻址”),例如,虚拟机监控程序驱动程序可以安装对特殊的VM间或来宾到主机套接字的支持。 See man 7 socket for an overview. 有关概述,请参见man 7 socket

If you use IPv4, you need to use sockaddr_in . 如果使用IPv4,则需要使用sockaddr_in If you use IPv6, you need to use sockaddr_in6 . 如果使用IPv6,则需要使用sockaddr_in6 In both cases, you need to cast your pointer to a sockaddr* . 在这两种情况下,都需要将指针转换为sockaddr*

To fill in a sockaddr_in , you need to do something like this: 要填写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;

htons and htonl stand for "host to network (short)" and "host to network (long)", respectively. htonshtonl分别表示“主机到网络(短)”和“主机到网络(长)”。 You need this because there was a time at which network drivers were too dumb to abstract away the machine's endianness and we can't go back in time to fix them. 您之所以需要它,是因为有时网络驱动程序过于笨拙,无法抽象出机器的字节序,因此我们无法及时返回来修复它们。 (The network byte order is big endian.) (网络字节顺序为大字节序。)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM