UDP广播和设备通讯

前言

公司有个小工具,用来给出厂的设备改参数。

设备上电后,开一个固定端口的UDP服务。
工具给局域网发特定内容的UDP广播包,端口固定。
设备收到特定命令后,给局域网回包。
这样,就可以搜索到公司自产的设备了,然后再发特定修改包,修改设备参数。

这个工具是以前同事在2013年用VB6写的,在现在同事的计算机上都好好的。就在我本本上不行。
搞得我每次做实验,都得麻烦他们帮我改设备参数。正好今天闲下来一天,将这个问题解决一下。

开始怕以前同事留下的源码和现在用的程序对不上,想找个工具反一下看看。
结果IDA不能单步这个VB6程序,一单步就报错。
用VB反编译工具,不能反出已经编译成本地代码的EXE.
那认怂了,看同事留下的源码吧。

在win10上,装了vb完整企业版。
打开工程后,编译报错,看日志,是2个OCX没法加载。
按照VB6日志说的OCX在部件管理中,找到全路径,去wow64目录下,将2个控件注册上。
这时,工程可以编译运行,没有报错,但是没有效果(找不到设备)。

看到UDP发送,用的是OCX提供的方法,广播地址是"255.255.255.255".
将设备用USB转网口接到本本上,运行编译好的小工具,用wireshark对USB转网口的网口进行抓包,根本就没有发包。

同事以前用VB6写的工程,有点糙,发送成功,失败,来点日志提示啊…
正常运行的时候没啥说的,这要是有点事,这咋排错? 这不难为我这VB新手么?

去找资料,用C写了一个小工具,简陋一点,但是好使。
发现是广播地址不只是255.255.255.255,像我的本本,用255.255.255.255就不行.
根据本地IP和网络不同,广播地址还得试试10.255.255.255, 172.255.255.255, 192.255.255.255, 255.255.255.255才行。能让设备接收到广播包的地址,绝对是这4个广播地址之一。

根据出厂设备IP的不同,要在USB转网口的网口设置上,加上10.x.x.x, 172.x.x.x的IP, 首先要能设备在一个大网段(IP的第1段要相同),才能通讯上。

给USB转网口的网口添加额外IP

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在USB转网口的网口属性上,将10.x.x.x, 172.x.x.x, 192.x.x.x 的额外IP都添加上。
这时,不管设备的IP是啥,都可以和设备通讯上了。
假设,我们知道设备的当前真实IP,这时ping设备丢失能通的。

这时,对设备广播就是纯软件的事情了。

测试程序

我做实验的设备,出厂IP是10.1.1.10, 此时,ping设备,是能通的。
下面是写好的测试程序,如果设备IP是未知的,但是属于10.x.x.x, 172.x.x.x, 192.x.x.x, 将测试程序中的广播地址改改,就可以通过广播和设备通讯上。广播地址必是以下广播地址(10.255.255.255, 172.255.255.255, 192.255.255.255, 255.255.255.255)之一.

贴上可用的测试程序,以后做实验还能用的上。

// find_device.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
// build on vs2017 vc++ console

#pragma warning(disable:4996)

#include<WinSock2.h>
#pragma comment(lib,"ws2_32.lib")

#include <windows.h>
#include <ws2tcpip.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// winsock version
#define WINSOCK_VER_1 2
#define WINSOCK_VER_2 2

// 私有地址 : 在互联网上不使用,而被用在局域网络中的地址
// 10.X.X.X是私有地址 范围(10.0.0.0-10.255.255.255)
// 172.16.0.0—172.31.255.255是私有地址
// 192.168.X.X是私有地址。(192.168.0.0-192.168.255.255) 

// @todo 为了找到在局域网内的设备(UDP服务器), 只需要执行3次不同的广播地址 10.255.255.255, 172.255.255.255, 192.255.255.255
// 在和设备连接的对应网卡上,也要在IP中加一个IP e.g. 10.1.1.13, 保证头一段和设备相同,掩码设置成255.0.0.0
// 如果掩码设置错了, e.g. 255.255.255.255, 是无法和设备通讯的, ping也ping不通
#define UDP_MULTICAST_IP "10.255.255.255" // UDP广播的IP

// 如果修改过设备参数,假设IP第1段变了,那么广播的第一段地址也要变成和设备IP第一段相同,否则收不到包
// 因为设备是通过(USB转网口)直连到计算机上的,假设设备修改后的IP, 和本本无线用的IP在一个网段了,这时,要断开无线网, 这时包才能通过(USB转网口发给设备)
// #define UDP_MULTICAST_IP "172.255.255.255" // UDP广播的IP

// 只有一种例外, 如果设备的IP已经被改成了和本本同网段的IP, 假设本本IP是192.168.1.60, 这时发送"192.255.255.255"作为广播地址,设备是不回答的。
// 只有将广播地址改为"255.255.255.255", 设备才能响应UDP命令, 可以结合wireshark抓包看看
// #define UDP_MULTICAST_IP "255.255.255.255" // UDP广播的IP

// 结合上述广播地址的实验,可以看出,对设备发UDP命令的广播地址是以下广播地址之一,绝对有UDP回答.
// 当做个完整程序给别人用时,将下面的广播地址遍历发送一次就行
// 10.255.255.255
// 172.255.255.255
// 192.255.255.255
// 255.255.255.255

#define UDP_SEND_TO_PORT 65011
#define UDP_SEND_CMD "<MY_HELLO_CMD>" // 要发送给设备的UDP命令字符串

// 发给设备的修改包
#define UDP_MODIFY_CMD "" \
						"{\"P1\":\"VAL1\"," \
						"\"P2\":\"VAL2\"," \
						"\"P3\":\"VAL3\"," \
						"\"P4\":\"VAL4\"," \
						"\"P5\":\"VAL5\"," \
						"\"P6\":\"VAL6\"," \
						"\"P7\":\"VAL7\"," \
						"\"P8\":\"VAL8\"," \
						"\"P9\":\"VAL9\"," \
						"\"P10\":\"VAL10\"," \
						"\"P11\":\"VAL11\"," \
						"\"P12\":\"VAL12\"," \
						"\"P13\":\"VAL13\"," \
						"\"P14\":\"VAL14\"," \
						"\"P15\":\"VAL15\"}"

#define UDP_RECV_FROM_PORT 65100

int main(int argc, char** argv)
{
	int i_rc = 0;
	WORD wVersionRequested = 0;
	WSADATA wsaData;

	SOCKET sock_host;
	BOOL bBoardcast = TRUE; // 是否广播
	sockaddr_in addr_multicast; // 广播用的socket地址信息
	sockaddr_in UDPServerAddr; // 远端UDP服务器的socket地址信息

	struct timeval tv; // 接收超时的设置, 不能因为设备不在,一直卡在recvfrom那里

	int udp_exec_cnt = 0; // UDP广播的执行次数
	char sz_buf[512] = "\0";
	int from_len = 0;

	BOOL b_need_send_cmd_to_device = FALSE; // 是否需要给设备发修改包
	BOOL b_send_cmd_to_device_once = FALSE; // 已经给设备发送过一次修改包
	BOOL b_recv_ok = FALSE; // 接收成功

	do {
		// socket lib init
		wVersionRequested = MAKEWORD(WINSOCK_VER_1, WINSOCK_VER_2); // Winsock DLL versions need v2.2
		i_rc = WSAStartup(wVersionRequested, &wsaData);
		if (0 != i_rc) {
			printf("WSAStartup failed with error: %d\n", i_rc);
			break;
		}

		if (LOBYTE(wsaData.wVersion) != WINSOCK_VER_1 || HIBYTE(wsaData.wVersion) != WINSOCK_VER_2) {
			printf("Could not find a usable %d.%d version of Winsock.dll\n", WINSOCK_VER_1, WINSOCK_VER_2);
			break;
		}
		else {
			printf("The Winsock %d.%d dll was found okay\n", WINSOCK_VER_1, WINSOCK_VER_2);
		}

		// socket creat
		sock_host = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

		// set socket to multicast
		if (SOCKET_ERROR == setsockopt(sock_host, SOL_SOCKET, SO_BROADCAST, (char*)&bBoardcast, sizeof(bBoardcast)))
		{
			printf("setsockopt failed with error code: %d/n", WSAGetLastError());
			break;
		}

		// set udp recv timeout
		tv.tv_sec = 10;
		tv.tv_usec = 0;
		if (setsockopt(sock_host, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv)) < 0) {
			printf("socket option  SO_RCVTIMEO not support\n");
		}

		// 广播IP的第一段地址必须和UDP服务器的第1段IP地址相同,才会发给UDP服务器
		// e.g. 如果广播地址为10.255.255.255, 那么10.1.1.20才会收到包
		addr_multicast.sin_addr.S_un.S_addr = inet_addr(UDP_MULTICAST_IP);
		addr_multicast.sin_port = htons(UDP_SEND_TO_PORT);
		addr_multicast.sin_family = AF_INET;

		UDPServerAddr.sin_port = htons(UDP_RECV_FROM_PORT);
		UDPServerAddr.sin_addr.s_addr = INADDR_ANY;
		UDPServerAddr.sin_family = AF_INET;

		while (1)
		{
			// print tip
			printf("%s\n", "________________________________________________________________________________");

			memset(sz_buf, 0, sizeof(sz_buf));
			strcpy(sz_buf, UDP_SEND_CMD);
			i_rc = sendto(sock_host, sz_buf, strlen(sz_buf), 0, (sockaddr *)&addr_multicast, sizeof(addr_multicast));
			if (i_rc < 0)
			{
				printf("setsockopt failed with error code: %d/n", WSAGetLastError());
				break;
			}

			printf("[%d] %d = sendto(%s)\n", udp_exec_cnt++, i_rc, sz_buf);

			memset(sz_buf, 0, sizeof(sz_buf));
			from_len = sizeof(UDPServerAddr);
			b_recv_ok = FALSE;
			i_rc = recvfrom(sock_host, sz_buf, sizeof(sz_buf), 0, (SOCKADDR *)&UDPServerAddr, &from_len);
			if (SOCKET_ERROR == i_rc)
			{
				printf("ERROR: recvfrom failed with error %d\n", WSAGetLastError());
			}
			else {
				//显示接收到的数据
				printf("UDP recv from [%s] :\n", inet_ntoa(UDPServerAddr.sin_addr));
				printf("%s\n", sz_buf);
				b_recv_ok = TRUE;
			}

			Sleep(5000);

			if (b_recv_ok) {
				// 通过上面设备回的包,改一下回包, 再发给设备,这样,设备的参数就变了
				// b_need_send_cmd_to_device = TRUE;  // 如果不需要给设备发修改包, 就注释掉本行
				if (!b_send_cmd_to_device_once && b_need_send_cmd_to_device) {
					b_send_cmd_to_device_once = TRUE;
					strcpy(sz_buf, UDP_MODIFY_CMD);

					i_rc = sendto(sock_host, sz_buf, strlen(sz_buf), 0, (sockaddr *)&addr_multicast, sizeof(addr_multicast));
					if (i_rc < 0)
					{
						printf("setsockopt failed with error code: %d/n", WSAGetLastError());
						break;
					}

					printf("!!! [%d] %d = sendto(%s)\n", udp_exec_cnt++, i_rc, sz_buf);

					// 等设备改完参数, 回答OK后,这个程序再广播的旧地址,就没回答了。这时,要按照新的IP段广播地址重新发包才有设备回答
				}

				Sleep(5000);
			}
		}

	} while (0);

	// socket lib uninit
	WSACleanup();
	return EXIT_SUCCESS;
}

备注

同事们计算机上,原来的小工具都好使。可能他们装的都是win7(在winserver2008上运行也正常), 而且不经常装其他软件,运行环境比较稳定的缘故吧。

就我本本上不好使。有个销售的同事建议我重新装系统,我差点晕倒:P

对于研发来说,有问题就找呗。解决问题是常态。

只要有运行正常和运行不正常2种环境,那问题的解决只是时间问题。做实验,查资料的次数和时间多少而已。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值