问题描述:

在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_ANY3002端口后,进入监听状态,此时,有客户端程序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地址。

05-02 13:01