實驗: 取得 RSSI 並回傳至伺服器

利用 Socket 的架構,即時的取得 RSSI 資訊,並回傳至後端伺服器

整體 RSSI 蒐集架構

本實驗中的程式以及說明由蔡東倫先生所準備,作者只做些微修改與補充。

本實驗的目標為取得監聽的 RSSI 資訊並回傳至後端的伺服器。一開始我們有一個 WiFi 裝置透過OpenWRT WiFi AP 進行資料傳輸,此 OpenWRT WiFi AP 以建立兩張網卡,分別處於 master 模式以及 monitor 模式,因此可以一邊幫該使用者轉傳封包,一邊記錄下 RSSI 資訊,並送到後端的伺服器。

伺服器和 WiFi AP 的資料傳輸使用 socket 模式,換言之,伺服器為 socket server,而 WiFi AP 為 socket client,兩者透過 polling 的方式將資料回傳。

甚麼是 socket?

簡單來說,socket 是一種透過網路傳輸資料的方式,透過指定的 IP 和 port,就可以進行通訊。在此架構下,socket client 會傳送連線需求至 socket server 的 IP 與指定的 port,socket server 會持續的監聽該 port 的連線需求,若有 client 的連線需求,則建立連線,並開始資料雙向交換。事實上,socket 有分成許多不同的類別,若有興趣,可以閱讀:

WiFi AP 端程式

之前的實驗中,我們在 OpenWRT 上建立虛擬無線網卡,模式分別為 AP 模式與 Monitor 模式。接著用 tcpdump 監聽 Monitor 模式的無線網卡,並用 grep 指令保留同時包含 RSSI 值與source MAC 的訊息。上述的動作皆可以由 C 語言建立 pipe 時觸發,接著在 C 語言實作 parser將所要資訊擷取出來。

pipe 函式簡介

如果我們除了想在 C 程式執行指令之外,也想要透過標準輸入與輸出與指令互動的話,pipe 函式比起 system 函式更加適於這種情境。在 Linux 系統中,為了使用將多個程式組合在一起,會在程式之間建立單向串列管道 (pipe),使得某一程式的輸出做為另一程式的輸入。在 Linux console 中,使用符號 “|” 即可建立 pipe,對於要處理大量序列的程式 (如:grep) 這是一種常見的作法。

在 C 語言中,利用popen()可以做到相同事情。在popen()中第一個輸入為要執行的程式或指令,第二個輸入 pipe 為讀取 (“r”) 或寫入 (“w”),其回傳值為檔案指標。只要在方向正確的情形下,可以用舊有的檔案處理函式 (fprintf、fscanf) 去讀寫這個 pipe。若要關閉 pipe 則用 pclose() 函式。使用此函式請記得引入 "unistd.h"。在許多情況下,pipe 會比 system 方便。以下是程式範例:

FILE *fp; char buffer[1000]; fp = popen(“ifconfig”, "r"); fgets(buffer, sizeof(buffer), fp); printf(“%s”, buffer); pclose(fp);

以下附的是參考程式碼,由於使用了 thread 因此用 gcc 編譯時需加上 -lpthread,才能編譯成功。在此程式中使用了兩個 thread:

  • sensing_func(): 負責讀取 pipe、取出對應的 RSSI、計算 RSSI 平均值。

  • report_func(): 當 socket server polling 時回報所得到的 RSSI 資訊。

為了避免兩者同時存取共用記憶體,在 critical section 使用了 mutex ,避免對 RSSI 數值同時存取,造成錯誤。以下是所撰寫的程式 (RSSI_client.c):

#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/socket.h"
#include "netinet/in.h"
#include "netdb.h"
#include "signal.h"

#define MAX_USER_NUM 200
#define ReconnectionDelayTime 2
#define WLAN "wlan0"

char* server_ip;
int server_port;

void *sensing_func(void *parm);
void *report_func(void *parm);
char* get_interface_MAC_string();
char* search_pattern_following(char* str1, char* str2);
static pthread_t sensing_thread;
static pthread_t report_thread;
static pthread_mutex_t sensing_report_mutex;

static int sensing_socket;
static int construct_link(char ip[], int port);
void disconnection(int sig);

struct sensing_result
{
	char src_mac[100];
	float rssi_mean;
	float rssi_variance;
	int sample_num;
};
static struct sensing_result sensing_result[100];
static int sensing_result_num=0;

int main(int argc, char *argv[])
{
	if(argc<3)
	{
		printf("argument: [Server IP] [Server Port]\n");
		return;
	}
	else
	{
		server_ip = argv[1];
		server_port = atoi(argv[2]);
	}
	(void)signal(SIGINT, disconnection);

	pthread_mutex_init(&sensing_report_mutex, NULL);
	pthread_mutex_lock(&sensing_report_mutex);

	pthread_create(&sensing_thread, NULL, sensing_func, NULL);
	pthread_create(&report_thread, NULL, report_func, NULL);

	pthread_mutex_unlock(&sensing_report_mutex);

	while(1);

	return 0;
}

#define TCPDUMP_CMD "tcpdump -ne -y ieee802_11_radio -s 65535 -i wlan1-2 |grep \"signal\" | grep \"SA\\|TA\" |grep \"dBm\""

void *sensing_func(void *parm)
{
	FILE *fp;
	char sensing_dump[1000];
	char src_mac[100];
	float rssi;
	float old_rssi_mean;
	float new_rssi_mean;
	float old_rssi_variance;
	float new_rssi_variance;
	int i;

	if( (fp = popen(TCPDUMP_CMD, "r"))== NULL)
	{
		printf("popen() error!\n");
		exit(1);
	}

	while(fgets(sensing_dump, sizeof(sensing_dump), fp))
	{
		sscanf(strstr(sensing_dump, "dBm ")-4, "%fdB", &rssi);

		memset(src_mac, 0, sizeof(src_mac));
		if(strstr(sensing_dump, "SA:")!=NULL)
		{
			sscanf(strstr(sensing_dump, "SA:")+3, "%s", src_mac);
		}
		else if(strstr(sensing_dump, "TA:")!=NULL)
		{
			sscanf(strstr(sensing_dump, "TA:")+3, "%s", src_mac);
		}
		else
		{
			printf("ERROR: SA and TA are not found\n");
		}

		for(i=0;i<strlen(src_mac);i++) if(islower(src_mac[i]))
		{
			src_mac[i]=toupper(src_mac[i]);
		}

		pthread_mutex_lock(&sensing_report_mutex);

		//search old item
		for(i=0;i<sensing_result_num;i++)
		{
			if(strcmp(sensing_result[i].src_mac, src_mac)==0)
			{
				//policy 1: keep the latest RSSI sample for all devices
				new_rssi_mean = rssi;
				new_rssi_variance = 0;
				sensing_result[i].rssi_mean=new_rssi_mean;
				sensing_result[i].rssi_variance=new_rssi_variance;
				sensing_result[i].sample_num=1;
				
				break;
			}
		}

		//create new item if necessary
		if(i==sensing_result_num)
		{
			memcpy(sensing_result[i].src_mac, src_mac, strlen(src_mac));
			sensing_result[i].rssi_mean=rssi;
			sensing_result[i].rssi_variance=0.0;
			sensing_result[i].sample_num=1;
			sensing_result_num++;
		}

		pthread_mutex_unlock(&sensing_report_mutex);

	}

	pclose(fp);

	return;
}

void *report_func(void *parm)
{
	int i;
	char sensing_report[100];
	char rcv_buf[100];
	
start:
	sensing_socket = construct_link(server_ip, server_port);

	while(1)
	{
		//format: [round] [observer MAC] [target MAC] [RSSI]

		memset(rcv_buf, 0, sizeof(rcv_buf));
		while(read(sensing_socket, rcv_buf, sizeof(rcv_buf))==0); //wait for polling

		printf("%s is received\n", rcv_buf);
		pthread_mutex_lock(&sensing_report_mutex);

		for(i=0;i<sensing_result_num;i++)
		{
			printf("sensing_result[%d].src_mac=%s\n", i, sensing_result[i].src_mac);
			if(1) 
			{
				memset(sensing_report, 0, sizeof(sensing_report));
				sprintf(sensing_report, "%s %s %s %f\n",
					rcv_buf,
					get_interface_MAC_string(),
					sensing_result[i].src_mac,
					sensing_result[i].rssi_mean);
					
				printf("%s is sent\n", sensing_report);

				if(write(sensing_socket, sensing_report, strlen(sensing_report))<=0)
				{
					printf("%s:socket write error\n", __FUNCTION__);
					goto start;
				}
			}
		}

		memset(sensing_result, 0, sizeof(sensing_result));
		sensing_result_num=0;

		pthread_mutex_unlock(&sensing_report_mutex);

		printf("\n");
	}
}

void disconnection(int sig)
{
	close(sensing_socket);
	(void)signal(SIGINT, SIG_DFL);
}

static int construct_link(char ip[], int port)
{
	int client_fd = -1;
	struct hostent *host;
	struct sockaddr_in client_sin;

	while(client_fd <= 0)
	{
		if((host = gethostbyname(ip)) == NULL)
		{
			fprintf(stderr, "Can't get ip !!!\n");
			return -1;
		}
		client_fd = socket(AF_INET, SOCK_STREAM, 0);
		bzero(&client_sin, sizeof(client_sin));
		client_sin.sin_family = AF_INET;
		client_sin.sin_addr = *((struct in_addr *)host->h_addr);
		client_sin.sin_port = htons(port);
		if(connect(client_fd, (struct sockaddr *)&client_sin, sizeof(client_sin)) == -1)
		{
			fprintf(stderr, "Can't connect to server \n");
			fprintf(stdout, "Reconstruct link after 2 seconds \n");
			client_fd = -1;
			sleep(ReconnectionDelayTime);
		}
	}
	return client_fd;
}

char* get_interface_MAC_string()
{
	FILE *fp;
	char cmd[100];
	static char ret[50];

	char line_buf[100];
	char *cursor;
	int mac[6];

	memset(ret, 0, sizeof(ret));
	sprintf(ret, "-1");

	memset(cmd, 0, sizeof(cmd));
	sprintf(cmd, "ifconfig %s", WLAN);

	if((fp=popen(cmd, "r"))!=NULL)
	{
		while(!feof(fp))
		{
			memset(line_buf, 0, sizeof(line_buf));
			fgets(line_buf, sizeof(line_buf), fp);

			if( (cursor=search_pattern_following(line_buf, "HWaddr ")) != NULL )
			{
				sscanf(cursor, "%02X:%02X:%02X:%02X:%02X:%02X",
					&mac[0], &mac[1], &mac[2],
					&mac[3], &mac[4], &mac[5] );

				memset(ret, 0, sizeof(ret));
				sprintf(ret, "%02X:%02X:%02X:%02X:%02X:%02X",
					mac[0], mac[1], mac[2],
					mac[3], mac[4], mac[5] );
					
				break;
			}

		}
		pclose(fp);
	}
	else
	{
		printf("%s:Fail to open the file\n", __FUNCTION__);
	}

	return ret;
}

char* search_pattern_following(char* str1, char* str2)
{
	char* ret;
	if( (ret=strstr(str1, str2)) != NULL ) ret+=strlen(str2);

	return ret;
}

編譯時指令為:

 mips-openwrt-linux-gcc RSSI_client.c --static -lpthread -o RSSI_client

會出現許多警告,如下所示,不過不影響使用:

ofwrt@ofwrt-18:~/ofwrt_code$ mips-openwrt-linux-gcc RSSI_client.c --static -lpthread -o RSSI_client
RSSI_client_smartAnt.c: In function 'main':
RSSI_client_smartAnt.c:48:3: warning: 'return' with no value, in function returning non-void
   return;
   ^~~~~~
RSSI_client_smartAnt.c:43:5: note: declared here
 int main(int argc, char *argv[])
     ^~~~
RSSI_client_smartAnt.c:57:2: warning: implicit declaration of function 'pthread_mutex_init'; did you mean 'pthread_kill'? [-Wimplicit-function-declaration]
  pthread_mutex_init(&sensing_report_mutex, NULL);
  ^~~~~~~~~~~~~~~~~~
  pthread_kill
RSSI_client_smartAnt.c:58:2: warning: implicit declaration of function 'pthread_mutex_lock'; did you mean 'pthread_kill'? [-Wimplicit-function-declaration]
  pthread_mutex_lock(&sensing_report_mutex);
  ^~~~~~~~~~~~~~~~~~
  pthread_kill
RSSI_client_smartAnt.c:60:2: warning: implicit declaration of function 'pthread_create'; did you mean 'pthread_kill'? [-Wimplicit-function-declaration]
  pthread_create(&sensing_thread, NULL, sensing_func, NULL);
  ^~~~~~~~~~~~~~
  pthread_kill
RSSI_client_smartAnt.c:63:2: warning: implicit declaration of function 'pthread_mutex_unlock' [-Wimplicit-function-declaration]
  pthread_mutex_unlock(&sensing_report_mutex);
  ^~~~~~~~~~~~~~~~~~~~
RSSI_client_smartAnt.c: In function 'sensing_func':
RSSI_client_smartAnt.c:108:37: warning: implicit declaration of function 'islower' [-Wimplicit-function-declaration]
   for(i=0;i<strlen(src_mac);i++) if(islower(src_mac[i]))
                                     ^~~~~~~
RSSI_client_smartAnt.c:110:15: warning: implicit declaration of function 'toupper' [-Wimplicit-function-declaration]
    src_mac[i]=toupper(src_mac[i]);
               ^~~~~~~
RSSI_client_smartAnt.c:167:2: warning: 'return' with no value, in function returning non-void
  return;
  ^~~~~~
RSSI_client_smartAnt.c:72:7: note: declared here
 void *sensing_func(void *parm)
       ^~~~~~~~~~~~

伺服器端程式

client 程式為執行在OpenWRT上的 C 程式,執行時需附上 server IP 與 port,即 ./{client程式名} {server IP} {server port}。另外,在使用 CRTL+C 中止程式時,請用指令 ps 確認是否所有執行緒皆已中止,並用指令 kill 中止殘留多餘執行緒。若遇到無法使用CRTL+C 中止程式的情形時,重新建立一個新的 SSH 連線並用指令 kill 中止程式。

在 server 與 client 之間,我們依不同方向制定了二種溝通格式,參數之間以空白隔開、訊息之間以換行隔開。server 觸發 client 回報最新的 RSSI 時,會附上該次觸發的序號 (round)。為了讓client 能支援回報多個傳送端的封包 RSSI、並讓 server 能區分回報結果屬於哪回合,client 回報時會附上 server 傳來的序號 (round)、傳送端 (target MAC) 與RSSI (單位為dBm)。

方向

格式

server → client

[round]

client → server

[round] [observer MAC] [target MAC] [RSSI]

透過此格式設定,server 會定期要求 WiFi AP 回傳 RSSI 訊號,並顯示於螢幕上。

import java.io.*;
import java.net.*;
import java.util.*;

public class RSSI_Server
{
	static public void main(String[] args) throws Exception
	{
		Server Server = new Server();
		Server.start();
		
		IdleHandler IdleHandler=new IdleHandler(Server);
		IdleHandler.start();
		
		//guard interval for access
		Thread.sleep(1000);
		
		int interval=0;
		int round=0;
		String FileName;
		
		Scanner input=new Scanner(System.in);
		
		while(true)
		{
			System.out.println("Enter parameters for sensing");
			
			System.out.println("Enter sensing interval(msec):");
			try
			{
				interval = Integer.valueOf(input.nextLine());
			}
			catch(NumberFormatException e)
			{
				System.out.println("Format ERROR!!");
				continue;
			}
			
			System.out.println("Enter total sensing round:");
			try
			{
				round = Integer.valueOf(input.nextLine());
			}
			catch(NumberFormatException e)
			{
				System.out.println("Format ERROR!!");
				continue;
			}
			
			System.out.println("Enter FileName to save result:");
			if( (FileName = input.nextLine())==null)
			{
				continue;
			}
				
			for(int i=0;i<round;i++)
			{
				Server.sendSensingCommandToAll(String.valueOf(i));
				
				Thread.sleep(interval*3/4);
				Server.getSensingResultFromAll(FileName);
				
				Thread.sleep(interval/4);
				System.out.println(interval+"msec passed");
			}
			
			IdleHandler.clearWatchDogCount();
		}
	}
}

class Server extends Thread
{
	List<Client> users;
		
	public void run()
	{
		users = new ArrayList<Client>();
		System.out.println("server start.");
		
		try
		{
			ServerSocket ss = new ServerSocket(2223);
			
			while(true)
			{
				Socket s = ss.accept(); //listen 2223
																
				Client u = new Client(s, users);
				users.add(u);
				u.start();

				System.out.println(users.size() +" clients are connected");
			}
		}
		catch (IOException e)
		{
			e.printStackTrace();
			System.out.println("server exception while listening.");
		}
	}
	
	public void sendSensingCommandToAll(String cmd)
	{
		for(Client u:users)
		{
			u.sendSensingCommand(cmd);
		}
	}
	
	public void getSensingResultFromAll(String FileName)
	{
		try
		{
			BufferedWriter bufferedWriter=new BufferedWriter(new FileWriter(FileName, true));
			
			for(Client u:users)
			{
				String result = u.getSensingResult();
				if(result!=null)
				{
					bufferedWriter.write(result+'\n');
				}
			}
			
			bufferedWriter.write('\n');
			bufferedWriter.close();
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
	}
}

class Client extends Thread
{
	Socket UserSocket;
	List<Client> users;
	String message;
	
	Client(Socket s, List<Client> list)
	{
		UserSocket=s;
		users=list;
	}
	
	public void run()
	{
		//server-to-client format: [round]
		//client-to-server format: [round] [observer MAC] [target MAC] [RSSI]
		
		try
		{
			BufferedReader in = new BufferedReader(new InputStreamReader(UserSocket.getInputStream()));
			
			while( (message=in.readLine())!=null)
			{
				System.out.println(message);
			}
		}
		catch (Exception e)
		{
			e.printStackTrace();
			System.out.println("an user is leaving");
			return;
		}
	}
	
	public void sendSensingCommand(String cmd)
	{
		try
		{
			BufferedWriter out = new BufferedWriter(new OutputStreamWriter(UserSocket.getOutputStream()));
			out.write(cmd);
			out.flush();
		}
		catch (Exception e)
		{
			e.printStackTrace();
		}
		
	}
	
	public String getSensingResult()
	{
		String ret=null;
		
		if(message!=null)
		{
			ret = message;
			message = null;
		}
		
		return ret;
	}
}

class IdleHandler extends Thread
{
	int WatchDogCount=0;
	Server Server;
	
	IdleHandler(Server s)
	{
		Server = s;
	}
	
	public void run()
	{
		while(true)
		{
			try
			{
				Thread.sleep(1000);
			}
			catch (InterruptedException e)
			{
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			WatchDogCount++;
			
			if(WatchDogCount>=60)
			{
				Server.sendSensingCommandToAll(String.valueOf(-1));
				WatchDogCount=0;
			}
		}
	}
	
	void clearWatchDogCount()
	{
		WatchDogCount=0;
	}
}

Last updated