问题描述:
在Linux平台下可能存在多个网络接口(网口),创建Socket使用的地址为INADDR_ANY时,表示监听本地0.0.0.0地址,这表示如果本地有多个IP地址时,无论哪个设备发送UDP的套接字消息时,只要端口正确,你都可以捕获到该消息并进行处理。
举例:
例如:当设备A 存在三个网口时,每个网口都有不同的IP地址。
网口一的IP地址为:192.168.1.10
网口二的IP地址为:192.168.1.11
网口三的IP地址为:192.168.1.12
该设备存在服务程序并绑定INADDR_ANY和3002端口后,进入监听状态,此时,有客户端程序B,使用Socket套接字向192.168.1.10和端口3002发送一个UDP消息,设备A都可以收到该UDP消息。
INADDR_ANY 其实就是泛指本机的意思,表示本机的所有IP。
使用示例:
struct sockaddr_in server;
// 设置监听地址
server_addr.sin_family = AF_INET;
// 所有地址0.0.0.0
server_addr.sin_addr.s_addr = INADDR_ANY;
// 主机字节顺序转换成网络字节顺序
server_addr.sin_port = htons(PORT);
遇到问题:
众所周知,UDP(UserDatagramProtocol)消息是面向无连接的,一般我们服务端使用UDP监听时,是用recvfrom接口进行读取消息,但是,如果监听地址使用使用INADDR_ANY此时,我们无法通过此接口获取到发送方的IP地址,以及发送方发送到本地的IP地址(有点拗口)。
简单的说,就是我们无法获取到双方IP地址,在某些需求下,这种情况我们不可再使用recvfrom接口了。
解决办法:
创建Socket时添加参数,使用recvmsg替换recvfrom接口。
具体操作代码:
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include <string.h>
#include <sys/uio.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/errno.h>
#include <netinet/in.h>
#include <sys/socket.h>
#define LOCAL_LISTEN_PORT 4006
#define RECV_BUFF_MAX_SIZE 4096
int main(int argc, char* argv[])
{
/*
// Create Variables
*/
struct iovec iove[1];
struct cmsghdr* cmhp;
struct msghdr message;
/* Source IP address used by the client when sending packets */
struct sockaddr_in saddr;
/* Destination IP address that the client sends to the server */
struct sockaddr_in daddr;
/* Local listening address information */
struct sockaddr_in server_addr;
/* Used to point to the obtained local address information */
struct in_pktinfo *pktinfo = NULL;
/* Calculates the necessary whitespace for a secondary data object. */
char buff[CMSG_SPACE(sizeof(struct in_pktinfo) + CMSG_SPACE(sizeof(int)))] = {0};
/* Control information */
struct cmsghdr *cmh = (struct cmsghdr *)buff;
int on = 1;
int ret, fd, addr_len, recv_len;
/* Message cache */
char buffer[BUFFER_MAX_SIZE] = {0};
/* initialize */
addr_len = sizeof(struct sockaddr_in);
memset(&buffer, 0, sizeof(buffer));
memset(&msg, 0, sizeof(struct msghdr));
memset(&iov, 0, sizeof(struct iovec));
memset(&saddr, 0, sizeof(struct sockaddr_in));
memset(&daddr, 0, sizeof(struct sockaddr_in));
// Set listening address
server_addr.sin_family = AF_INET;
// All addresses are 0.0.0.0
server_addr.sin_addr.s_addr = INADDR_ANY;
server_addr.sin_port = htons(PORT);
// Create a UDP socket : SOCK_DGRAM expressed as UDP
fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd == -1)
{
printf("create socket fail \r\n");
return 1;
}
// Set the SO_REUSEADDR attribute to enable address multiplexing and bind multiple addresses to the same port
if ((ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))) != 0)
{
log("setsockopt reuseaddr fail, ret : %d,error : %d \r\n", ret, errno);
close(fd);
return 1;
}
/* Set the IP_PKTINFO property - to obtain information about the packet */
if (0 != setsockopt(fd, IPPROTO_IP, IP_PKTINFO, &on, sizeof(on)))
{
printf("setsockopt ip_pktinfo fail, errno : %d \r\n", errno);
close(fd);
return 1;
}
// Bind the local listening address
if (0 != bind(fd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr_in)))
{
printf("bind local listening addr fail,errno : %d \r\n", errno);
close(fd);
return 1;
}
// Cyclic receiving information
while(1)
{
/* Number of buffers */
message.msg_iovlen = 1;
/* Address of multiple I/O buffers */
message.msg_iov = &iov[0];
/* Address of secondary data */
message.msg_control = cmh;
/* Protocol address of the message Source address of the stored message, port */
message.msg_name = &saddr;
/* Length of address */
message.msg_namelen = addr_len;
/* Length of auxiliary data */
message.msg_controllen = sizeof(buff);
// Information sent by the client
iove[0].iov_base = &buffer;
// Length of the message sent by the client
iove[0].iov_len = sizeof(buffer);
/* recvmsg : udp reflector
sockfd: represents the socket file descriptor that needs to receive the message.
msg: A pointer to an msghdr structure that holds the received message and related information.
flags: Call option, which specifies the behavior of the function */
recv_len = recvmsg(fd, &message, 0);
if (recv_len > 0)
{
printf("Read Client buffer:[%s]!\n",buffer);
/* Sets the location of secondary data */
message.msg_control = cmh;
/* Sets the size of the secondary data */
message.msg_controllen = sizeof(buff);
/* CMSG_FIRSTHDR()
This macro is used to return a struct cmsghdr pointer to the first firsthDR in the FirsthDR buffer.
The input value is a pointer to the struct msghdr structure */
/* CMSG_NXTHDR()
This struct cmsghdr pointer is used to return the next attached data object.
This macro takes two input parameters:A pointer to the struct msghdr structure ,
A pointer to the current struct cmsghdr
If there is no next attached data object, the macro returns NULL.
*/
for (cmhp = CMSG_FIRSTHDR(&message); cmhp; cmhp = CMSG_NXTHDR(&message, cmhp))
{
/* cmsg_level - Original protocol */
if (cmhp->cmsg_level == IPPROTO_IP)
{
/* cmsg_type: type of control information */
if(cmhp->cmsg_type == IP_PKTINFO)
{
/* CMSG_DATA()
The macro accepts a pointer to the cmsghdr structure.
The returned pointer value points to the first byte of the subsidiary data following the header and,
if present, the padding byte.*/
pktinfo = (struct in_pktinfo *)CMSG_DATA(cmhp);
if(pktinfo != NULL)
{
daddr.sin_family = AF_INET;
daddr.sin_addr = pktinfo->ipi_addr;
/* Source address and port of the packet */
printf("saddr : %u:%u:%u:%u:%hu \r\n", NIPQUAD(saddr.sin_addr), ntohs(saddr.sin_port));
/* The header identifies the destination address */
/*If the message sent by the client is sent by the broadcast, the content of daddr.sin_addr is the broadcast address.
But ipi_spec_dst is still the IP address received by the server */
printf("daddr : %u:%u:%u:%u \r\n", NIPQUAD(daddr.sin_addr));
/* Route destination address */
printf("daddr : %u:%u:%u:%u \r\n", NIPQUAD(pktinfo->ipi_spec_dst));
}
else
{
printf("Para CMSGHDR Fail!\n");
}
}
}
}
}
memset(buff, 0, sizeof(buff));
memset(buffer, 0, sizeof(buffer));
memset(&saddr, 0, sizeof(struct sockaddr_in));
memset(&daddr, 0, sizeof(struct sockaddr_in));
}
return 0;
}
上述程序可以获取对应发送方的源IP地址和端口以及接收方路由表分配的IP地址。