问题
I'm faced with a very strange issue on QA's Google Pixel with Android 7.1.1 (N_MR1). We use UDP Server and Client for handshake during establish the TCP connection.
QA reports that handshake to Pixel doesn't work. After exploring Logcat I found that UdpServerTask
throws an exception:
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)
What I tried so far:
- enabled
Reuse address
feature (see code) - no luck - forced usage of IPv4 (see code) - same, no luck
- in the loop, checked ports range (32100 - 32110) - also doesn't help. Also all ports throw the same exception
java.net.BindException: Address already in use
- hardcoded IP's "0.0.0.0" and "10.1.x.x" (see code) - the same
- restarted device, changed WiFi network - didn't help as well
Also, I checked who uses ports on the device (NetStat+ app) - IP's and ports are free, no one used. But when I tried to call bind()
- exception happens.
At the same time UDP client (called on demand) works fine - I can send UDP packets via target port.
Also what noticed - on my Nexus with Android 7.1.1 and devices with lower Android version I can't reproduce the issue.
Test example
public class UDPServer {
int PORT = 32100;
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;
}
}
}
回答1:
The issue is in the port you use. On my Pixel phone the following port ranges are defined in the /proc/sys/net/ipv4/ip_local_reserved_ports
file:
32100-32600,40100-40150
If I change the port number in your code to anything out of this range (and above 1024, of course), it works fine and I'm able to send data to the app from the other host.
Linux Kernel documentation describes this file like this:
ip_local_reserved_ports
- list of comma separated rangesSpecify the ports which are reserved for known third-party applications. These ports will not be used by automatic port assignments (e.g. when calling
connect()
orbind()
with port number 0). Explicit port allocation behavior is unchanged.
So, when you explicitly pass the port number to the bind
method it should still be possibly to use those ports. Apparently this doesn't work. In my opinion, there is a bug somewhere in the network-stack provided by the Linux Kernel implementation used in Android. But this requires additional investigation.
You may also find useful the following list of ip_local_reserved_ports
contents on different phones:
https://census.tsyrklevich.net/sysctls/net.ipv4.ip_local_reserved_ports
来源:https://stackoverflow.com/questions/43209304/issue-with-datagramsocket-on-android-7-1-1