出于测试/基准测试的目的,我想编写一个Java程序,它在循环中执行以下任务:
此循环同时在多个线程上运行。
启动后,程序可以在短时间内正常运行,并且每秒每个线程可以执行约300个循环(Web服务器在同一台机器上运行)。但是5-7秒后,我得到了BindException: Address already in use
。
在20-30秒的冷却时间后重新启动程序会导致相同的行为;当我立即重启它而不等待时,它会立即崩溃......所以我认为这可能是绑定资源的问题。
使用HttpURLConnection
这是一种快速而肮脏的方法。相关部分:
从网络服务器获取数据
public String fetchData() throws IOException {
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setUseCaches(false);
conn.setRequestMethod("GET");
BufferedReader in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String inputLine;
StringBuffer response = new StringBuffer();
while ((inputLine = in.readLine()) != null) {
response.append(inputLine);
}
in.close();
conn.disconnect();
return response.toString();
}
发送答案
public void sendData(byte[] data) throws IOException {
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setUseCaches(false);
conn.setRequestMethod("POST");
conn.setDoOutput(true);
OutputStream os = conn.getOutputStream();
os.write(data);
os.close();
conn.disconnect();
}
在线程中调用两个方法
@Override
public void run() {
while(true) {
try {
String data = fetchData();
String answer = // ... generating answer
sendData(answer.getBytes("UTF-8"));
} catch (IOException e) {
// ...
}
}
}
没有一个URL对象在线程之间共享 - 每个线程都有自己的URL实例(但是,每个实例都指向同一个地址)。
修改
这是发生的第一个异常的堆栈跟踪:
java.net.BindException: Address already in use: connect
at java.net.DualStackPlainSocketImpl.connect0(Native Method)
at java.net.DualStackPlainSocketImpl.socketConnect(Unknown Source)
at java.net.AbstractPlainSocketImpl.doConnect(Unknown Source)
at java.net.AbstractPlainSocketImpl.connectToAddress(Unknown Source)
at java.net.AbstractPlainSocketImpl.connect(Unknown Source)
at java.net.PlainSocketImpl.connect(Unknown Source)
at java.net.SocksSocketImpl.connect(Unknown Source)
at java.net.Socket.connect(Unknown Source)
at java.net.Socket.connect(Unknown Source)
at sun.net.NetworkClient.doConnect(Unknown Source)
at sun.net.www.http.HttpClient.openServer(Unknown Source)
at sun.net.www.http.HttpClient.openServer(Unknown Source)
at sun.net.www.http.HttpClient.<init>(Unknown Source)
at sun.net.www.http.HttpClient.New(Unknown Source)
at sun.net.www.http.HttpClient.New(Unknown Source)
at sun.net.www.protocol.http.HttpURLConnection.getNewHttpClient(Unknown Source)
at sun.net.www.protocol.http.HttpURLConnection.plainConnect0(Unknown Source)
at sun.net.www.protocol.http.HttpURLConnection.plainConnect(Unknown Source)
at sun.net.www.protocol.http.HttpURLConnection.connect(Unknown Source)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream0(Unknown Source)
at sun.net.www.protocol.http.HttpURLConnection.getInputStream(Unknown Source)
at PayloadProcessor.fetchData(PayloadProcessor.java:66)
at PayloadProcessor.run(PayloadProcessor.java:32)
at java.lang.Thread.run(Unknown Source)
它出现在fetchdata方法中的以下行:
in = new BufferedReader(new InputStreamReader(conn.getInputStream()));
我删除了对disconnect()
的调用(正如Aaron所指出的) - 不幸的是,同样的问题仍然存在。
让我们假设一切都正确关闭(不确定这里是否是这种情况,只是假设它) - 可能是程序太快了吗?我在stackoverflow上发现了另一个post,“解决方案”是添加一个简单的Thread.sleep - 但由于它是一个基准测试并且应该尽可能快地运行,我不喜欢这样。负载测试工具如何处理这个问题?
我将线程数量减少到1,即使这样也会出现问题。
答案 0 :(得分:2)
听起来你可能已经没有本地端口了。是否有可能并行生成太多连接并且没有足够快地释放其资源以供其他线程重用?
我会删除对disconnect()
的调用,因为这会为你几乎禁用连接池。来自文档:
表示在不久的将来不太可能向服务器发出其他请求。调用disconnect()不应该暗示这个HttpURLConnection实例可以重用于其他请求。
答案 1 :(得分:1)
我认为你的本地端口用完了以查看: 运行程序并运行netstat命令。 尝试使用连接池进行http连接 HTTP connection pooling using HttpClient
答案 2 :(得分:0)
看起来你的本地端口已经用完了。为防止这种情况,您必须确保重用连接。 Java将尝试do this for you,但您需要为每个请求读取整个响应。
虽然fetchData
函数确实读取了回复,但sendData
没有。虽然你在这里不需要响应主体,但你仍然应该阅读它(直到最后),然后关闭相应的InputStream
。然后可以重用此连接。
也不要disconnect
你的关系。
This question可能会有所帮助。
答案 3 :(得分:0)
要获取和发送数据,您需要为每HttpURLConnection
和fetchData
次呼叫创建一个sendData
。这意味着,您与服务器的连接太多。正如已经指出的那样,这将使所有可用的本地端口号都用完。
每个帖子只需创建一个HttpURLConnection
。我强烈建议您按如下方式重构给定的代码:
@Override
public void run() {
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
while(true) {
try {
String data = fetchData(conn);
String answer = // ... generating answer
sendData(conn, answer.getBytes("UTF-8"));
} catch (IOException e) {
// ...
}
}
}
也就是说,在while循环之外创建HttpURLConnection
对象,并将其作为参数传递给fetchData
和sendData
。
答案 4 :(得分:0)
简单的解决方案只是在回答之前引入延迟。
String data = fetchData(conn);
String answer = // ... generating answer
Thread.sleep(1000);
sendData(conn, answer.getBytes("UTF-8"));