安卓真机上UDP程序收不到PC机数据包的解决方法

   最近在写安卓UDP通信程序时遇到这个问题,就是在安卓模拟器上可以实现收发,但是放到真机上就只能发收不到PC机发来的数据在网上查了一些资料,发现有很多朋友也遇到这个问题,都没有非常明确的解决方法,我经过尝试终于在真机上收到了PC机发的数据,跟大家分享一下。我用的是eclipse开发环境,安卓虚拟机为2.2版本。为方便讲解,先贴几张图:

               图一 安卓模拟器界面

                                                                             图二 PC机服务程序界面

    图一为安卓虚拟机上的界面,在真机上除了文字位置有一些错位外其它都一致(偷个懒就不贴真机效果的照片了),远程IP地址是你的PC端服务程序所在PC的公网IP;远程端口是PC服务程序接收端口;本地端口是手机发送数据时用的端口。图二为测试时用的一个PC端服务程序。PC机上的服务程序必须要能解析出包中的IP和端口号,建议用我提供的PC端服务器,不保证使用其它PC端服务器能实现收发。上图二中的服务程序是在模拟机测试时用的,在真机测试时用不了(好像是我的电脑在内网,它只能读出内网地址的原因)。

    如果在安卓模拟器上收不到数据,要把PC机端口与模拟器连接一下,具体方法参考《android模拟器接收不到UDP数据包》。还要注意,用真机发送数据时远程IP要写成服务器所在PC机的公网IP手机上远程端口与PC机服务器上接收端口一致,手机上本地端口任意设。

    对于遇到这个问题的朋友,前面所介绍的很多是多余的,下面进入主题,结合源码应该能讲的清楚些,先贴出来:

[java]  view plain copy
  1. <span style="FONT-SIZE: 16px">package udp.net;  
  2.   
  3. import java.io.IOException;  
  4. import java.net.DatagramPacket;  
  5. import java.net.DatagramSocket;  
  6. import java.net.InetAddress;  
  7. import android.app.Activity;  
  8. import android.app.AlertDialog;  
  9. import android.content.DialogInterface;  
  10. import android.os.Bundle;  
  11. import android.os.Handler;  
  12. import android.os.Message;  
  13. import android.view.KeyEvent;  
  14. import android.view.View;  
  15. import android.view.View.OnClickListener;  
  16. import android.widget.Button;  
  17. import android.widget.TextView;  
  18.   
  19. public class UDPActivity extends Activity {  
  20.     /** Called when the activity is first created. */  
  21.       
  22.     //收发用同一个socket  ,只在连接时实例化一次  
  23.     private DatagramSocket socketUDP=null;  
  24.     private Thread    thread=null;  
  25.     private TextView  textSend;  
  26.     private TextView  textReceive;  
  27.     private TextView  portRemote;  
  28.     private TextView  portLocal;  
  29.     private TextView  textIP;  
  30.     private Button    btnSend;  
  31.     private Button    btnConnect;  
  32.     private Button    btnDisConnect;  
  33.     private int       portRemoteNum;  
  34.     private int       portLocalNum;   
  35.     private String    addressIP;  
  36.     private String    revData;  
  37.     private boolean   flag=false;  
  38.       
  39.     @Override  
  40.     public void onCreate(Bundle savedInstanceState) {  
  41.         super.onCreate(savedInstanceState);  
  42.         setContentView(R.layout.main);  
  43.          //与UI关联         
  44.         textSend = (TextView)findViewById(R.id.textsend);  
  45.         textReceive = (TextView)findViewById(R.id.textrcecive);  
  46.         textIP = (TextView)findViewById(R.id.textip);         
  47.         portRemote = (TextView)findViewById(R.id.textPortRemote);  
  48.         portLocal = (TextView)findViewById(R.id.textPortLocal);  
  49.         btnSend = (Button) findViewById(R.id.btnsend);  
  50.         btnSend.setEnabled(false);  
  51.         btnConnect = (Button) findViewById(R.id.btnconnect);  
  52.         btnDisConnect = (Button) findViewById(R.id.btndisconnect);  
  53.         btnDisConnect.setEnabled(false);  
  54.           
  55.         buttonAction();  
  56.   
  57.     }  
  58.       
  59.     //设置按键监听及动作  
  60.     public void buttonAction(){  
  61.         btnSend.setOnClickListener(new OnClickListener() {  
  62.             public void onClick(View v) {  
  63.                 // TODO Auto-generated method stub  
  64.                 sendData();   
  65.             }         
  66.         });   
  67.           
  68.         btnConnect.setOnClickListener(new OnClickListener() {  
  69.             public void onClick(View v) {  
  70.                 // TODO Auto-generated method stub                
  71.             try{  
  72.                  flag=true;  
  73.                  portRemoteNum=Integer.parseInt(portRemote.getText().toString());  
  74.                  portLocalNum=Integer.parseInt(portLocal.getText().toString());  
  75.                  addressIP = textIP.getText().toString();    
  76.                 //用设定的本地端口发数据,括号内也可以为空  
  77.                  socketUDP = new DatagramSocket(portLocalNum);  
  78.                 // 启动接收线程,用本地端口接收,因为本地端口和PC解析出来端口的对应  
  79.                  thread = new Thread(revMsg);  
  80.                  thread.start();  
  81.                  btnConnect.setEnabled(false);  
  82.                  btnDisConnect.setEnabled(true);   
  83.                  btnSend.setEnabled(true);  
  84.                  textReceive.setText("已连接");  
  85.                } catch (Exception e) {  
  86.                    // TODO Auto-generated catch block  
  87.                     e.printStackTrace();  
  88.               }                   
  89.   
  90.               }  
  91.         });   
  92.           
  93.         btnDisConnect.setOnClickListener(new OnClickListener() {  
  94.             public void onClick(View v) {  
  95.                 // TODO Auto-generated method stub  
  96.                     flag=false;//结束线程  
  97.                     socketUDP.close();  
  98.                     btnConnect.setEnabled(true);  
  99.                     btnDisConnect.setEnabled(false);  
  100.                     btnSend.setEnabled(false);  
  101.             }  
  102.         });   
  103.     }  
  104.   
  105.     void sendData()  
  106.     {  
  107.         try {  
  108.                               
  109.             InetAddress serverAddress = InetAddress.getByName(addressIP);  
  110.             String str = textSend.getText().toString();          
  111.             byte data [] = str.getBytes();   
  112.             DatagramPacket packetS = new DatagramPacket(data,  
  113.                     data.length,serverAddress,portRemoteNum);     
  114.             //从本地端口给指定IP的远程端口发数据包  
  115.             socketUDP.send(packetS);  
  116.             } catch (Exception e) {  
  117.                 // TODO Auto-generated catch block  
  118.                 e.printStackTrace();  
  119.             }  
  120.     }  
  121.       
  122.     private Handler handler = new Handler()  
  123.     {  
  124.         @Override  
  125.         public void handleMessage(Message msg)  
  126.         {  
  127.             if (textReceive != null)  
  128.             {  
  129.             if (revData==nullreturn;  
  130.                 StringBuilder sb = new StringBuilder();  
  131.                 sb.append(textReceive.getText().toString().trim());  
  132.                 sb.append("\n");  
  133.                 sb.append(revData);  
  134.                 textReceive.setText(sb.toString().trim());  
  135.                 sb.delete(0, sb.length());  
  136.                 sb = null;  
  137.             }  
  138.         }  
  139.     };      
  140.     private Runnable revMsg = new Runnable() {  
  141.         @Override  
  142.         public void run()  
  143.         {  
  144.             while (flag)  
  145.             {             
  146.                 byte data[] = new byte[1024];         
  147.                 DatagramPacket packetR = new DatagramPacket(data, data.length);           
  148.                 try {  
  149.                       socketUDP.receive(packetR);  
  150.                       revData = new String(packetR.getData(),  
  151.                               packetR.getOffset(),packetR.getLength());  
  152.                       handler.sendEmptyMessage(0);  
  153.                     } catch (IOException e) {  
  154.                         // TODO Auto-generated catch block  
  155.                         e.printStackTrace();  
  156.                     }                 
  157.             }  
  158.         }  
  159.     };  
  160.     //退出提示  
  161.     @Override  
  162.     public boolean onKeyDown(int keyCode, KeyEvent event) {  
  163.   
  164.         if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {  
  165.   
  166.             AlertDialog alertDialog = new AlertDialog.Builder(  
  167.                 UDPActivity.this).setTitle("退出程序").setMessage("是否退出程序")  
  168.                 .setPositiveButton("确定",  
  169.                         new DialogInterface.OnClickListener() {  
  170.   
  171.                             public void onClick(DialogInterface dialog,  
  172.                                     int which) {  
  173.                                        android.os.Process.killProcess(  
  174.                                                android.os.Process.myPid());  
  175.                             }  
  176.   
  177.                         })  
  178.                 .setNegativeButton("取消",  
  179.                     new DialogInterface.OnClickListener() {  
  180.                         public void onClick(DialogInterface dialog, int which) {  
  181.                             return;  
  182.                         }  
  183.                     }).create(); // 创建对话框  
  184.   
  185.            alertDialog.show(); // 显示对话框  
  186.              
  187.             return false;  
  188.         }  
  189.           
  190.         return false;  
  191.     }  
  192. }</span><span style="FONT-SIZE: 16px">  
  193. </span>  

       开始的时候手机一直收不到数据,如果解析出PC机上收到的数据,会发现手机发送端口一直在变,这是第一个关键点。问题就在于多数人会在每次发送数据时都用DatagrmSocketsocket=new DatagramSocket()这句代码新建一个socket(其中括号内没有参数就会由手机分配空闲端口发数据,有参数则根据参数分配),那么每次新建手机就会分配不同的端口,即使分配同一个端口经网关映射后也可能不一样(端口会变的根源)。手机发出的数据在PC机收到后解析出来的地址(包括IP和端口)和设定是不同的,这是由于经过网关映射的缘故,所以要求PC端服务程序必须能够解析出来映射后的地址,但这时我们也只是能够知道映射后的发送端口,其它的端口还是不知道,所以只能按照映射后的地址回发才能被手机收到,用手机的发送端口接收,这也是下面讲只建一个socket的原因之一,这是第二个关键点

    解决问题的关键:第一,只建立一个socket用来收发数据,每次点击连接时新建,中间不在新建或close同一端口的socket直到点击断开,这样PC端服务程序解析出的端口就在断开前不会变了,这可以解决前一段提到的两个问题。第二,PC端服务程序要具有解析功能,最好用我提供的。顺带提一下,如果同一个端口的socket在没有close的时候再次新建会出现程序自动退出的现象。
    总结一下:关键点就是只建立一个socket用来收发数据。再个就是在连接时新建socket,在没有执行socketUDP.close的时候不要新建同样端口的socket否则会造成冲突,使程序退出。最后就是PC端服务器的问题,要能够解析收到数据的地址信息。

    差不多讲完了,在此感谢广大网友的帮助,特别对上面引用到的文章作者表示感谢,我的PC端服务程序参考了《Android实现TCP与UDP传输》一文,对作者也表示一下感谢。不知道有没有讲清楚,我的整个工程可以到手机端程序下载,建议的PC端服务端程序是java的需要在eclipse中运行,可以到PC端程序下载,欢迎有好的建议或问题的朋友跟我交流。我的邮箱:xh52029294f@sohu.com

[java]  view plain copy
  1. <pre></pre>  
  2. <pre></pre>  
  3. <pre></pre>  
  4. <pre></pre>  
  5. <pre></pre>  
  6. <pre></pre>  
  7. <pre></pre>  
  8. <pre></pre>  
  9. <pre></pre>  
  10. <pre></pre>  
  11. <pre></pre>  
  12. <pre></pre>  
  13. <pre></pre>  
  14. <pre></pre>  
  15. <pre></pre>  
  16. <pre></pre>  
  17. <pre></pre>  
  18. <pre></pre>  
  19. <pre></pre>  
  20. <pre></pre>  
  21. <pre></pre>  
  22. <pre></pre>  
  23. <pre></pre>  
  24. <pre></pre>  
  25. <pre></pre>  
  26. <pre></pre>  
  27. <pre></pre>  
  28. <pre></pre>  
  29. <pre></pre>  
  30. <pre></pre>  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值