在项目当中一般使用tcp作为网络通信连接,但需要经历多次握手和分手才能建立可靠连接,
并且当物联网设备突然断电后连接会在内核中挂起,出现超长等待, 使用udp虽然不会让通讯
出现挂起现象但是数据不是按照队列方式发送,并且也不能确保数据收发的完整性,
所以在面向物联网的产品使用开源项目udt能比较好解决上述二者的缺陷.
udt交叉编译修改udt4/src/Makefile
#C++ = g++
C++ = /home/openwrt-master/staging_dir/toolchain-mips_74kc_gcc-5.3.0_musl-1.1.16/bin/mips-openwrt-linux-g++
ifndef os
os = LINUX
endif
ifndef arch
arch = IA32
endif
CCFLAGS = -fPIC -Wall -Wextra -D$(os) -finline-functions -O3 -fno-strict-aliasing -fvisibility=hidden
ifeq ($(arch), IA32)
CCFLAGS += -DIA32
endif
ifeq ($(arch), POWERPC)
CCFLAGS += -mcpu=powerpc
endif
ifeq ($(arch), SPARC)
CCFLAGS += -DSPARC
endif
ifeq ($(arch), IA64)
CCFLAGS += -DIA64
endif
ifeq ($(arch), AMD64)
CCFLAGS += -DAMD64
endif
OBJS = api.o buffer.o cache.o ccc.o channel.o common.o core.o epoll.o list.o md5.o packet.o queue.o window.o
DIR = $(shell pwd)
all: libudt.so libudt.a udt
%.o: %.cpp %.h udt.h
$(C++) $(CCFLAGS) $< -c
libudt.so: $(OBJS)
ifneq ($(os), OSX)
$(C++) -shared -o $@ $^
else
$(C++) -dynamiclib -o libudt.dylib -lstdc++ -lpthread -lm $^
endif
libudt.a: $(OBJS)
ar -rcs $@ $^
udt:
cp udt.h udt
clean:
rm -f *.o *.so *.dylib *.a udt
install:
export LD_LIBRARY_PATH=$(DIR):$$LD_LIBRARY_PATH
执行交叉编译 make -e os=LINUX arch=-DMIPS
在openwrt中创建udt的封装库libudt_trans.so
#ifndef __UDT_TRANS_H__
#define __UDT_TRANS_H__
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <pthread.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>
#include <signal.h>
#include <sys/time.h>
#include <netdb.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <ctype.h>
#include <fcntl.h>
#include <udt.h>
typedef char I8;
typedef unsigned char UI8;
#define int8_t I8
#define uint8_t UI8
typedef short I16;
typedef unsigned short UI16;
typedef int I32;
typedef unsigned int UI32;
typedef enum{FALSE,TRUE=!FALSE} BOOLEAN;
#define DELAY_1MS (1000)
#define DELAY_5MS (5*1000)
#define DELAY_10MS (10*1000)
#define DELAY_30MS (30*1000)
#define DELAY_100MS (100*1000)
#define DELAY_300MS (300*1000)
#define DELAY_500MS (500*1000)
#define DELAY_1S (1000*1000)
#define DELAY_2S (2000*1000)
#define DELAY_3S (3000*1000)
//server info
#define UDT_SERVER_DEF_IP "192.168.59.254"
#define UDT_SERVER_PORT (59342)
#define UDT_SERVER_PORT_STR "59342"
//-1为无穷,非负数即有效
#define SEND_TIMEOUT (0)
#define BUFSIZE (128)
#define UDT_IPADDR_FILE_PATH "/etc/udt_ipaddr"
#define DEBUG
#ifdef __cplusplus
extern "C"{
#endif
//udt库初始化
int udt_trans_init(void);
//udt通道发送数据
int udt_trans_send(UI8*data,UI8 datalen);
//udt通道接收数据
int udt_trans_recv(UI8**data,UI8*datalen);
//data为udt服务端ip地址
int udt_trans_conf(I8*data);
#ifdef __cplusplus
}
#endif
#endif
/**
create by iversondeng168
*/
#include "udt_trans.h"
static UI8 recvbuf[BUFSIZE];
static int is_recv_stream_runing = 0;
static UDTSOCKET client;
static struct addrinfo hints,*local,*peer;
static char serverip[32] = {0};
static int is_udt_connect = 0;
static int is_ip_need_refresh = 0;
void* udt_trans_loop(void*param);
void* udt_trans_recv_thread_func(void*param);
static char cmdbuf[BUFSIZE];
static char* cmd_system(const char* command)
{
char* result = NULL;
FILE *fpRead;
fpRead = popen(command, "r");
memset(cmdbuf,'\0',BUFSIZE);
while(fgets(cmdbuf,BUFSIZE - 1,fpRead) != NULL)
{
result = cmdbuf;
}
if(fpRead!=NULL)
pclose(fpRead);
return result;
}
//查找字符串中指定字符最后出现的索引
static int get_last_char_index(char*str,char c)
{
int i;
int slen = 0;
int tarind = -1;
if(str == NULL || strlen(str) <= 0) return tarind;
slen = strlen(str);
for(i = 0;i < slen;i++)
{
if(str[i] == c){
tarind = i;
}
}
return tarind;
}
/**
wlan0网段匹配
*/
static BOOLEAN wlan0_netseg(char*serverip)
{
#define WLAN0_GET_IP "ifconfig wlan0 | grep 'inet addr' | awk '{ print $2}' | awk -F: '{print $2}'"
I8*wlan0ip;
int trycnt;
int tmp;
//不同网段对socket连接有影响
wlan0ip = cmd_system(WLAN0_GET_IP);
trycnt = 0;
while((wlan0ip == NULL || strlen(wlan0ip) == 0) && trycnt++ < 5)
{
usleep(DELAY_100MS);
wlan0ip = cmd_system(WLAN0_GET_IP);
}
if(trycnt >= 5) {
if(wlan0ip == NULL) wlan0ip = (I8*)"";
}
#ifdef DEBUG
printf("wlan0ip=%s\n",wlan0ip);
#endif
tmp = get_last_char_index(wlan0ip,'.');
if(tmp <= -1) return FALSE;
if(strncmp(serverip,wlan0ip,tmp)){
#ifdef DEBUG
printf("local ip SERVER_IP are not in lan\n");
#endif
return FALSE;
}
return TRUE;
}
//文件读
static int read_file(const char* _fileName, void* _buf, int _bufLen)
{
FILE* fp = NULL;
if( NULL == _buf || _bufLen <= 0 ) return (-1);
fp = fopen(_fileName, "rb");
if( NULL == fp )
{
return (-1);
}
fread(_buf, _bufLen, 1, fp);
fclose(fp);
return 0;
}
//文件写
static int write_file(const char* _fileName, void* _buf, int _bufLen)
{
FILE * fp = NULL;
if( NULL == _buf || _bufLen <= 0 ) return (-1);
fp = fopen(_fileName, "wb");
if( NULL == fp )
{
return (-1);
}
fwrite(_buf, _bufLen, 1, fp);
fclose(fp);
fp = NULL;
return 0;
}
/**
udt configure initial
*/
int udt_trans_init(void)
{
int optval;
int tmp;
pthread_t utl;
/**
获取用户自定义IP /etc/udt_ipaddr
*/
if(access(UDT_IPADDR_FILE_PATH,F_OK) < 0)
{
}
else
{
read_file(UDT_IPADDR_FILE_PATH,serverip,sizeof(serverip));
tmp = strlen(serverip);
if(tmp >= 1){
tmp -= 1;
if(serverip[tmp] == 0x0a){
serverip[tmp] = 0;
}
}
}
if(strlen(serverip) <= 0)
{
memcpy(serverip,UDT_SERVER_DEF_IP,strlen(UDT_SERVER_DEF_IP));
}
//it starts the garbage collection thread
UDT::startup();
memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_flags = AI_PASSIVE;
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
if(0 != getaddrinfo(NULL,UDT_SERVER_PORT_STR, &hints, &local))
{
#ifdef DEBUG
printf("incorrect network address.\n");
#endif
return -1;
}
/**
create socket
*/
client = UDT::socket(local->ai_family, local->ai_socktype, local->ai_protocol);
optval = SEND_TIMEOUT;
/*
send timeout Default -1 (infinite).
send:Non-blocking call failure.
*/
UDT::setsockopt(client, 0, UDT_SNDTIMEO,&optval,sizeof(int));
/**
configure server ip&port
*/
if(0 != getaddrinfo(serverip,UDT_SERVER_PORT_STR,&hints,&peer))
{
#ifdef DEBUG
printf("incorrect server/peer address\n");
#endif
return -1;
}
if(pthread_create(&utl,NULL,udt_trans_loop,NULL) != 0)
{
#ifdef DEBUG
printf("The thread of utl is not create!\n");
#endif
return -1;
}
pthread_detach(utl);
return 0;
}
/**
data send
*/
int udt_trans_send(UI8*data,UI8 datalen)
{
if(is_udt_connect)
{
//加入发送队列
return 0;
}
return -1;
}
/**
udt data real loop send/recv
*/
void* udt_trans_loop(void*param)
{
#define EMPTY_PACKET_SIZE (1)
int ss,tmp;
int optval = SEND_TIMEOUT;
UI8*sendata;
UI8 sendatalen;
UI8 empty_packet[EMPTY_PACKET_SIZE] = {0};
pthread_t rec_t;
void**retcode = NULL;
while(true)
{
is_udt_connect = 0;
//wlan0网段匹配与否
if(!wlan0_netseg(serverip))
{
usleep(DELAY_2S);
continue;
}
/*
动态切换ip,防止强制切换IP产生
connect:Operation not supported: Cannot do this operation on a CONNECTED socket
和close关闭不了
*/
if(is_ip_need_refresh)
{
is_ip_need_refresh = 0;
UDT::startup();
client = UDT::socket(local->ai_family, local->ai_socktype, local->ai_protocol);
UDT::setsockopt(client, 0, UDT_SNDTIMEO,&optval, sizeof(int));
if(0 != getaddrinfo(serverip,UDT_SERVER_PORT_STR,&hints,&peer))
{
#ifdef DEBUG
printf("incorrect server/peer address\n");
#endif
return 0;
}
}
//connect to the server, implict bind
if(UDT::ERROR == UDT::connect(client, peer->ai_addr, peer->ai_addrlen))
{
#ifdef DEBUG
printf("connect:%s\n",UDT::getlasterror().getErrorMessage());
#endif
tmp = UDT::getlasterror().getErrorCode();
/*server does not exist,
这样无休止的连接会耗尽cpu,尽管速度很慢!
解决的办法就是释放内存,终止线程,同时多休息
*/
if(1001 == tmp)
{
UDT::close(client);
UDT::cleanup();
usleep(DELAY_100MS);
UDT::startup();
client = UDT::socket(local->ai_family, local->ai_socktype, local->ai_protocol);
UDT::setsockopt(client, 0, UDT_SNDTIMEO,&optval, sizeof(int));
}
//aborted due to security reasons.
//连接Android手机会导致这个异常并且内存暴增加
if(1004 == tmp)
{
UDT::close(client);
UDT::cleanup();
usleep(DELAY_100MS);
UDT::startup();
client = UDT::socket(local->ai_family, local->ai_socktype, local->ai_protocol);
UDT::setsockopt(client, 0, UDT_SNDTIMEO,&optval, sizeof(int));
continue;
}
//socket connected,just skip
if(5002 == tmp)
{
goto CONNECT_OK;
}
//u socket is invalid!
if(5004 == tmp)
{
client = UDT::socket(local->ai_family, local->ai_socktype, local->ai_protocol);
UDT::setsockopt(client, 0, UDT_SNDTIMEO,&optval, sizeof(int));
}
usleep(DELAY_1S);
continue;
}
else
{
CONNECT_OK:
if(is_recv_stream_runing)
{
pthread_cancel(rec_t);
pthread_join(rec_t,retcode);
usleep(DELAY_100MS);
#ifdef DEBUG
printf("rec_t thread exit code=%d\n",(int)retcode);
#endif
}
if(pthread_create(&rec_t, NULL, udt_trans_recv_thread_func, NULL) != 0){
#ifdef DEBUG
printf("The thread of rec is not create!\n");
#endif
return 0;
}
//connect ok !
is_udt_connect = 1;
#ifdef DEBUG
printf("connect ok\n");
#endif
while(true)
{
//网络检查
if(!is_udt_connect)
{
if(is_recv_stream_runing)
{
is_recv_stream_runing = 0;
pthread_cancel(rec_t);
}
pthread_join(rec_t,retcode);
break;
}
/*
没有数据发送时,也要保持发送一个类似心跳包的东西,才能实时检测到服务端断开,
比如可以每30ms发送一个字节0.
*/
if(1)
{
sendata = empty_packet;
sendatalen = sizeof(empty_packet);
usleep(DELAY_30MS);
}
else{
//从发送队列中取数据
}
//发送数据
if( sendata != NULL &&
sendatalen > 0 &&
UDT::ERROR == (ss = UDT::send(client,(char*)sendata,sendatalen,0)))
{
#ifdef DEBUG
printf("send:%s\n",UDT::getlasterror().getErrorMessage());
#endif
tmp = UDT::getlasterror().getErrorCode();
#ifdef DEBUG
printf("errorno=%d\n",tmp);
#endif
/*
2001 : server is close,6003 send timeout,
5004 : u socket is invalid,
7000:server is broken,
*/
if(2001 == tmp || 6003 == tmp || 5004 == tmp || 7000 == tmp)
{
if(6003 == tmp)
{
UDT::cleanup();
usleep(DELAY_100MS);
UDT::startup();
}
//网络主动断开时产生的异常不重新初始化client
if(is_udt_connect){
client = UDT::socket(local->ai_family, local->ai_socktype, local->ai_protocol);
UDT::setsockopt(client, 0, UDT_SNDTIMEO,&optval, sizeof(int));
}
//确保接收线程关闭,释放部分内存
if(is_recv_stream_runing)
{
is_recv_stream_runing = 0;
#ifdef DEBUG
printf("pthread_cancel\n");
#endif
pthread_cancel(rec_t);
}
#ifdef DEBUG
printf("pthread_join\n");
#endif
pthread_join(rec_t,retcode);
#ifdef DEBUG
printf("pthread_join ok\n");
#endif
}
break;
}
else{
//send data ok!
}
}
}
}
UDT::close(client);
UDT::cleanup();
return 0;
}
/**
data recv
*/
int udt_trans_recv(UI8**data,UI8*datalen)
{
if(is_udt_connect)
{
#ifdef DEBUG
printf("datalen=%d\tdata=%s\n",*datalen,*data);
#endif
//加入数据接收队列
return 0;
}
return -1;
}
void* udt_trans_recv_thread_func(void*param)
{
int len;
UI16 tmp;
is_recv_stream_runing = 1;
while(true)
{
if(UDT::ERROR == (len = UDT::recv(client,(char*)recvbuf,sizeof(recvbuf),0)))
{
#ifdef DEBUG
printf("recv:%s\n",UDT::getlasterror().getErrorMessage());
#endif
break;
}
if(len > 0)
{
if(len > BUFSIZE){
len = BUFSIZE;
}
#ifdef DEBUG
printf("len=%d\trecvbuf=%s\n",len,recvbuf);
#endif
}
}
is_recv_stream_runing = 0;
return 0;
}
/**
server ip重定向
*/
int udt_trans_conf(I8*data)
{
if(data == NULL || strlen(data) <= 0) return -1;
if(!strcmp(serverip,data)) return -1;
memset(serverip,0,sizeof(serverip));
memcpy(serverip,data,strlen(data));
//动态切换,必须先置位,以便close后立马起作用
is_ip_need_refresh = 1;
/**
close函数会卡主的原因是因为它需要等待send()和recv()结束,
然而之前的方式直接先把recv线程强行终止和send不再调用,导致close函数
一直在等待同步释放的信号,所以永远也关不了,释放不了资源!!!
*/
UDT::close(client);
UDT::cleanup();
//重新连接
is_udt_connect = 0;
#ifdef DEBUG
printf("new ip=%s\n",serverip);
#endif
return 0;
}
执行交叉编译
GPP=/home/openwrt-master/staging_dir/toolchain-mips_74kc_gcc-5.3.0_musl-1.1.16/bin/mips-openwrt-linux-g++
$GPP udt_trans.c -fPIC -shared -o libudt_trans.so -O3 -I../src -L../src -ludt -lstdc++ -lpthread
openwrt本地测试
/**
create by iversondeng168
*/
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <unistd.h>
#include <time.h>
#include <errno.h>
#include <signal.h>
#include <sys/time.h>
#include <unistd.h>
#include "udt_trans.h"
int main(int argc,char*argv[])
{
unsigned char*recvdata;
unsigned char recvdatalen;
int cnt= 0;
I8 ip[32] = {0};
int ret;
unsigned char s[]={0xFE,0x01,0x00,0x02,0x00,0x2c,0x01,0xFF,0xFF};
srand(time(NULL));
ret = udt_trans_init();
printf("udt_trans_init ret=%d\n",ret);
while(1){
usleep(1000 * 1000);
//1s发送一次数据
udt_trans_send(s,sizeof(s));
//60s更新一次ip
if(!(cnt++ % 60))
{
sprintf((char*)ip,"192.168.59.%d",150 + rand() % 100);
printf("target ip=%s\n",ip);
udt_trans_conf(ip);
}
}
return 0;
}
执行交叉编译
GPP=/home/openwrt-master/staging_dir/toolchain-mips_74kc_gcc-5.3.0_musl-1.1.16/bin/mips-openwrt-linux-g++
$GPP main.c -o udt_main_test -O3 -I../src -L./ -L../src -ludt_trans -ludt
Android udt测试可以使用