文章目录概要整体架构流程技术细节小结概要开源项目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 count0\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小结完整源码