概要
开源项目ser2net作为Linux/openwrt系统下串口转以太网服务端使用非常广泛, 并且非常适合作为被动监听连接场景下的物联网设备使用,本文开发的ser2tcpclient就是将串口转为tcp协议为主的客户端,方便在局域网中实现高速的点对点通信。
整体架构流程
1.soc串口采用mavlink的状态机协议不断的接收mcu上报的数据帧。
2.无线网络管理器不断尝试搜索,连接,认证,握手,通信数据加解密,通信虚拟时间同步,双向数据透传和监听无线信号。
3.程序本身异常情况监听和处理。
技术细节
使用mavlink协议状态机接收mcu上报的数据帧
while(TRUE){
readbuflen = serial_recv(gd_serial_fd,readbuf,RECV_BUFFER_SIZE);
if(readbuflen <= 0){
continue;
}
//解析出完整的数据包
#if 0
printf("retval=%d\n",readbuflen);
print0x(readbuf,readbuflen);
#endif
databuf = readbuf;
is_parse_complete = 0;
while(readbuflen--)
{
//if(Mavlink_Frame_Char_Buffer(&rxmsg_dev,&status_dev,*databuf++,FALSE)
if(Mavlink_Frame_Char_Buffer(&rxmsg_dev,&status_dev,*databuf++,TRUE)
== MAVLINK_FRAMING_OK)
//!= MAVLINK_FRAMING_INCOMPLETE)
{
is_parse_complete = 1;
}
}
if(!is_parse_complete) continue;
#if 0
printf("recv on frame\n");
#endif
handle_mainboard_read(&rxmsg_dev);
}
无线信号的配对,连接,认证,状态监听的管理
/**
有效wifi的连接和tcp server的连接
*/
void* wifi_connect_manager_thread(void*param)
{
#define WIFI_CONNECT_TIMOUT (15)
#define IWINFO_NC_MAX_CNT (10)
UI8 trycnt;
I8 data[MAX_BUFFER_CACHE_SIZE];
I8*cur_board_connect_ssid;
BOOLEAN is_cur_board_connect_ssid;
I8*cur_board_connect_passwd;
BOOLEAN is_cur_board_connect_passwd;
I8*wlan0ip;
I8*iwinfoname;
I8*iwinfomac;
UI8 iwinfo_nc_cnt = 0;
UI8 is_iwinfo_need_check = 0;
int tmp_read_cnt = 0;
while(TRUE)
{
if(!strlen(curssid) || is_iwinfo_need_check){
if(is_iwinfo_need_check) is_iwinfo_need_check = 0;
iwinfoname = cmd_system(IWINFO_GET_NAME);
if(iwinfoname != NULL &&
strlen(iwinfoname) > 0 &&
strncmp(IWINFO_GET_NAME_UNKNOWN,iwinfoname,strlen(IWINFO_GET_NAME_UNKNOWN)))
{
//bakup cur name
memset(data,0,sizeof(data));
//需要去除两个双引号加一个换行
strncpy(data,iwinfoname + 1,strlen(iwinfoname) - 3 );
iwinfomac = cmd_system(IWINFO_GET_MAC);
if(iwinfomac != NULL &&
strlen(iwinfomac) > 0 &&
!strncmp(iwinfomac,DEVICE_FIX_MAC,10))
{
#ifdef DEBUG
printf("iwinfo get name & mac ok...\n");
#endif
memset(curssid,'\0',sizeof(curssid));
strcpy(curssid,data);
memset(curpasswd,'\0',sizeof(curpasswd));
strcpy(curpasswd,WPS_MODE_FIX_PASSWD);
continue;
}
else{
#ifdef DEBUG
printf("iwinfo get mac is invalid...\n");
#endif
}
}else{
#ifdef DEBUG
printf("iwinfo get name is invalid...\n");
#endif
}
#ifdef DEBUG
printf("curssid is invalid...\n");
#endif
usleep(DELAY_1S);
continue;
}
#ifdef DEBUG
printf("start to connect wifi=%s:%s\n",curssid,curpasswd);
#endif
//避免重复连接已经连上的WiFi
is_cur_board_connect_ssid = FALSE;
cur_board_connect_ssid = cmd_system(UCI_GET_SSID);
//忽略结尾0x0A
if(!strncmp(curssid,cur_board_connect_ssid,strlen(cur_board_connect_ssid) - 1)) is_cur_board_connect_ssid = TRUE;
is_cur_board_connect_passwd = FALSE;
cur_board_connect_passwd = cmd_system(UCI_GET_PASSWD);
//忽略结尾0x0A
if(!strncmp(curpasswd,cur_board_connect_passwd,strlen(cur_board_connect_passwd) - 1)) is_cur_board_connect_passwd = TRUE;
if(!(is_cur_board_connect_ssid &&
is_cur_board_connect_passwd
)){
#ifdef DEBUG
printf("wifi_connect do...\n");
#endif
wifi_connect(curssid,curpasswd);
//当切换WiFi时,当前连接的WiFi对wlan0的状态有影响
//故延时消除
//usleep(DELAY_3S);
usleep(DELAY_2S);
#ifdef DEBUG
printf("wifi_connect do complete...\n");
#endif
trycnt = 0;
while(!wlan0_status() && trycnt++ < WIFI_CONNECT_TIMOUT){
#ifdef DEBUG
printf("wifi connect failed!\n");
#endif
usleep(DELAY_100MS);
}
if(trycnt >= WIFI_CONNECT_TIMOUT){
//当一直连接失败时,iwinfo也要周期性检查
if(iwinfo_nc_cnt++ >= IWINFO_NC_MAX_CNT / 2){
iwinfo_nc_cnt = 0;
is_iwinfo_need_check = 1;
}
continue;
}else{
iwinfo_nc_cnt = 0;
}
}
/**
WiFi配置已经都对得上,但无线网络接口还未连接上,
此种情况等待系统去重新连接wifi即可,
而不必亲自动手,在开机自动连接和断开重连方面能节省时间
*/
if(!wlan0_status())
{
#ifdef DEBUG
printf("wait system to connect target wifi...\n");
#endif
usleep(DELAY_100MS);
continue;
}
#ifdef DEBUG
printf("wifi connect success!\n");
#endif
//不同网段对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 = "";
}
#ifdef DEBUG
printf("wlan0ip=%s\n",wlan0ip);
#endif
if(strncmp(SERVER_IP,wlan0ip,9)){
#ifdef DEBUG
printf("local ip SERVER_IP are not in lan\n");
#endif
continue;
}
memset(data,0,sizeof(data));
sprintf(data,"%s&%s",curssid,curpasswd);
if(write_file(WIFI_CONFIG_PATH,data,strlen(data)) < 0){
#ifdef DEBUG
printf("save valid wifi failed!\n");
#endif
}
#ifdef DEBUG
printf("save valid wifi success!\n");
#endif
//当一直连接成功时却未建立有效连接,iwinfo也要周期性检查
if(iwinfo_nc_cnt++ >= IWINFO_NC_MAX_CNT){
iwinfo_nc_cnt = 0;
is_iwinfo_need_check = 1;
#ifdef DEBUG
printf("iwinfo period check!\n");
#endif
continue;
}
trycnt = 0;
is_wifi_info_prepare = 0;
//有新的连接请求就断开当前连接,去连接新的连接
while(TRUE)
{
if(!is_tcp_client_connect)
{
tcp_client_socket_deinit();
usleep(DELAY_300MS);
if(tcp_client_socket_init() < 0){
if(trycnt++ < 3){
continue;
}else{
break;
}
}
is_tcp_client_connect = 1;
}
else
{
//新的网络请求
if(is_wifi_info_prepare){
#ifdef DEBUG
printf("new wifi connect request...\n");
#endif
is_tcp_client_connect = 0;
break;
}
/*当用户将设备关闭时,是得不到任何反馈信息的,所以采用状态检测*/
//网络断开
//连续累加
trycnt = 0;
while(!wlan0_status()){
#ifdef DEBUG
printf("wlan0_status is test\n");
#endif
if(trycnt++ >= WIFI_CONNECT_TIMOUT){
tcp_client_socket_deinit();
is_tcp_client_connect = 0;
#ifdef DEBUG
printf("wlan0 status is down\n");
#endif
break;
}
usleep(DELAY_100MS);
}
//连接断开
if(!is_tcp_client_connect){
#ifdef DEBUG
printf("tcp client disconnect 1\n");
#endif
pthread_mutex_lock(&serial_mutex);
serial_send(gd_serial_fd,wifi_connect_failed,sizeof(wifi_connect_failed));
pthread_mutex_unlock(&serial_mutex);
break;
}
//间接检测tcp是否断开
trycnt = 0;
tmp_read_cnt = global_read_cnt;
while(tmp_read_cnt == global_read_cnt)
{
/*
#ifdef DEBUG
printf("tmp_read_cnt=%d,global_read_cnt=%d,trycnt=%d\n",
tmp_read_cnt,global_read_cnt,trycnt);
#endif
*/
//等待6s
if(trycnt++ >= 12)
{
tcp_client_socket_deinit();
is_tcp_client_connect = 0;
#ifdef DEBUG
printf("wait 6s over...is_tcp_client_connect=%d\n",is_tcp_client_connect);
#endif
break;
}
usleep(DELAY_500MS);
}
//连接断开
if(!is_tcp_client_connect){
#ifdef DEBUG
printf("tcp client disconnect 2\n");
#endif
pthread_mutex_lock(&serial_mutex);
serial_send(gd_serial_fd,wifi_connect_failed,sizeof(wifi_connect_failed));
pthread_mutex_unlock(&serial_mutex);
break;
}
}
//正常连接状态需要休眠
usleep(DELAY_100MS);
}
}
pthread_detach(pthread_self());
}
网络连接的认证,握手,同步,加解密,双向数据透传管理
//负责client socket的连接初始化
int tcp_client_socket_init()
{
static pthread_t send_t, rec_t;
struct sockaddr_in serv_addr;
UI8 tcprxbuf[RECV_LENGTH];
int readlen;
int options = 1;
void*retcode = NULL;
/**
重新连接时不需要反复分配socket句柄
*/
if(gd_sock_client == -1){
#ifdef DEBUG
printf("new socket handler create...\n");
#endif
gd_sock_client = socket(AF_INET, SOCK_STREAM, 0);
}
if(gd_sock_client < 0){
#ifdef DEBUG
printf("The client socket is not create!\n");
#endif
return -1;
}
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = inet_addr(SERVER_IP);
serv_addr.sin_port = htons(SERVER_PORT);
if (connect(gd_sock_client, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0){
//global_tcp_close(gd_sock_client);
//gd_sock_client = -1;
#ifdef DEBUG
printf("The tcp client connect is not build!\n");
#endif
return -1;
}
//配置socketfd阻塞属性
//fcntl(gd_sock_client,F_SETFL,fcntl(gd_sock_client,F_GETFL,0) & ~O_NONBLOCK);
if(setsockopt(gd_sock_client, IPPROTO_TCP, TCP_NODELAY,(char *) &options, sizeof(options)) == -1){
#ifdef DEBUG
printf("setsockopt TCP_NODELAY failed\n");
#endif
global_tcp_close(gd_sock_client);
gd_sock_client = -1;
return -1;
}
//设置tcp心跳保持连接
if (setsockopt(gd_sock_client, SOL_SOCKET, SO_KEEPALIVE, (void *)&options,sizeof(options)) == -1) {
#ifdef DEBUG
printf("setsockopt SO_KEEPALIVE failed\n");
#endif
global_tcp_close(gd_sock_client);
gd_sock_client = -1;
return -1;
}
memset(tcprxbuf,0,sizeof(tcprxbuf));
//读取连接状态
readlen = read(gd_sock_client,tcprxbuf,RECV_LENGTH);
//支持协议
if(!strcmp(CONNECT_STATUS_ERROR,tcprxbuf)){
global_tcp_close(gd_sock_client);
gd_sock_client = -1;
#ifdef DEBUG
printf("socket connect status:%s\n",CONNECT_STATUS_ERROR);
#endif
return -1;
}else if(!strcmp(CONNECT_STATUS_SUCCESS,tcprxbuf)){
#ifdef DEBUG
printf("socket connect status:%s\n",CONNECT_STATUS_SUCCESS);
#endif
}else{
global_tcp_close(gd_sock_client);
gd_sock_client = -1;
return -1;
}
//协议握手
if(handle_benchmark_opts() < 0) {
global_tcp_close(gd_sock_client);
gd_sock_client = -1;
return -1;
}
if(handle_comm_protocol() < 0) {
global_tcp_close(gd_sock_client);
gd_sock_client = -1;
return -1;
}
alarm_cnt = 0;
timer_setup();
bm_calibrate_cnt = 0;
global_read_cnt = 0;
//清空缓冲队列
tcflush(gd_serial_fd,TCIFLUSH);
tcflush(gd_serial_fd,TCOFLUSH);
pthread_mutex_lock(&inqueue_mutex);
set_clear();
pthread_mutex_unlock(&inqueue_mutex);
//
if(is_send_stream_runing)
{
pthread_cancel(send_t);
pthread_join(send_t,retcode);
usleep(DELAY_100MS);
#ifdef DEBUG
printf("send_t thread exit code=%d\n",(int)retcode);
#endif
}
is_send_stream_break = 0;
if (pthread_create(&send_t, NULL, tcp_send_stream, NULL) != 0){
is_send_stream_break = 1;
global_tcp_close(gd_sock_client);
gd_sock_client = -1;
#ifdef DEBUG
printf("The thread of send is not create!\n");
#endif
return -1;
}
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
}
is_recv_stream_break = 0;
if (pthread_create(&rec_t, NULL, tcp_recv_stream, NULL) != 0){
is_recv_stream_break = 1;
global_tcp_close(gd_sock_client);
gd_sock_client = -1;
#ifdef DEBUG
printf("The thread of rec is not create!\n");
#endif
return -1;
}
#ifdef DEBUG
printf("tcp connect server ok!\n");
#endif
pthread_mutex_lock(&serial_mutex);
serial_send(gd_serial_fd,wifi_connect_ok,sizeof(wifi_connect_ok));
pthread_mutex_unlock(&serial_mutex);
return 0;
}
双向透传数据上行和下行任务
//负责tcp socket的全部发送任务
void* tcp_send_stream(void*param)
{
UI8*sendata;
UI8 sendatalen;
UI8 is_alloc;
is_send_stream_runing = 1;
while (!is_send_stream_break)
{
if(gd_sock_client != -1 && is_tcp_client_connect && !is_empty()){
pthread_mutex_lock(&inqueue_mutex);
out_queue(&sendata,&sendatalen,&is_alloc);
pthread_mutex_unlock(&inqueue_mutex);
if(sendata != NULL && sendatalen > 0){
if(tcp_data_io_write(gd_sock_client,sendata,sendatalen) < 0)
{
#ifdef DEBUG
printf("tcp_data_io_write failed\n");
#endif
if(gd_sock_client != -1)
global_tcp_close(gd_sock_client);
is_send_stream_break = 1;
is_recv_stream_break = 1;
gd_sock_client = -1;
is_tcp_client_connect = 0;
//对于动态分配的内存需要手动释放
if(is_alloc)
{
buffree(sendata);
}
break;
}
if(is_alloc)
{
buffree(sendata);
}
}
}
else{
usleep(DELAY_1MS);
}
}
/**
创建一个线程默认的状态是joinable, 如果一个线程结束运行但没有被join,
则它的状态类似于进程中的Zombie Process,
即还有一部分资源没有被回收(退出状态码),
所以创建线程者应该调用pthread_join来等待线程运行结束,
并可得到线程的退出代码,回收其资源(类似于wait,waitpid)
但是调用pthread_join(pthread_id)后,如果该线程
没有运行结束,
调用者会被阻塞,在有些情况下我们并不希望如此,
比如在Web服务器中当主线程为每个新来的链接创建一个子线程进行处理的时候,
主线程并不希望因为调用pthread_join而阻塞(因为还要继续处理之后到来的链接),
这时可以在子线程中加入代码
pthread_detach(pthread_self())
或者父线程调用
pthread_detach(thread_id)(非阻塞,可立即返回)
这将该子线程的状态设置为detached,则该线程运行结束后会自动释放所有资源。
*/
pthread_detach(pthread_self());
is_send_stream_runing = 0;
#ifdef DEBUG
printf(".........tcp_send_stream exit............\n");
#endif
}
//负责tcp socket的全部接收任务
void* tcp_recv_stream(void*param)
{
UI8 tcprxbuf[RECV_LENGTH]={0};
int count;
int try_cnt;
is_recv_stream_runing = 1;
while (!is_recv_stream_break)
{
count = read(gd_sock_client, tcprxbuf, RECV_LENGTH);
if (count < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK) {
continue;
}
#ifdef DEBUG
printf("tcp_recv_stream count < 0\n");
#endif
/* Got an error on the read, shut down the port. */
#if 1
if(gd_sock_client != -1)
global_tcp_close(gd_sock_client);
is_send_stream_break = 1;
is_recv_stream_break = 1;
gd_sock_client = -1;
is_tcp_client_connect = 0;
break;
#endif
}
else if (count == 0) {
/* The other end closed the port, shut it down. */
#ifdef DEBUG
printf("tcp_recv_stream count==0\n");
#endif
if(gd_sock_client != -1)
global_tcp_close(gd_sock_client);
is_send_stream_break = 1;
is_recv_stream_break = 1;
gd_sock_client = -1;
is_tcp_client_connect = 0;
break;
}
#if 0
#ifdef DEBUG
printf("tcp_recv_stream:");
print0x(tcprxbuf,count);
#endif
#endif
/
//长度限制
UI8 buf_len = count;
if(buf_len < PROTOCOL_MIN_LEN || buf_len > PROTOCOL_MAX_LEN) continue;
//向手机转发
/*if(gd_local_mobile_client != -1){
tcp_data_io_write(gd_local_mobile_client,tcprxbuf,buf_len);
}*/
//异或解密
UI8* decbuf = decode_protocol(tcprxbuf,buf_len);
//校验
UI32 sam = check_sam(decbuf,buf_len);
UI32 decbufsam = decbuf[PROTOCOL_SAMH_INDEX(buf_len)] << 8 | decbuf[PROTOCOL_SAML_INDEX(buf_len)];
#if 0
#ifdef DEBUG
print0x(decbuf,buf_len);
printf("sam=%08x,decbufsam=%08x\n",sam,decbufsam);
#endif
#endif
if(sam != decbufsam) continue;
//平台命令过滤
if(handle_platform_opts(decbuf,buf_len) >= 0) continue;
//mavlink接收与校验
UI8*prxmsg;
UI8 retval;
UI8 tmplen = buf_len - PROTOCOL_EXTRA_LEN;
status_tcp.parse_state = MAVLINK_PARSE_STATE_IDLE;
while(tmplen--)
{
retval = Mavlink_Frame_Char_Buffer(&rxmsg_tcp,&status_tcp,*decbuf++,TRUE);
if(MAVLINK_FRAMING_INCOMPLETE == retval)
continue;
else if(MAVLINK_FRAMING_BAD_CRC == retval)
break;
else
{
prxmsg = (UI8*)&rxmsg_tcp + CHECKSUM_OFFSET_LEN;
break;
}
}
#if 0
#ifdef DEBUG
printf("tcp_recv_stream retval=%d,tmplen=%d\n",retval,tmplen);
print0x(prxmsg,buf_len - PROTOCOL_EXTRA_LEN);
#endif
#endif
if(retval == MAVLINK_FRAMING_BAD_CRC) continue;
//加强协议完整检测
if(!(tmplen == 0 && retval == MAVLINK_FRAMING_OK)) continue;
buf_len -= PROTOCOL_EXTRA_LEN;
//远程应答回复指令过滤
//if(!(buf_len >= NON_PAYLOAD_LEN && prxmsg[5] == PROTOCOL_CUSTOM_ECHO_CTL)) continue;
prxmsg[PROTOCOL_MAVLINK_SAMH_INDEX(buf_len)] = MAVLINK_TAIL_REPLACE_CKA;
prxmsg[PROTOCOL_MAVLINK_SAML_INDEX(buf_len)] = MAVLINK_TAIL_REPLACE_CKB;
//统计
global_read_cnt++;
try_cnt = 0;
try_ser_write:
pthread_mutex_lock(&serial_mutex);
count = serial_send(gd_serial_fd,prxmsg,buf_len);
pthread_mutex_unlock(&serial_mutex);
if(count < 0 && try_cnt++ < 3) goto try_ser_write;
}
//防止内存泄露
//pthread_detach(pthread_self());
pthread_detach(pthread_self());
is_recv_stream_runing = 0;
#ifdef DEBUG
printf(".........tcp_recv_stream exit............\n");
#endif
}
check_tcp_close_wait.sh在后台监听由服务端异常导致的情况。
#轮询检查CLOSE_WAIT,重启堵塞进程
while true
do
pid=$(netstat -anp | grep 192.168.2.1:16888 | grep CLOSE_WAIT | awk '{print $7}' | cut -d \/ -f1 | grep -oE "[[:digit:]]{1,}")
if [ ! -n "$pid" ]; then
echo "check_tcp_close_wait IS NULL"
else
killall -9 ser2tcpclient
sleep 1
ser2tcpclient &
echo "check_tcp_close_wait NOT NULL"
fi
sleep 3
done