Article directory
-
- summary
- overall architecture process
- technical details
- summary
Summary
The open source project ser2net is widely used as a serial-to-Ethernet server under the Linux/openwrt system, and is very suitable for use as an IoT device in a passive monitoring connection scenario. The ser2tcpclient developed in this paper is a client that converts serial ports to tcp protocols. , to facilitate the realization of high-speed point-to-point communication in the local area network.
Overall architecture process
1. The soc serial port uses the state machine protocol of mavlink to continuously receive the data frames reported by the mcu.
2. The wireless network manager keeps trying to search, connect, authenticate, shake hands, encrypt and decrypt communication data, synchronize virtual time of communication, two-way data transparent transmission and monitor wireless signals.
3. Monitor and handle the abnormal situation of the program itself.
Technical details
Use the mavlink protocol state machine to receive the data frame reported by the mcu
while(TRUE){ readbuflen = serial_recv(gd_serial_fd,readbuf,RECV_BUFFER_SIZE); if(readbuflen <= 0){ continue; } \t // parse out the complete packet #if 0 printf("retval=%d\\ ",readbuflen); print0x(readbuf,readbuflen); #endif \t databuf = readbuf; is_parse_complete = 0; \t 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; } } \t if(!is_parse_complete) continue; #if 0 printf("recv on frame\\ "); #endif handle_mainboard_read( &rxmsg_dev); \t }
Management of wireless signal pairing, connection, authentication, and status monitoring
/** Effective wifi connection and tcp server connection */ 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; \t 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 & amp; & amp; strlen(iwinfoname) > 0 & amp; & amp; strncmp(IWINFO_GET_NAME_UNKNOWN, iwinfoname, strlen(IWINFO_GET_NAME_UNKNOWN))) { //bakup cur name memset(data,0,sizeof(data)); //Need to remove two double quotes and add a newline strncpy(data, iwinfoname + 1, strlen(iwinfoname) - 3 ); iwinfomac = cmd_system(IWINFO_GET_MAC); if(iwinfomac != NULL & amp; & amp; strlen(iwinfomac) > 0 & amp; & amp; !strncmp(iwinfomac,DEVICE_FIX_MAC,10)) { #ifdef DEBUG printf("iwinfo get name & amp; mac ok...\\ "); #endif memset(curssid,'\0',sizeof(curssid)); strcpy(curssid,data); memset(curpasswd,'\0',sizeof(curpasswd)); strcpy(curpasswd,WPS_MODE_FIX_PASSWD); \t\t\t\t\t continue; } else { #ifdef DEBUG printf("iwinfo get mac is invalid...\\ "); #endif } }else{ #ifdef DEBUG printf("iwinfo get name is invalid...\\ "); #endif } #ifdef DEBUG printf("curssid is invalid...\\ "); #endif usleep(DELAY_1S); continue; } #ifdef DEBUG printf("start to connect wifi=%s:%s\\ ",curssid,curpasswd); #endif //Avoid repeated connection to already connected WiFi is_cur_board_connect_ssid = FALSE; cur_board_connect_ssid = cmd_system(UCI_GET_SSID); //ignore ending 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); //ignore ending 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 & amp; & amp; is_cur_board_connect_passwd )){ #ifdef DEBUG printf("wifi_connect do...\\ "); #endif wifi_connect(curssid,curpasswd); //When switching WiFi, the currently connected WiFi has an impact on the status of wlan0 //So the delay is eliminated //usleep(DELAY_3S); usleep(DELAY_2S); #ifdef DEBUG printf("wifi_connect do complete...\\ "); #endif trycnt = 0; while(!wlan0_status() & amp; & amp; trycnt + + < WIFI_CONNECT_TIMOUT){ #ifdef DEBUG printf("wifi connect failed!\\ "); #endif usleep(DELAY_100MS); } if(trycnt >= WIFI_CONNECT_TIMOUT){ //When the connection fails all the time, iwinfo should also check periodically if(iwinfo_nc_cnt + + >= IWINFO_NC_MAX_CNT / 2){ iwinfo_nc_cnt = 0; is_iwinfo_need_check = 1; } continue; }else{ iwinfo_nc_cnt = 0; } } /** The WiFi configuration is all right, but the wireless network interface is not connected yet, In this case, just wait for the system to reconnect to wifi, Instead of having to do it yourself, it can save time in terms of automatic connection and disconnection after power on */ if(!wlan0_status()) { #ifdef DEBUG printf("wait system to connect target wifi...\\ "); #endif usleep(DELAY_100MS); continue; } #ifdef DEBUG printf("wifi connect success!\\ "); #endif //Different network segments have an impact on socket connections wlan0ip = cmd_system(WLAN0_GET_IP); trycnt = 0; while((wlan0ip == NULL || strlen(wlan0ip) == 0) & amp; & amp; trycnt ++ < 5) { usleep(DELAY_100MS); wlan0ip = cmd_system(WLAN0_GET_IP); } if(trycnt >= 5) { if(wlan0ip == NULL) wlan0ip = ""; } \t\t #ifdef DEBUG printf("wlan0ip=%s\\ ",wlan0ip); #endif if(strncmp(SERVER_IP,wlan0ip,9)){ #ifdef DEBUG printf("local ip SERVER_IP are not in lan\\ "); #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!\\ "); #endif } #ifdef DEBUG printf("save valid wifi success!\\ "); #endif //When the connection has been successful but no valid connection has been established, iwinfo should also check periodically if(iwinfo_nc_cnt + + >= IWINFO_NC_MAX_CNT){ iwinfo_nc_cnt = 0; is_iwinfo_need_check = 1; #ifdef DEBUG printf("iwinfo period check!\\ "); #endif continue; } \t\t trycnt = 0; is_wifi_info_prepare = 0; \t\t //If there is a new connection request, disconnect the current connection and connect to the new connection 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 { //new network request if(is_wifi_info_prepare){ #ifdef DEBUG printf("new wifi connect request...\\ "); #endif is_tcp_client_connect = 0; break; } /*When the user turns off the device, there is no feedback information, so the state detection is used*/ \t\t\t\t //Disconnected from the network //Continuous accumulation trycnt = 0; while(!wlan0_status()){ #ifdef DEBUG printf("wlan0_status is test\\ "); #endif if(trycnt + + >= WIFI_CONNECT_TIMOUT){ tcp_client_socket_deinit(); is_tcp_client_connect = 0; #ifdef DEBUG printf("wlan0 status is down\\ "); #endif \t\t\t\t\t\t break; } usleep(DELAY_100MS); } \t\t\t\t //Disconnect if(!is_tcp_client_connect){ #ifdef DEBUG printf("tcp client disconnect 1\\ "); #endif pthread_mutex_lock( & serial_mutex); serial_send(gd_serial_fd, wifi_connect_failed, sizeof(wifi_connect_failed)); pthread_mutex_unlock( & serial_mutex); \t\t\t\t\t break; } //Indirectly detect whether tcp is disconnected 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\\ ", tmp_read_cnt, global_read_cnt, trycnt); #endif */ //Wait for 6s if(trycnt + + >= 12) { tcp_client_socket_deinit(); is_tcp_client_connect = 0; #ifdef DEBUG printf("wait 6s over...is_tcp_client_connect=%d\\ ",is_tcp_client_connect); #endif break; } usleep(DELAY_500MS); } \t\t\t\t //Disconnect if(!is_tcp_client_connect){ #ifdef DEBUG printf("tcp client disconnect 2\\ "); #endif pthread_mutex_lock( & serial_mutex); serial_send(gd_serial_fd, wifi_connect_failed, sizeof(wifi_connect_failed)); pthread_mutex_unlock( & serial_mutex); \t\t\t\t\t break; } } //The normal connection state needs to sleep usleep(DELAY_100MS); \t\t\t } } pthread_detach(pthread_self()); }
Network connection authentication, handshake, synchronization, encryption and decryption, two-way data transparent transmission management
//Responsible for connection initialization of 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; /** There is no need to repeatedly allocate socket handles when reconnecting */ if(gd_sock_client == -1){ #ifdef DEBUG printf("new socket handler create...\\ "); #endif gd_sock_client = socket(AF_INET, SOCK_STREAM, 0); } if(gd_sock_client < 0){ #ifdef DEBUG printf("The client socket is not create!\\ "); #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*) & amp;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!\\ "); #endif return -1; } //Configure socketfd blocking properties //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 *) & amp;options, sizeof(options)) == -1){ #ifdef DEBUG printf("setsockopt TCP_NODELAY failed\\ "); #endif global_tcp_close(gd_sock_client); gd_sock_client = -1; \t return -1; } \t //Set tcp heartbeat to keep connected if (setsockopt(gd_sock_client, SOL_SOCKET, SO_KEEPALIVE, (void *) & options, sizeof(options)) == -1) { #ifdef DEBUG printf("setsockopt SO_KEEPALIVE failed\\ "); #endif global_tcp_close(gd_sock_client); gd_sock_client = -1; \t return -1; } memset(tcprxbuf,0,sizeof(tcprxbuf)); // read connection status readlen = read(gd_sock_client,tcprxbuf,RECV_LENGTH); \t//supporting agreement if(!strcmp(CONNECT_STATUS_ERROR,tcprxbuf)){ \t\t global_tcp_close(gd_sock_client); gd_sock_client = -1; \t\t #ifdef DEBUG printf("socket connect status:%s\\ ",CONNECT_STATUS_ERROR); #endif return -1; }else if(!strcmp(CONNECT_STATUS_SUCCESS,tcprxbuf)){ #ifdef DEBUG printf("socket connect status:%s\\ ",CONNECT_STATUS_SUCCESS); #endif }else{ \t global_tcp_close(gd_sock_client); gd_sock_client = -1; return -1; } //protocol handshake 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; // Clear the buffer queue 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_running) { pthread_cancel(send_t); pthread_join(send_t, retcode); \t\t usleep(DELAY_100MS); #ifdef DEBUG printf("send_t thread exit code=%d\\ ",(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; \t\t #ifdef DEBUG printf("The thread of send is not create!\\ "); #endif return -1; } \t if(is_recv_stream_running) { pthread_cancel(rec_t); pthread_join(rec_t, retcode); usleep(DELAY_100MS); #ifdef DEBUG printf("rec_t thread exit code=%d\\ ",(int)retcode); #endif } \t 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; \t\t #ifdef DEBUG printf("The thread of rec is not create!\\ "); #endif return -1; } \t #ifdef DEBUG printf("tcp connect server ok!\\ "); #endif pthread_mutex_lock( & serial_mutex); serial_send(gd_serial_fd, wifi_connect_ok, sizeof(wifi_connect_ok)); pthread_mutex_unlock( & serial_mutex); \t return 0; }
Two-way transparent transmission of data uplink and downlink tasks
//Responsible for all sending tasks of tcp socket void* tcp_send_stream(void*param) { UI8*sendata; UI8 sendatalen; UI8 is_alloc; is_send_stream_running = 1; while (!is_send_stream_break) { if(gd_sock_client != -1 & amp; & amp; is_tcp_client_connect & amp; & amp; !is_empty()){ pthread_mutex_lock( & inqueue_mutex); out_queue( &sendata, &sendatalen, &is_alloc); pthread_mutex_unlock( & inqueue_mutex); if(sendata != NULL & amp; & amp; sendatalen > 0){ if(tcp_data_io_write(gd_sock_client,sendata,sendatalen) < 0) { #ifdef DEBUG printf("tcp_data_io_write failed\\ "); #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; //The dynamically allocated memory needs to be released manually if(is_alloc) { buffree(sendata); } \t\t\t\t break; } if(is_alloc) { buffree(sendata); } } } else { usleep(DELAY_1MS); } } /** The default state of creating a thread is joinable, if a thread finishes running but has not been joined, Then its state is similar to Zombie Process in the process, That is, some resources have not been recovered (exit status code), So the creator of the thread should call pthread_join to wait for the thread to finish running, And get the exit code of the thread, reclaim its resources (similar to wait, waitpid) But after calling pthread_join(pthread_id), if the thread did not run to end, The caller will be blocked, in some cases we do not want this, For example, in a web server, when the main thread creates a sub-thread for each new link to process, The main thread does not want to be blocked by calling pthread_join (because it will continue to process the incoming links), At this time, you can add code in the child thread pthread_detach(pthread_self()) or the parent thread calls pthread_detach(thread_id) (non-blocking, return immediately) This sets the state of the child thread to detached, and all resources will be released automatically after the thread finishes running. */ pthread_detach(pthread_self()); is_send_stream_running = 0; #ifdef DEBUG printf("......tcp_send_stream exit......\\ "); #endif } //Responsible for all receiving tasks of tcp socket void* tcp_recv_stream(void*param) { UI8 tcprxbuf[RECV_LENGTH]={0}; int count; int try_cnt; \t is_recv_stream_running = 1; while (!is_recv_stream_break) { count = read(gd_sock_client, tcprxbuf, RECV_LENGTH); \t\t if (count < 0) { if (errno == EAGAIN || errno == EWOULDBLOCK) { continue; } #ifdef DEBUG printf("tcp_recv_stream count < 0\\ "); #endif /* Got an error on the read, shut down the port. */ #if 1 \t\t\t 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\\ "); #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 / // length limit UI8 buf_len = count; if(buf_len < PROTOCOL_MIN_LEN || buf_len > PROTOCOL_MAX_LEN) continue; //forward to phone /*if(gd_local_mobile_client != -1){ tcp_data_io_write(gd_local_mobile_client, tcprxbuf, buf_len); }*/ \t\t \t\t // XOR decryption UI8* decbuf = decode_protocol(tcprxbuf,buf_len); // check 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=x,decbufsam=x\\ ",sam,decbufsam); #endif #endif \t\t if(sam != decbufsam) continue; //Platform command filtering if(handle_platform_opts(decbuf,buf_len) >= 0) continue; //mavlink receiving and verification UI8*prxmsg; UI8 retval; UI8 tmplen = buf_len - PROTOCOL_EXTRA_LEN; status_tcp.parse_state = MAVLINK_PARSE_STATE_IDLE; \t while(tmplen--) { retval = Mavlink_Frame_Char_Buffer( &rxmsg_tcp, &status_tcp,*decbuf++,TRUE); \t\t\t 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\\ ",retval,tmplen); print0x(prxmsg,buf_len - PROTOCOL_EXTRA_LEN); #endif #endif if(retval == MAVLINK_FRAMING_BAD_CRC) continue; //Strengthen protocol integrity detection if(!(tmplen == 0 & amp; & amp; retval == MAVLINK_FRAMING_OK)) continue; \t \t buf_len -= PROTOCOL_EXTRA_LEN; //Remote response reply command filtering //if(!(buf_len >= NON_PAYLOAD_LEN & amp; & amp; 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; //statistics 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); \t\t if(count < 0 & amp; & amp; try_cnt + + < 3) goto try_ser_write; } //Prevent memory leaks //pthread_detach(pthread_self()); pthread_detach(pthread_self()); is_recv_stream_running = 0; #ifdef DEBUG printf("......tcp_recv_stream exit......\\ "); #endif }
check_tcp_close_wait.sh monitors the situation caused by server exceptions in the background.
#Poll check CLOSE_WAIT, restart the blocked process 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" the fi sleep 3 done
Summary
full source code