最近在写安卓UDP通信程序时遇到这个问题,就是在安卓模拟器上可以实现收发,但是放到真机上就只能发收不到PC机发来的数据。在网上查了一些资料,发现有很多朋友也遇到这个问题,都没有非常明确的解决方法,我经过尝试终于在真机上收到了PC机发的数据,跟大家分享一下。我用的是eclipse开发环境,安卓虚拟机为2.2版本。为方便讲解,先贴几张图:
图一 安卓模拟器界面
图二 PC机服务程序界面
图一为安卓虚拟机上的界面,在真机上除了文字位置有一些错位外其它都一致(偷个懒就不贴真机效果的照片了),远程IP地址是你的PC端服务程序所在PC的公网IP;远程端口是PC服务程序接收端口;本地端口是手机发送数据时用的端口。图二为测试时用的一个PC端服务程序。PC机上的服务程序必须要能解析出包中的IP和端口号,建议用我提供的PC端服务器,不保证使用其它PC端服务器能实现收发。上图二中的服务程序是在模拟机测试时用的,在真机测试时用不了(好像是我的电脑在内网,它只能读出内网地址的原因)。
如果在安卓模拟器上收不到数据,要把PC机端口与模拟器连接一下,具体方法参考《android模拟器接收不到UDP数据包》。还要注意,用真机发送数据时远程IP要写成服务器所在PC机的公网IP,手机上远程端口与PC机服务器上接收端口一致,手机上本地端口任意设。
对于遇到这个问题的朋友,前面所介绍的很多是多余的,下面进入主题,结合源码应该能讲的清楚些,先贴出来:
- <span style="FONT-SIZE: 16px">package udp.net;
- import java.io.IOException;
- import java.net.DatagramPacket;
- import java.net.DatagramSocket;
- import java.net.InetAddress;
- import android.app.Activity;
- import android.app.AlertDialog;
- import android.content.DialogInterface;
- import android.os.Bundle;
- import android.os.Handler;
- import android.os.Message;
- import android.view.KeyEvent;
- import android.view.View;
- import android.view.View.OnClickListener;
- import android.widget.Button;
- import android.widget.TextView;
- public class UDPActivity extends Activity {
- /** Called when the activity is first created. */
- //收发用同一个socket ,只在连接时实例化一次
- private DatagramSocket socketUDP=null;
- private Thread thread=null;
- private TextView textSend;
- private TextView textReceive;
- private TextView portRemote;
- private TextView portLocal;
- private TextView textIP;
- private Button btnSend;
- private Button btnConnect;
- private Button btnDisConnect;
- private int portRemoteNum;
- private int portLocalNum;
- private String addressIP;
- private String revData;
- private boolean flag=false;
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.main);
- //与UI关联
- textSend = (TextView)findViewById(R.id.textsend);
- textReceive = (TextView)findViewById(R.id.textrcecive);
- textIP = (TextView)findViewById(R.id.textip);
- portRemote = (TextView)findViewById(R.id.textPortRemote);
- portLocal = (TextView)findViewById(R.id.textPortLocal);
- btnSend = (Button) findViewById(R.id.btnsend);
- btnSend.setEnabled(false);
- btnConnect = (Button) findViewById(R.id.btnconnect);
- btnDisConnect = (Button) findViewById(R.id.btndisconnect);
- btnDisConnect.setEnabled(false);
- buttonAction();
- }
- //设置按键监听及动作
- public void buttonAction(){
- btnSend.setOnClickListener(new OnClickListener() {
- public void onClick(View v) {
- // TODO Auto-generated method stub
- sendData();
- }
- });
- btnConnect.setOnClickListener(new OnClickListener() {
- public void onClick(View v) {
- // TODO Auto-generated method stub
- try{
- flag=true;
- portRemoteNum=Integer.parseInt(portRemote.getText().toString());
- portLocalNum=Integer.parseInt(portLocal.getText().toString());
- addressIP = textIP.getText().toString();
- //用设定的本地端口发数据,括号内也可以为空
- socketUDP = new DatagramSocket(portLocalNum);
- // 启动接收线程,用本地端口接收,因为本地端口和PC解析出来端口的对应
- thread = new Thread(revMsg);
- thread.start();
- btnConnect.setEnabled(false);
- btnDisConnect.setEnabled(true);
- btnSend.setEnabled(true);
- textReceive.setText("已连接");
- } catch (Exception e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- });
- btnDisConnect.setOnClickListener(new OnClickListener() {
- public void onClick(View v) {
- // TODO Auto-generated method stub
- flag=false;//结束线程
- socketUDP.close();
- btnConnect.setEnabled(true);
- btnDisConnect.setEnabled(false);
- btnSend.setEnabled(false);
- }
- });
- }
- void sendData()
- {
- try {
- InetAddress serverAddress = InetAddress.getByName(addressIP);
- String str = textSend.getText().toString();
- byte data [] = str.getBytes();
- DatagramPacket packetS = new DatagramPacket(data,
- data.length,serverAddress,portRemoteNum);
- //从本地端口给指定IP的远程端口发数据包
- socketUDP.send(packetS);
- } catch (Exception e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- private Handler handler = new Handler()
- {
- @Override
- public void handleMessage(Message msg)
- {
- if (textReceive != null)
- {
- if (revData==null) return;
- StringBuilder sb = new StringBuilder();
- sb.append(textReceive.getText().toString().trim());
- sb.append("\n");
- sb.append(revData);
- textReceive.setText(sb.toString().trim());
- sb.delete(0, sb.length());
- sb = null;
- }
- }
- };
- private Runnable revMsg = new Runnable() {
- @Override
- public void run()
- {
- while (flag)
- {
- byte data[] = new byte[1024];
- DatagramPacket packetR = new DatagramPacket(data, data.length);
- try {
- socketUDP.receive(packetR);
- revData = new String(packetR.getData(),
- packetR.getOffset(),packetR.getLength());
- handler.sendEmptyMessage(0);
- } catch (IOException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- }
- }
- };
- //退出提示
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
- AlertDialog alertDialog = new AlertDialog.Builder(
- UDPActivity.this).setTitle("退出程序").setMessage("是否退出程序")
- .setPositiveButton("确定",
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog,
- int which) {
- android.os.Process.killProcess(
- android.os.Process.myPid());
- }
- })
- .setNegativeButton("取消",
- new DialogInterface.OnClickListener() {
- public void onClick(DialogInterface dialog, int which) {
- return;
- }
- }).create(); // 创建对话框
- alertDialog.show(); // 显示对话框
- return false;
- }
- return false;
- }
- }</span><span style="FONT-SIZE: 16px">
- </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
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>
- <pre></pre>