socket通信——多角度控製LED燈亮滅
今天以物聯網網關(網關鏈接)以服務器,在多個客戶端就做一個非常簡單的功能:點亮或熄滅網關上的LED燈。目前想到了三種方式,分別是:TCP&UDP測試工具、自編Java客戶端和Mono
Android客戶端。相信這會很有意思的。
1、服務器端
在使用或編寫客戶端之前,首先來看看服務器端代碼,其專門通過串口燒進網關內部
OutputPort led = new OutputPort((Cpu.Pin)GPIO_NAMES.PF8, false); Socket sc; Socket ss = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//創建tcp套接字 IPEndPoint iep = new IPEndPoint(IPAddress.Parse("192.168.13.200"), 8888);//網關IP已靜態配置,端口自選 try { ss.Bind(iep); ss.Listen(3); Debug.Print("create server ok"); } catch { Debug.Print("connection failed"); }
然後等待客戶端的數據並在終端打印,根據所收信息來判斷燈亮滅,最後向客戶端發反饋
while (true) { if ((sc = ss.Accept()) != null) { Debug.Print("someone connected:" + sc.RemoteEndPoint.ToString()); while (true) { Array.Clear(data, 0, data.Length);//一定要清 sc.Receive(data, 16, 0);//從java客戶端收到的包括\r\n string str = new string(System.Text.Encoding.UTF8.GetChars(data)); Debug.Print(str); Debug.Print(str.Length.ToString()); if (str.IndexOf("ON") >= 0) // = 號不要忽略了 { led.Write(true); } else if (str.IndexOf("OFF") >= 0) { led.Write(false); } Debug.Print("recv ok,about to send "); sc.Send(System.Text.Encoding.UTF8.GetBytes(str.IndexOf("ON") >= 0 ? "ON\r\n" : "OFF\r\n"));//為方便java中的readline,添加了行結束符 } } }
注釋的地方基本上是我犯錯的地方
2、客戶端
2.1、TCP&UDP測試工具
測試效果如下:
2.2、Java客戶端
代碼如下:
public class LightLED { private Socket client; private String host = "192.168.13.200"; private int port = 8888; private String on = "ON"; private String off = "OFF"; public static void main(String[] args) throws InterruptedException,IOException { // TODO Auto-generated method stub new LightLED().doLED(); } public LightLED() throws IOException{ client = new Socket(host,port); System.out.println("connected..."); } public void doLED() throws IOException,InterruptedException { try { BufferedReader br = getReader(client); PrintWriter pw = getWriter(client); while (true){ pw.println(on); //不能用print,為什麼?而且發過去的包括\r\n System.out.println("LED: " + br.readLine()); Thread.sleep(1000); pw.println(off); System.out.println("LED: " + br.readLine()); Thread.sleep(1000); } } catch (IOException e) { // TODO: handle exception } } private PrintWriter getWriter(Socket s) throws IOException{ OutputStream ous = s.getOutputStream(); return new PrintWriter(ous,true); } private BufferedReader getReader(Socket s) throws IOException { InputStream ins = s.getInputStream(); return new BufferedReader(new InputStreamReader(ins)); } }
VS調試窗口:
Java調試窗口:
2.3、Mono Android客戶端
其實用C#也可以寫android程序的,並且還可跨平台,也輕鬆移植到IOS上。它使用的框架是Mono,開發環境是Xamarin,以前叫Mono Develop。網上有很多教程,我在這裏就不細說了。我本人的相關軟件放在了這裏
之前我也沒用過mono寫過android程序,所以今天是個很好的嚐試。事實證明算這一客戶端最有趣了,待我細細道來。。
我突然想改變一下前麵的想法,由於網關上有3個LED燈,於是這次想讓客戶端同時操作這3個燈。當我發一個數值時,相應的燈亮或滅。具體細則是這樣規定的:我所發送的數值範圍是0-7,共8個數,化成二進製恰好可以代表3個燈的狀態,1為亮,0為滅,就這樣簡單定義。而且,為了使得多個android客戶端可同時登陸服務器,利用多線程方式來實現。主線程隻負責接收客戶端連接,每個客戶端對應一個單獨線程來與服務器通信。
既然這樣,服務器端代碼就得變了,見下:
首先還是服務器的初使化:
Socket sc = null; Socket ss = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//創建tcp套接字 IPEndPoint iep = new IPEndPoint(IPAddress.Parse("192.168.13.200"), 8888);//網關IP已靜態配置,端口自選 try { ss.Bind(iep); ss.Listen(5); Debug.Print("create server ok"); } catch { Debug.Print("connection failed"); }
然後無限循環接收客戶連接:
while (true) { byte[] recedata = new byte[1]; if ((sc = ss.Accept()) != null) { Debug.Print("someone connected:" + sc.RemoteEndPoint.ToString()); ClientThread ch = new ClientThread(sc);//創建客戶線程類 Thread t = new Thread(new ThreadStart(ch.service)); t.Start(); } }
相信已發現上麵用到了一個類ClientThread,這是我自定義的:
internal class ClientThread { enum GPIO_NAMES { PA0, PA1, PA2, PA3, PA4, PA5, PA6, PA7, PA8, PA9, PA10, PA11, PA12, PA13, PA14, PA15, PB0, PB1, PB2, PB3, PB4, PB5, PB6, PB7, PB8, PB9, PB10, PB11, PB12, PB13, PB14, PB15, PC0, PC1, PC2, PC3, PC4, PC5, PC6, PC7, PC8, PC9, PC10, PC11, PC12, PC13, PC14, PC15, PD0, PD1, PD2, PD3, PD4, PD5, PD6, PD7, PD8, PD9, PD10, PD11, PD12, PD13, PD14, PD15, PE0, PE1, PE2, PE3, PE4, PE5, PE6, PE7, PE8, PE9, PE10, PE11, PE12, PE13, PE14, PE15, PF0, PF1, PF2, PF3, PF4, PF5, PF6, PF7, PF8, PF9, PF10, PF11, PF12, PF13, PF14, PF15, PG0, PG1, PG2, PG3, PG4, PG5, PG6, PG7, PG8, PG9, PG10, PG11, PG12, PG13, PG14, PG15 }; private Socket sc; OutputPort led1 = new OutputPort((Cpu.Pin)GPIO_NAMES.PF8, false);//第一個LED OutputPort led2 = new OutputPort((Cpu.Pin)GPIO_NAMES.PF7, false);//第二個LED OutputPort led3 = new OutputPort((Cpu.Pin)GPIO_NAMES.PF6, false);//第三個LED public ClientThread(Socket s) { sc = s; //sc.ReceiveTimeout = 60000; // 1分鍾內若未收到數據,則關閉連接。由於在虛擬設備中反應很慢,所以就注釋掉了 } public void service() { byte[] recedata = new byte[1];//目前隻接收0-7的某個數 try { while (sc.Receive(recedata) != 0) { Debug.Print(recedata[0].ToString()); doLED(recedata);//根據數值點亮或熄滅燈 Array.Clear(recedata,0,1); } } catch (System.Exception ex) { sc.Close(); Debug.Print("rece timeout" + ex.Message); } } private void doLED(byte[] recedata) { //感覺下麵寫得不太簡潔 byte L1 = (byte)(recedata[0] >> 2); byte L2 = (byte)((recedata[0] & 3) >> 1); byte L3 = (byte)(recedata[0] & 1); if (L1 == 1) { led1.Write(true); } else { led1.Write(false); } if (L2 == 1) { led2.Write(true); } else { led2.Write(false); } if (L3 == 1) { led3.Write(true); } else { led3.Write(false); } } }
好,以上就是修改過的服務器端。下麵是用c#寫的android客戶端。安裝好相關軟件,打開Xamarin,創建android工程:
相關細節就不詳述了,具體可參考官方文檔,寫得很詳細,其實還是用到了android開發的相關概念比如Activity
IDE已經為你生成了相關框架代碼,生成了類MainActivity,繼承自Activity,重點修改其OnCreate方法,它在界麵出現時被調用。
首先創建一些私有量:
private Socket sc = null; private string host = "192.168.13.200";//服務器端IP和端口 private int port = 8888;
修改OnCreate方法如下:
protected override void OnCreate (Bundle bundle) { base.OnCreate (bundle); var layout = new LinearLayout (this); layout.Orientation = Orientation.Vertical; var lbl = new TextView (this); lbl.Text = "hello,xmarin.android"; sc= new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); try { sc.Connect(IPAddress.Parse(host),port);//連接服務器 lbl.Text = "connected with gateway server"; } catch { lbl.Text = "conn failed"; } EditText et = new EditText (this); var btn = new Button (this); btn.Text = "click and send"; btn.Click += (sender, e) => { //添加事件處理 doLED(et.Text); }; layout.AddView (lbl); layout.AddView (et); layout.AddView (btn); SetContentView (layout); }
public void doLED(string leddata) { byte num = Convert.ToByte (leddata);//獲取EditText的數值 byte[] senddata = new byte[1] {num}; sc.Send (senddata); }
最後來一張界麵截圖:
Yoxi.. 客戶端代碼就是這樣,接下來配置好android模擬器,先在模擬器中跑一跑。在服務器端設一個斷點,首先開啟服務器,然後再開啟Xamarin中的程序。一段時間的等待後(android模擬器啟動忒慢),部署程序到模擬器,自動啟動了客戶端。於是首先在文本框中輸入7,使3個燈全亮,見下圖:
果然,三燈全亮。再輸入4,使第1燈亮,其餘二個燈全滅:
最後輸入0,不用說,全滅。如果沒有反應,可嚐試給網關重新上電。
好,最後一招,將客戶端部署到android手機。注意,要提前破解Mono for Android,而且不要簡簡單單地把bin目錄中的apk安裝到手機中,我試過,不能啟動。正確做法是將手機連接PC,安裝好驅動後,在IDE中可看到設備:
我在第一次部署到手機時,碰到了這個問題:FastDev directory creation failed。經查詢這裏有了答案:
https://bugzilla.xamarin.com/show_bug.cgi?id=14474
所以我首先采用release模式部署,再改成debug模式,最後部署成功。
啟動android客戶端,連上路由器使手機與網關在同一個局域網內。先後輸入7,4,0,三燈反應正常:
VS調試窗口如下:
OK,到目前為止,我的目標總算是實現了,能在android跑還是挺歡喜的。邏輯上沒問題,至於界麵的美化嘛,慢慢修改唄。
其實,又有了個新想法,可創建一個html5 移動web應用,放在手機上運行,應該也可以。那個前端東西我不是很熟悉就不做了,感覺應該也不難。或者這樣也可以,以網關為服務器,以Netduino為客戶端,通過紅外操作netduino,從而控製網關,這樣也不錯。今天暫擱筆於此,以後有想法再補充。
最後更新:2017-04-03 08:26:12