本實驗的目標為取得監聽的 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 有分成許多不同的類別,若有興趣,可以閱讀:
在之前的實驗中,我們在 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 方便。以下是程式範例:
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 會定期要求 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;
}
}