最近弄了一個UDP/TCP的小東西,主要需要實現的功能如下(服務器端):
1、基於局域網
2、服務器端網絡接口為無線與有線
3、服務器端接收到客戶端的數據需要模擬按鍵進行處理
4、開機自啟動
5、使用UDP進行連接,TCP進行通訊
基於以上幾點,我們開始分析:
1.需要獲取當前的網絡IP地址,這里枚舉了本機所有的網絡地址,只返回ipv4
1 public String getAddressIP() { 2 //檢查網絡是否連接 3 while (!isNetWorkConnected()) { 4 //等待網絡連接 5 } 6 ip = getLocalIpAddress(); 7 return ip; 8 } 9 10 public String getLocalIpAddress() { 11 String address = null; 12 try { 13 for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();) { 14 NetworkInterface intf = en.nextElement(); 15 for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements();) { 16 InetAddress inetAddress = enumIpAddr.nextElement(); 17 if (!inetAddress.isLoopbackAddress()) {//127.0.0.1 18 address = inetAddress.getHostAddress().toString(); 19 //ipV6 20 if(!address.contains("::")){ 21 return address; 22 } 23 } 24 } 25 } 26 } catch (SocketException ex) { 27 Log.e("getIpAddress Exception", ex.toString()); 28 } 29 return null; 30 } 31 32 private boolean isNetWorkConnected() { 33 // TODO Auto-generated method stub 34 try{ 35 connectivity = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); 36 if(connectivity != null){ 37 netWorkinfo = connectivity.getActiveNetworkInfo(); 38 if(netWorkinfo != null && netWorkinfo.isAvailable()){ 39 if(netWorkinfo.getState() == NetworkInfo.State.CONNECTED){ 40 isConnected = true; 41 return true; 42 } 43 } 44 } 45 }catch(Exception e){ 46 Log.e("UdpService : ",e.toString()); 47 return false; 48 } 49 return false; 50 }
2.獲得IP之后,創建一個多播組
1 try { 2 3 while(ip == null){ 4 ip = getAddressIP(); 5 } 6 7 inetAddress = InetAddress.getByName(BROADCAST_IP);//多點廣播地址組 8 multicastSocket = new MulticastSocket(BROADCAST_PORT);//多點廣播套接字 9 multicastSocket.setTimeToLive(1); 10 multicastSocket.joinGroup(inetAddress); 11 12 } catch (UnknownHostException e) { 13 e.printStackTrace(); 14 } catch (IOException e) { 15 e.printStackTrace(); 16 }
這里設置一組特殊網絡地址作為多點廣播地址,第一個多點廣播地址都被看作是一個組,當客戶端需要發送接收廣播信息時,加入該組就可以了。
IP協議為多點廣播提供這批特殊的IP地址,這些IP地址范圍是224.0.0.0---239.255.255.255,其中224.0.0.0為系統自用。
下面BROADCAST_IP是自己聲明的一個String類型的變量,其范圍也是前面所說的IP范圍,比如BROADCAST_IP="224.224.224.224"。
1 private static int BROADCAST_PORT = 1234; 2 private static int PORT = 4444; 3 private static String BROADCAST_IP = "224.0.0.1";
3.服務端開始發送本機IP地址廣播,如果網絡斷開,則結束掉此線程,並設置標識
1 public class UDPBoardcastThread extends Thread { 2 public UDPBoardcastThread() { 3 this.start(); 4 } 5 6 @Override 7 public void run() { 8 DatagramPacket dataPacket = null; 9 //將本機的IP地址放到數據包里 10 byte[] data = ip.getBytes(); 11 dataPacket = new DatagramPacket(data, data.length, inetAddress, BROADCAST_PORT); 12 //判斷是否中斷連接了 13 while (isNetWorkConnected()) { 14 try { 15 multicastSocket.send(dataPacket); 16 Thread.sleep(5000); 17 Log.i("UDPService:","再次發送ip地址廣播"); 18 } catch (Exception e) { 19 e.printStackTrace(); 20 } 21 } 22 isConnected = false; 23 Message msg = new Message(); 24 msg.what = 0x0001; 25 mHandler01.sendMessage(msg); 26 27 } 28 }
4.新開一個線程,等待客戶端連接,使用TCP進行通訊
1 new Thread() { 2 @Override 3 public void run() { 4 try { 5 //建立一個線程池,每次收到一個客戶端,新開一個線程 6 mExecutorService = Executors.newCachedThreadPool(); 7 Socket client = null; 8 mList.clear(); 9 while (isConnected) { 10 11 client = server.accept(); 12 //把客戶端放入客戶端集合中 13 if (!connectOrNot(client)) { 14 mList.add(client); 15 Log.i("UDPService","當前連接數:"+mList.size()); 16 } 17 mExecutorService.execute(new Service(client)); 18 } 19 //釋放客戶端 20 for(int i = 0 ; i < mList.size() ; i++) 21 mList.get(i).close(); 22 23 } catch (IOException e) { 24 e.printStackTrace(); 25 } 26 } 27 }.start();
5.新開一個客戶端的線程,處理客戶端發送過來的數據等
1 //客戶端線程,組成線程池 2 class Service implements Runnable { 3 private Socket socket; 4 private BufferedReader in = null; 5 private String msg = ""; 6 7 public Service(Socket socket) { 8 this.socket = socket; 9 } 10 11 @Override 12 public void run() { 13 try { 14 in = new BufferedReader(new InputStreamReader(socket.getInputStream())); 15 //等待接收客戶端發送的數據 16 while (isConnected) { 17 18 if ((msg = in.readLine()) != null) { 19 20 // 創建一個Instrumentation對象,調用inst對象的按鍵模擬方法 21 Instrumentation inst = new Instrumentation(); 22 try{ 23 int codeKey = Integer.parseInt(msg); 24 //codeKey對應鍵值參照KeyCodeTable.txt文件,在客戶端中實現 25 inst.sendKeyDownUpSync(codeKey); 26 27 //發送回執 28 this.sendmsg(socket); 29 }catch(Exception ex){ 30 ex.printStackTrace(); 31 } 32 33 } 34 } 35 } catch (Exception e) { 36 e.printStackTrace(); 37 } 38 } 39 40 private void sendmsg(Socket socket2) { 41 // TODO Auto-generated method stub 42 PrintWriter pout = null; 43 44 try { 45 pout = new PrintWriter(new BufferedWriter( 46 new OutputStreamWriter(socket2.getOutputStream())), true); 47 pout.println("I am ok"); 48 } catch (IOException e) { 49 // TODO Auto-generated catch block 50 e.printStackTrace(); 51 } 52 53 } 54 55 }
這里使用了Instrumentation()對象來模擬按鍵的處理,在實際使用中,效率還行,沒有很嚴重的延時,若真有延時,感覺也是網絡方面的。
使用了socket.getInputStream()與socket.getOutputStream()方法來進行socket數據的接收與發送
6.最后新開一個Handler對網絡斷開時進行處理,也可以監聽系統網絡變化的廣播,有時間研究下service的生命周期
1 private Handler mHandler01 = new Handler(){ 2 3 @Override 4 public void handleMessage(Message msg) { 5 // TODO Auto-generated method stub 6 super.handleMessage(msg); 7 switch(msg.what){ 8 //連接失敗 9 case 0x0001: 10 initData(); 11 break; 12 } 13 } 14 15 };
7.開機自啟動,繼承BroadcastReceiver,監聽系統開機廣播就ok了,記得在AndroidManifest.xml文件中聲明BOOT_COMPLETED屬性
1 if(intent.getAction().equals("android.intent.action.BOOT_COMPLETED")){ 2 Intent intent2 = new Intent(context, UdpService.class); 3 context.startService(intent2); 4 }
8.還有一個問題,如果我們就這樣直接編譯,輸出apk到電視中,會出現權限不足的error,原因是apk不是系統應用,只有uid為system id才可以去模擬按鍵事件,所以在
AndroidManifest.xml中加上android:sharedUserId="android.uid.system",以及<uses-permission android:name="android.permission.INJECT_EVENTS" />
再編寫Android.mk,最后在android源碼中使用mm命令編譯apk,這樣就ok了。
服務器端的流程差不多是這樣了,附上完整源碼,包含服務器端與客戶端Demo:
http://download.csdn.net/detail/u012062785/9684842
thread與runnable的區別:https://www.oschina.net/question/565065_86563