2017-04-04 32 views
4

我在Android 7.1.1(N_MR1)版QA的Google Pixel上遇到了一個非常奇怪的問題。 在建立TCP連接時,我們使用UDP服務器和客戶端進行握手。Android 7.1.1上的DatagramSocket出現問題

QA報告與Pixel的握手不起作用。探索logcat的後,我發現,UdpServerTask拋出一個異常:

java.net.BindException: Address already in use 
at java.net.PlainDatagramSocketImpl.bind0(Native Method) 
at java.net.AbstractPlainDatagramSocketImpl.bind(AbstractPlainDatagramSocketImpl.java:96) 
at java.net.DatagramSocket.bind(DatagramSocket.java:387) 

我試了一下,到目前爲止:

  • 啓用Reuse address功能(見代碼) - 沒有運氣
  • 被迫的IPv4的使用(見代碼) - 同樣,沒有運氣
  • 在循環中,檢查端口範圍(32100 - 32110) - 也沒有幫助。另外所有端口拋出同樣的異常java.net.BindException: Address already in use
  • 硬編碼IP的「0.0.0.0」和「10.1.xx」(見代碼) - 同
  • 重新啓動設備,改變了WiFi網絡 - 沒有幫助以及

此外,我檢查了誰使用設備上的端口(NetStat +應用) - IP和端口是免費的,沒有人使用。但是,當我試圖撥打bind() - 發生異常。

同時UDP客戶端(按需呼叫)工作正常 - 我可以通過目標端口發送UDP數據包。

還有什麼注意到 - 在我的Nexus與Android 7.1.1和低Android設備的設備我不能重現這個問題。

測試例如

public class UDPServer { 

    int PORT =0; 
    long TIMEOUT = 30000; 

    private void log(String msg) { 
     System.out.println(msg); 
    } 

    private boolean isActive = false; 
    public ArrayList<UdpServerTask> tasks = new ArrayList<>(); 

    public void process(final byte[] data) { 
     AsyncTask<Void, Void, Void> loadTask = new AsyncTask<Void, Void, Void>() { 

      @Override 
      protected Void doInBackground(Void... params) { 
       //process data 
       return null; 
      } 

     }; 

     Utils.executeTask(loadTask); 
    } 

    public void startAddress(String host) { 
     UdpServerTask loadTask = new UdpServerTask(host, PORT); 
     tasks.add(loadTask); 
     Utils.executeTask(loadTask); 
    } 


    public void runUdpServer() { 
     java.lang.System.setProperty("java.net.preferIPv6Addresses", "false"); 
     java.lang.System.setProperty("java.net.preferIPv4Stack", "true"); 
     stop_UDP_Server(); 
     isActive = true; 
     AsyncTask<Void, Void, Void> mainTask = new AsyncTask<Void, Void, Void>() { 
      ArrayList<String> ips = new ArrayList<>(); 

      @Override 
      protected Void doInBackground(Void... params) { 
       log("UDP starting servers "); 
       ips.add(null); 
       ips.add("0.0.0.0"); 
       try { 
        Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces(); 
        while (interfaces.hasMoreElements()) { 
         NetworkInterface networkInterface = interfaces.nextElement(); 

         if (networkInterface.isLoopback() || !networkInterface.isUp()) { 
          continue; 
         } 
         for (InterfaceAddress interfaceAddress : networkInterface.getInterfaceAddresses()) { 
          InetAddress broadcast = interfaceAddress 
            .getBroadcast(); 
          if (broadcast == null || broadcast instanceof Inet6Address) { 
           continue; 
          } 

          if (!ips.contains(broadcast.getHostAddress())) { 
           ips.add(broadcast.getHostAddress()); 
          } 
         } 

        } 
       } catch (final Throwable e) { 
        e.printStackTrace(); 

       } 
       return null; 
      } 

      @Override 
      protected void onPostExecute(Void result) { 
       for (String host : ips) { 
        startAddress(host); 
       } 

      } 

     }; 

     Utils.executeTask(mainTask); 

    } 

    public boolean reallyStopped() { 
     return !isActive && tasks.isEmpty(); 
    } 

    public void stop_UDP_Server() { 
     isActive = false; 

     AsyncTask<Void, Void, Void> mainTask = new AsyncTask<Void, Void, Void>() { 
      @Override 
      protected Void doInBackground(Void... params) { 
       log("UDP start stopping"); 

       for (UdpServerTask task : tasks) { 
        task.cancelServer(); 
       } 

       tasks.clear(); 
       return null; 
      } 

     }; 

     Utils.executeTask(mainTask); 

     while (!reallyStopped()) { 
      try { 
       Thread.sleep(100); 
      } catch (Exception e) { 
      } 
     } 

    } 


    private class UdpServerTask extends AsyncTask<Void, Void, Void> { 
     String ip; 
     int port; 

     public UdpServerTask(String ip, int port) { 
      this.ip = ip; 
      this.port = port; 
     } 

     DatagramSocket ds = null; 

     public void cancelServer() { 
      log("UDP server cancelServer"); 
      if (ds != null && !ds.isClosed()) { 
       try { 
        ds.close(); 
        ds = null; 
       } catch (Exception e) { 
        e.printStackTrace(); 
       } 
      } 
      log("UDP server stopped"); 
     } 

     @Override 
     protected Void doInBackground(Void... params) { 

      long time = System.currentTimeMillis(); 
      boolean firstAttempt = true; 
      while (System.currentTimeMillis() - time <= TIMEOUT && isActive) { 
       try { 

        if (ds != null && !ds.isClosed()) { 
         try { 
          ds.close(); 
          ds = null; 
         } catch (Exception e) { 
          e.printStackTrace(); 
         } 
        } 

        log("UDP try create connection " + this.ip + ":" + this.port); 

        if (firstAttempt) { 
         ds = new DatagramSocket(new InetSocketAddress(TextUtils.isEmpty(this.ip) ? null : InetAddress.getByName(this.ip), this.port)); 
        } else { 
         ds = new DatagramSocket(null); 
        } 

        ds.setBroadcast(true); 

        if (!firstAttempt) { 
         ds.setReuseAddress(true); 
         ds.bind(new InetSocketAddress(TextUtils.isEmpty(this.ip) ? null : InetAddress.getByName(this.ip), this.port)); 
        } 

        long start = System.currentTimeMillis(); 

        while (!ds.isBound()) { 
         if (System.currentTimeMillis() - start >= TIMEOUT) { 
          throw new Exception("Cann't bind to " + this.ip + ":" + this.port); 
         } 
         Thread.sleep(150); 
        } 

        log("UDP Server Started on " + this.ip + ":" + this.port); 
        while (isActive) { 
         final byte[] lMsg = new byte[4096]; 
         final DatagramPacket dp = new DatagramPacket(lMsg, lMsg.length); 
         ds.receive(dp); 


         log("process UDP from " + dp.getAddress().toString() + ":" + dp.getPort()); 
         process(dp.getData()); 


        } 
        log("UDP Server Stopped on " + this.ip + ":" + this.port); 


       } catch (final Throwable e) { 
        e.printStackTrace(); 
        firstAttempt = false; 
        log("UDP Server Failed " + this.ip + ":" + this.port + " " + e); 
        try { 
         Thread.sleep(TIMEOUT/10); 
        } catch (Exception ex) { 
        } 

       } 
      } 


      if (ds != null && !ds.isClosed()) 
       try { 
        ds.close(); 
        ds = null; 
       } catch (Exception e) { 
        e.printStackTrace(); 
       } 

      log("UDP Server finish task"); 

      return null; 
     } 

    } 

} 
+0

兩個關於你的代碼更多的東西:1)'isActive'已被宣佈爲'volatile'。 2)考慮在這裏使用'Thread'而不是'AsyncTask',因爲後者重用線程池中的線程並最終阻塞(在服務器關閉期間實際發生在我身上) – Idolon

回答

2

的問題是在你使用的端口。在我的手機像素以下的端口範圍在/proc/sys/net/ipv4/ip_local_reserved_ports文件中定義:

32100-32600,40100-40150

如果我在你的代碼更改端口號,以任何超出此範圍(及以上的1024,當然),它的工作原理很好,我可以從其他主機發送數據到應用程序。

Linux內核documentation介紹這個文件是這樣的:

ip_local_reserved_ports - 逗號的列表分離的範圍

指定哪些被保留用於已知的第三方應用程序的端口。這些 端口不會被自動端口分配使用(例如,當 調用端口號爲0的connect()bind()時)。 顯式端口 分配行爲不變

因此,當您明確將端口號傳遞給bind方法時,它應該仍然可能使用這些端口。顯然這不起作用。在我看來,Android中使用的Linux內核實現提供的網絡堆棧中存在一處錯誤。但是這需要額外的調查。

您也可以找到有用的ip_local_reserved_ports內容在不同的手機下面的列表: ​​

相關問題