通过设置套接字的选项(IP_PKTINFO) 来获取收到报文的源地址和目的地址。
这里使用的是调用的是recvmsg接口,实际上一个还有更简单的接口recvfrom接口也能够满足要求,可以参考这篇博客。
javascript:void(0)
代码如下:
/*
* Description : UDP套接字编程获取源地址和目的地址
* Date : 20180527
*/
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/errno.h>
#include <sys/uio.h>
#include <stdint.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include "udptest.h"
void main() {
int on = 1;
int ret, fd, addr_len, recv_len;
char buffer[512] = {0}; /* 接收缓存 */
struct msghdr msg;
struct iovec iov[1];
struct sockaddr_in saddr; /* src */
struct sockaddr_in daddr; /* dst */
struct cmsghdr *cmhp;
struct sockaddr_in server_addr;
struct in_pktinfo *pktinfo = NULL; /* 用于指向获取的本地地址信息 */
char buff[CMSG_SPACE(sizeof(struct in_pktinfo) + CMSG_SPACE(sizeof(int)))] = {0}; /* 控制信息 */
struct cmsghdr *cmh = (struct cmsghdr *)buff; /* 控制信息 */
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));
// 创建UDP套接字
fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd == -1) {
log("create socket fail \r\n");
return ;
}
// 设置监听地址
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = INADDR_ANY; // 所有地址
server_addr.sin_port = htons(3500);
// 设置SO_REUSEADDR属性, 地址复用
if ((ret = setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof(on))) != 0) {
log("setsockopt reuseaddr fail, ret : %d,error : %d \r\n", ret, errno);
close(fd);
return ;
}
/* 设置IP_PKTINFO属性 */
if (0 != setsockopt(fd, IPPROTO_IP, IP_PKTINFO, (char *)&on, sizeof(on))) {
log("setsockopt ip_pktinfo fail, errno : %d \r\n", errno);
close(fd);
return ;
}
// 绑定本地监听地址
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 ;
}
// 接收信息
while(1) {
msg.msg_name = &saddr; // 存储报文来源地址,端口
msg.msg_namelen = addr_len;
msg.msg_iov = &iov[0];
msg.msg_iovlen = 1;
msg.msg_control = cmh;
msg.msg_controllen = sizeof(buff);
iov[0].iov_base = &buffer;
iov[0].iov_len = sizeof(buffer);
// 超时退出
recv_len = recvmsg(fd, &msg, 0);
if (recv_len > 0)
{
/* 辅助信息 */
msg.msg_control = cmh;
msg.msg_controllen = sizeof(buff);
for (cmhp = CMSG_FIRSTHDR(&msg); cmhp; cmhp = CMSG_NXTHDR(&msg, cmhp)) {
if (cmhp->cmsg_level == IPPROTO_IP) {
if (cmhp->cmsg_type == IP_PKTINFO) {
pktinfo = (struct in_pktinfo *)CMSG_DATA(cmhp);
daddr.sin_family = AF_INET;
daddr.sin_addr = pktinfo->ipi_addr;
/* 报文源地址、端口 */
log("saddr : %u:%u:%u:%u:%hu \r\n", NIPQUAD(saddr.sin_addr), ntohs(saddr.sin_port));
/* 报文目的地址 */
log("daddr : %u:%u:%u:%u \r\n", NIPQUAD(daddr.sin_addr));
log("daddr : %u:%u:%u:%u \r\n", NIPQUAD(pktinfo->ipi_spec_dst));
}
}
}
}
memset(buffer, 0, sizeof(buffer));
memset(buff, 0, sizeof(buff));
memset(&saddr, 0, sizeof(struct sockaddr_in));
memset(&daddr, 0, sizeof(struct sockaddr_in));
}
return ;
}
头文件:
#ifndef _UDPTEST_H
#define _UDPTEST_H
#define log(fmt, arg...) printf("[udptest] %s:%d "fmt, __FUNCTION__, __LINE__, ##arg)
#ifndef NIPQUAD
#define NIPQUAD(addr) \
((unsigned char *)&addr)[0], \
((unsigned char *)&addr)[1], \
((unsigned char *)&addr)[2], \
((unsigned char *)&addr)[3]
#endif
#endif
Makefile:
default:
@gcc -o udptest udptest.c
clean:
@rm -rf udptest
命令行执行程序后使用网络调试助手发送报文查看打印信息:
这里可以看到有两个目的地址。这是因为在设置IP_PKTINFO 选项后,返回的结构体包含两个目的地址,通常情况下这两个地址是一样的,路由目的地址指的是报文原始的目的地址,如果是广播报文的话这里就是255.255.255.255,头标识目的地址指的是本地程序的地址,例如一台路由器可能连接多台PC,当外部程序发送报文的时候填写到目的地址是路由器的地址,路由器会把这样到报文转发到对应到设备,这时头标识目的地址就是对应设备的地址。
struct in_pktinfo { unsigned int ipi_ifindex; /* 接口索引 */ struct in_addr ipi_spec_dst; /* 路由目的地址 */ struct in_addr ipi_addr; /* 头标识目的地址 */ };
通过wireshark 可以看出:
linux 网络调试助手可以在这里下载,当然也可以找到对应到windows版本
链接: https://pan.baidu.com/s/1Z15YxE1r-dIOU_l9saEO0A
密码: i7am