1.简介
很多有这样需求的上位机都会具有一键搜索局域网内设备,各家用的方法都各不相同。
这样做非常方便用户使用,用户不再需要在设备端确定设备ip,在上位机上在输入,然后连接。一键就可以发现并选择对应的设备,这种功能一听就很有吸引力,以下就着手实现该功能。
2.原理
原理很简单,找到对方ip,然后对协议,协议对上后即可确认对方设备为自己需要的。
那么难点就在于找到ip,以下列举几种方法:
① ping网段下所有的ip,ping通的地址再发送私有协议
② 广播自身地址,设备收到后主动连接
③ 设立服务器,设备主动连接服务器
3.实现
基于第二种方法,以下C语言实现相关功能
首先肯定是socket初始化并配置ip端口了
socket= socket(PF_INET, SOCK_DGRAM, 0));
对于发送端,我们还需要配置socket的属性是广播,这样才能发送广播包
int optval = 1;
setsockopt(socket, SOL_SOCKET, SO_BROADCAST , &optval, sizeof(optval));
然后设置ip及端口
struct sockaddr_in recvAddr;
memset(&recvAddr, 0, sizeof(struct sockaddr_in));
recvAddr.sin_family = AF_INET;
recvAddr.sin_port = htons(port);
recvAddr.sin_addr.s_addr = INADDR_ANY;
发送端就完成初始化操作了。但是对于接收端,还需要绑定端口,不然无法接收信息
bind(socket, (struct sockaddr *)&recvAddr, sizeof(struct sockaddr));
最后发送端使用发送函数发送需要的内容
sendto(socket, sendbuf, len, 0, (struct sockaddr *)&recvAddr, sizeof(struct sockaddr))
接收端则接收
recvfrom(socket, recvbuf, sizeof(recvbuf), 0,(struct sockaddr *)&recvAddr, &addrLen);
这样接收端就得到对方地址了,后面就可以根据自己需求做相应的操作。
下面给出发送端程序
int broadcast_server_init(struct broadcast_handle *bse, broadcast_mode mode, char *ip, uint16_t port, long timeout)
{
if(!bse) return -1;
if((bse->fd = socket(PF_INET, SOCK_DGRAM, 0)) == -1){
strcpy(bse->msg,"socket fail");
return -1;
}
int optval = 1;//这个值一定要设置,否则可能导致sendto()失败
if(setsockopt(bse->fd, SOL_SOCKET, SO_BROADCAST , (char *)&optval, sizeof(optval)) == -1) //windows下需要分开设置
{
strcpy(bse->msg,"setsockopt fail 1");
return -1;
}
if(setsockopt(bse->fd, SOL_SOCKET, SO_REUSEADDR, (char *)&optval, sizeof(optval)) == -1)
{
strcpy(bse->msg,"setsockopt fail 2");
return -1;
}
struct timeval tv = {1,0}; //first means sec, second means usec
setsockopt(bse->fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv));
memset(&bse->thisAddr, 0, sizeof(struct sockaddr_in));
bse->thisAddr.sin_family = AF_INET;
if(!ip) bse->thisAddr.sin_addr.s_addr = inet_addr("255.255.255.255");
else bse->thisAddr.sin_addr.s_addr = inet_addr(ip);
bse->thisAddr.sin_port = htons(port);
return 0;
}
int broadcast_server_run(struct broadcast_handle *bse)
{
if(!bse || (bse->fd==-1)) return -1;
int ret,len;
char recvbuf[MAX_BUFFER_LEN],sendbuf[MAX_BUFFER_LEN];
struct sockaddr_in recvAddr;
int addrLen = sizeof(struct sockaddr_in);
if((ret = sendto(bse->fd, sendbuf, len, 0, (struct sockaddr *)&bse->thisAddr, sizeof(struct sockaddr))) == -1)
{
sprintf(bse->msg, "sendto fail, ret=%d\n", ret);
return -1;
}
if((ret = recvfrom(bse->fd, recvbuf, sizeof(recvbuf), 0,
(struct sockaddr *)&recvAddr, &addrLen)) > 0)
{
recvbuf[ret] = '\0';
if(authentication(bse,recvbuf) == -1) continue;
fun_avltree_unit* info = malloc(sizeof(fun_avltree_unit));
if(!info) {
strcpy(bse->msg,"malloc list fail");
return -1;
}
strncpy(info->remote_ip,(char *)inet_ntoa(recvAddr.sin_addr),sizeof(info->remote_ip));
info->remote_port = htons(recvAddr.sin_port);
info->key = info->remote_ip;
if(tree_add_remote_info(bse,info) != 0)
{
strcpy(bse->msg,"insert info fail");
free(info);
}
#ifdef BROADCAST_DEBUG
printf("IP:%s\n", info->remote_ip);
printf("Port:%d\n", info->remote_port);
printf("receive a broadCast messgse:%s\n", recvbuf);
#endif
}
}
接收端程序:
int broadcast_client_init(struct broadcast_handle *bcl, broadcast_mode mode, char *ip, uint16_t port, long timeout)
{
if(!bcl) return -1;
bcl->fd = socket(AF_INET, SOCK_DGRAM, 0);
if(bcl->fd == -1)
{
strcpy(bcl->msg,"socket fail");
return -1;
}
int set = 1;
if(setsockopt(bcl->fd, SOL_SOCKET, SO_REUSEADDR, (char *)&set, sizeof(set)) == -1) //set char* to adapter windows api
{
strcpy(bcl->msg,"setsockopt fail");
return -1;
}
struct timeval tv = {timeout,0}; //first means sec, second means usec
if(timeout>=0) setsockopt(bcl->fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv, sizeof(tv));
struct sockaddr_in recvAddr;
memset(&recvAddr, 0, sizeof(struct sockaddr_in));
recvAddr.sin_family = AF_INET;
recvAddr.sin_port = htons(port);
if(ip==NULL) recvAddr.sin_addr.s_addr = INADDR_ANY;
else recvAddr.sin_addr.s_addr = inet_addr(ip);
if(bind(bcl->fd, (struct sockaddr *)&recvAddr, sizeof(struct sockaddr)) == -1){
strcpy(bcl->msg,"bind fail");
return -1;
}
return 0;
}
int broadcast_client_run(struct broadcast_handle *bcl)
{
int ret;
char recvbuf[MAX_BUFFER_LEN];
struct sockaddr_in recvAddr;
int addrLen = sizeof(struct sockaddr_in);
time_t startTime = get_timestamp();
do
{
#ifndef _WIN32
if((ret = recvfrom(bcl->fd, recvbuf, sizeof(recvbuf), 0,
(struct sockaddr *)&recvAddr, (socklen_t*)&addrLen)) > 0)
#else
if((ret = recvfrom(bcl->fd, recvbuf, sizeof(recvbuf), 0,
(struct sockaddr *)&recvAddr, &addrLen)) > 0)
#endif
{
recvbuf[ret] = '\0';
if(sendto(bcl->fd, recvbuf, ret, 0, (struct sockaddr *)&recvAddr, addrLen) < 0)
{
sprintf(bcl->msg,"sendto fail");
return -1;
}
fun_avltree_unit* info = malloc(sizeof(fun_avltree_unit));
if(!info) {
strcpy(bcl->msg,"malloc list fail");
return -1;
}
strncpy(info->remote_ip,(char *)inet_ntoa(recvAddr.sin_addr),sizeof(info->remote_ip));
info->remote_port = htons(recvAddr.sin_port);
info->key = info->remote_ip;
if(tree_add_remote_info(bcl,info) != 0)
{
strcpy(bcl->msg,"insert info fail");
free(info);
return -1;
}
#ifdef BROADCAST_DEBUG
printf("IP:%s\n", info->remote_ip);
printf("Port:%d\n", info->remote_port);
printf("receive a broadCast messgse:%s\n", recvbuf);
#endif
return 0;
}
else
{
#ifdef BROADCAST_DEBUG
printf("recvfrom fail(%d): errno=%d\n",__LINE__, errno);
#endif
if(errno != EAGAIN){
sprintf(bcl->msg,"recvfrom: %m");
break;
}
}
} while ((bcl->timeout==-1) || (get_timestamp_difftime(startTime,get_timestamp()) < bcl->timeout));
sprintf(bcl->msg,"timeout");
return -1;
}
头文件:
#ifndef _WIN32
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <sys/time.h>
#else
#include <winsock2.h> //链接时需要添加 -lwsock32
#include <windows.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdint.h>
#include <malloc.h>
#include <errno.h>
#include "broadcast.h"
#include "cJSON.h"
#include "fun_runtime.h"
4.最后
调试过程中很多坑,特别在windows下很多设置要特殊处理,如头文件winsock2要写在windows前面,设置参数不可以用(xxxx | xxxx)一次性设置,需要分开两次等等,还有其他设置项出错导致发送失败。
好在最后都正常跑了,可喜可贺