最近弄了一个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 (Enumerationen = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();) { 14 NetworkInterface intf = en.nextElement(); 15 for (Enumeration enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements();) { 16 InetAddress inetAddress = enumIpAddr.nextElement(); 17 if (!inetAddress.isLoopbackAddress()) {//127.0.0.118 address = inetAddress.getHostAddress().toString(); 19 //ipV620 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 stub34 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 @Override12 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 stub42 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 block50 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:
thread与runnable的区别: