我在QA的Google Pixel和Android 7.1.1(N_MR1)上遇到了一个非常奇怪的问题。 我们在建立TCP连接时使用UDP服务器和客户端进行握手。
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
功能(参见代码) - 没有运气java.net.BindException: Address already in use
另外,我检查了谁在设备上使用端口(NetStat + app) - IP和端口是免费的,没有人使用过。但是当我试图调用bind()
时 - 异常发生。
同时UDP客户端(按需调用)工作正常 - 我可以通过目标端口发送UDP数据包。
另外注意到 - 在我的带有Android 7.1.1的Nexus和Android版本较低的设备上,我无法重现这个问题。
测试示例
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;
}
}
}
答案 0 :(得分:2)
问题出在你使用的端口上。在我的Pixel手机上,/proc/sys/net/ipv4/ip_local_reserved_ports
文件中定义了以下端口范围:
32100-32600,40100-40150
如果我将代码中的端口号更改为超出此范围的任何内容(当然,大于1024),它可以正常工作,并且我可以从其他主机向应用程序发送数据。
Linux Kernel documentation描述了这样的文件:
ip_local_reserved_ports
- 逗号分隔范围列表指定为已知第三方应用程序保留的端口。这些 自动端口分配不会使用端口(例如,何时 使用端口号0调用
connect()
或bind()
。 显式端口 分配行为未改变。
因此,当您明确地将端口号传递给bind
方法时,仍应该使用这些端口。显然这不起作用。在我看来,Android中使用的Linux内核实现提供的网络堆栈中存在一个错误。但这需要进一步调查。
您可能还会在不同手机上找到以下ip_local_reserved_ports
内容列表:
https://census.tsyrklevich.net/sysctls/net.ipv4.ip_local_reserved_ports