UDP 打孔:PC 到 Android 的 UDP 连接不适用于非本地地址

Posted

技术标签:

【中文标题】UDP 打孔:PC 到 Android 的 UDP 连接不适用于非本地地址【英文标题】:UDP holepunching: PC-to-Android UDP connection does not work with nonlocal addresses 【发布时间】:2014-09-21 05:58:01 【问题描述】:

我在 android 应用程序和 Python 服务器之间编写了一个简单的 UDP 传输。我知道系统正在运行,因为当我尝试连接本地 IP 地址 (192.168.X.X) 时,会发送和接收正确的消息。但是,当我尝试使用公共 IP 地址时,这不起作用。有谁知道为什么以及如何尝试解决此问题?

我正在尝试实现 UDP 打孔,让服务器充当 Android 客户端的目标客户端,但我无法将第二个客户端的 UDP 数据包发送到 Android 客户端,它永远不会被 Android 端接收。让第二台机器作为第二台客户端来解决这个问题,还是我的代码不完整?

我的提供商 (T-Mobile) 对 UDP 数据包通信是否重要?

客户端(安卓):

public class CustomizeGatewayActivity extends ActionBarActivity 

    AsyncUDPReceiver aReceive = null;
    static TextView recieve = null;

    public static class PlaceholderFragment extends Fragment 
        EditText addressText, portText, messageText;
        Button udpsend, tcpsend;
        Socket socket = null;

        public PlaceholderFragment() 
        

        @Override
        public View onCreateView(LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState) 
            View rootView = inflater.inflate(
                    R.layout.fragment_customize_gateway, container, false);

            recieve = (TextView) rootView.findViewById(R.id.textView1);
            addressText = (EditText) rootView.findViewById(R.id.editText1);
            messageText = (EditText) rootView.findViewById(R.id.editText3);

            udpsend = (Button) rootView.findViewById(R.id.UDP);
            udpsend.setOnClickListener(new OnClickListener() 

                @Override
                public void onClick(View v) 
                    // TODO Auto-generated method stub
                    AsyncUDPSend aSend = new AsyncUDPSend(addressText.getText().toString(), messageText.getText().toString());
                    aSend.execute();
                
            );

        public class AsyncUDPSend extends AsyncTask<Void, Void, Void> 
            String address = "";
            String message = "";
            String response = "";
            AsyncUDPSend(String addr, String mes) 
                address = addr;
                message = mes;
            

            @Override
        protected Void doInBackground(Void... params) 
            // TODO Auto-generated method stub
            DatagramSocket dsocket = null;
            try 
                dsocket = new DatagramSocket();
                dsocket.setSoTimeout(10000);
                InetAddress dest = InetAddress.getByName(address);
                DatagramPacket packet = new DatagramPacket(message.getBytes(), message.length(), dest, 5001);
                dsocket.send(packet);
                System.out.println("Sent");

                byte[] resp = new byte[1024];
                DatagramPacket recv = new DatagramPacket(resp, resp.length);
                System.out.println("Waitng for Response");
                dsocket.receive(recv);
                System.out.println("Received");
                response = new String(recv.getData());
                 catch (IOException e) 
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                    response = "IOException: " + e.toString();
                    System.out.println(response);
                 finally 
                    if (dsocket != null) 
                        dsocket.close();
                    
                
                return null;
            

            @Override
            protected void onPostExecute(Void result) 
                recieve.setText(response);
                super.onPostExecute(result);
            
        

    @Override
    protected void onResume() 
        super.onResume();
        aReceive = new AsyncUDPReceiver();
        aReceive.start();
    

    @Override
    protected void onPause() 
        super.onPause();
        aReceive.kill();
    

    public class AsyncUDPReceiver extends Thread 
        boolean keepRunning = true;
        String response = "";

        Runnable updateText = new Runnable()
            public void run() 
                if(aReceive == null && recieve == null)
                    return;
                recieve.setText(response);
            
        ;

        public void run() 
            android.os.Debug.waitForDebugger();
            System.out.println("running");
            DatagramSocket dsock = null;
            byte[] message = new byte[1024];
            DatagramPacket dpack = new DatagramPacket(message, message.length);
            try 
                dsock = new DatagramSocket(5002);
                System.out.println(dsock.toString());
                while(keepRunning) 
                    dsock.receive(dpack);
                    response = new String(dpack.getData());
                    System.out.println(response);
                    runOnUiThread(updateText);
                
             catch (SocketException e) 
                // TODO Auto-generated catch block
                response = "SocketException: " + e.toString();
                System.out.println(response);
                e.printStackTrace();
             catch (IOException e) 
                // TODO Auto-generated catch block
                response = "IOException: " + e.toString();
                System.out.println(response);
                e.printStackTrace();
             finally 
                if(dsock != null)
                    dsock.close();
            
        

        public void kill() 
            keepRunning = false;
        
    

服务器(Python):

class ThreadedUDPRequestHandler(socketserver.BaseRequestHandler):
    def handle(self):
        data = self.request[0].strip().decode("utf-8")
        print(" Recieved: ".format(self.client_address) + data)
        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        response = data.upper()
        sock.sendto(bytes(response, "utf-8"), self.client_address)
        print(" Sent: ".format(self.client_address,response))

class ThreadedUDPServer(socketserver.ThreadingMixIn, socketserver.UDPServer): 
    pass

if __name__ == "__main__":
    # Port 0 means to select an arbitrary unused port
    HOST, PORT = "", 5000

    udpserver = ThreadedUDPServer((HOST,PORT+1), ThreadedUDPRequestHandler)
    udp_thread = threading.Thread(target=udpserver.serve_forever)
    udp_thread.daemon = True
    udp_thread.start()
    print("UDP serving at port", PORT+1)

    while True:
        pass
    udpserver.shutdown()

【问题讨论】:

查看我对类似问题的回答 [此处][1] [1]:***.com/a/25095781/3199595 我意识到 NAT 引起了我的问题,我想实现 UDP 打孔来克​​服阻塞的数据包,但即使 Android 和服务器相互发送 UDP 数据包,只有Android 可以通过服务器,而服务器永远不会到达 Android 客户端。您是否知道任何解决方案,或者您可以向我推荐一些寻找答案的方向吗? 啊,这让我想到了我写的另一个答案:***.com/a/25096374/3199595 至于 NAT 遍历技术,我将从查看为您工作的现有框架开始。编写打孔或实现像 IGD (en.wikipedia.org/wiki/Internet_Gateway_Device_Protocol) 这样的协议是一件痛苦的事情。我假设 android 有一个现有的解决方案,因为谷歌确实推送通知(从服务器到 android 设备的通知,其中一些可能在 NAT 之后) 【参考方案1】:

您是否将预期值提供给 InetAddress.getByName(address);

另外,由于你试图在后台做一些事情,如果你运行一个带有唤醒锁的服务会更好,这样你就可以消除由于杀死进程而导致的错误。

【讨论】:

向哪个部分提供期望值,发送方还是接收方?发件人有正确的 IP 地址,我的服务器在正确的端口上接收消息。我假设服务器也在正确的端口上发送,因为我编写了一个正常工作的测试客户端。当我将 IP 地址作为 InetAddress 提供给接收方时,我得到 SocketException: java.net.BindException: bind failed: EADDRNOTAVAIL (Cannot assign requested address) for dsock = new DatagramSocket(5002, address);在 AsyncUDPReceiver 的 run() 方法中。 尝试使用不同的端口号。有些端口号是为系统保留的 您说的是哪个系统,Android 还是 PC?我知道我正在使用的端口没有被我的服务器系统使用。一般1024以下的端口是系统保留的,以上的都可以免费使用。 运行此命令并检查端口范围-adb shell cat /proc/sys/net/ipv4/ip_local_port_range。对于我的手机,它在 32768 和 61000 之间。当我使用端口号为 50000 时,它可以工作. 我运行了命令,显示32768到61000。我把回复端口改成了50002,还是不行。

以上是关于UDP 打孔:PC 到 Android 的 UDP 连接不适用于非本地地址的主要内容,如果未能解决你的问题,请参考以下文章

UDP打孔可能吗?

UDP打孔到期[关闭]

UDP打孔只能部分工作c#

UDP 打孔 - 无法到达目的地

UDP打孔:无法从服务器发送到客户端

1个端口上的UDP打孔?