【发布时间】:2021-01-22 16:35:06
【问题描述】:
作为并发博客系列的一部分,我正在用不同的语言(Java、Kotlin、Rust、Go、JS、TS)构建最简单的 HTTP 服务器,并且除了 Java/Kotlin(也就是在 JVM 上)之外的所有东西都可以正常工作。所有代码都可以找到here。下面是 Java 中的服务器代码,我尝试了一个基于传统线程的代码和一个基于 AsynchronousServerSocketChannel 的代码,但是无论我使用 ApacheBench 运行基准测试时,它都以 Broken pipe 和 apr_socket_recv: Connection reset by peer (104) 失败,这很奇怪,因为类似的设置其他语言工作正常。这里的问题只发生在 ApacheBench 上,因为当我在浏览器中访问 URL 时它工作正常。所以我正在努力弄清楚发生了什么。我试图玩keep-alive等,但似乎没有帮助。我看了一堆类似的例子,我没有看到任何地方有什么特别之处。我希望有人能弄清楚这里出了什么问题,因为这肯定与 JVM + APacheBench 有关。我已经用 Java 11 和 15 尝试过,但结果相同。
Java 线程示例(hello.html 可以是任何 HTML 文件)
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class JavaHTTPServerCopy {
public static void main(String[] args) {
int port = 8080;
try (ServerSocket serverSocket = new ServerSocket(port)) {
System.out.println("Server is listening on port " + port);
while (true) {
new ServerThreadCopy(serverSocket.accept()).start();
}
} catch (IOException ex) {
System.out.println("Server exception: " + ex.getMessage());
}
}
}
class ServerThreadCopy extends Thread {
private final Socket socket;
public ServerThreadCopy(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
var file = new File("hello.html");
try (
// we get character output stream to client (for headers)
var out = new PrintWriter(socket.getOutputStream());
// get binary output stream to client (for requested data)
var dataOut = new BufferedOutputStream(socket.getOutputStream());
var fileIn = new FileInputStream(file)
) {
var fileLength = (int) file.length();
var fileData = new byte[fileLength];
int read = fileIn.read(fileData);
System.out.println("Responding with Content-length: " + read);
var contentMimeType = "text/html";
// send HTTP Headers
out.println("HTTP/1.1 200 OK");
out.println("Connection: keep-alive");
out.println("Content-type: " + contentMimeType);
out.println("Content-length: " + fileLength);
out.println(); // blank line between headers and content, very important !
out.flush(); // flush character output stream buffer
dataOut.write(fileData, 0, fileLength);
dataOut.flush();
} catch (Exception ex) {
System.err.println("Error with exception : " + ex);
} finally {
try {
socket.close(); // we close socket connection
} catch (Exception e) {
System.err.println("Error closing stream : " + e.getMessage());
}
}
}
}
控制台出错
Responding with Content-length: 176
Error with exception : java.net.SocketException: Broken pipe (Write failed)
Error with exception : java.net.SocketException: Broken pipe (Write failed)
Error with exception : java.net.SocketException: Broken pipe (Write failed)
Error with exception : java.net.SocketException: Broken pipe (Write failed)
Error with exception : java.net.SocketException: Broken pipe (Write failed)
ApacheBench 输出
ab -c 100 -n 1000 http://localhost:8080/
This is ApacheBench, Version 2.3 <$Revision: 1879490 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking localhost (be patient)
apr_socket_recv: Connection reset by peer (104)
Java 异步示例
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.StandardSocketOptions;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
public class JavaAsyncHTTPServer {
public static void main(String[] args) throws Exception {
new JavaAsyncHTTPServer().go();
Thread.currentThread().join();//Wait forever
}
private void go() throws IOException {
AsynchronousServerSocketChannel server = AsynchronousServerSocketChannel.open();
InetSocketAddress hostAddress = new InetSocketAddress("localhost", 8080);
server.bind(hostAddress);
server.setOption(StandardSocketOptions.SO_REUSEADDR, true);
System.out.println("Server channel bound to port: " + hostAddress.getPort());
if (server.isOpen()) {
server.accept(null, new CompletionHandler<>() {
@Override
public void completed(final AsynchronousSocketChannel result, final Object attachment) {
if (server.isOpen()) {
server.accept(null, this);
}
handleAcceptConnection(result);
}
@Override
public void failed(final Throwable exc, final Object attachment) {
if (server.isOpen()) {
server.accept(null, this);
System.out.println("Connection handler error: " + exc);
}
}
});
}
}
private void handleAcceptConnection(final AsynchronousSocketChannel ch) {
var content = "Hello Java!";
var message = ("HTTP/1.0 200 OK\n" +
"Connection: keep-alive\n" +
"Content-length: " + content.length() + "\n" +
"Content-Type: text/html; charset=utf-8\r\n\r\n" +
content).getBytes();
var buffer = ByteBuffer.wrap(message);
ch.write(buffer);
try {
ch.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
控制台没有错误
ApacheBench 输出
❯ ab -c 100 -n 1000 http://localhost:8080/
This is ApacheBench, Version 2.3 <$Revision: 1879490 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking localhost (be patient)
apr_socket_recv: Connection reset by peer (104)
保持活动状态的 ApacheBench 输出
ab -k -c 100 -n 1000 http://localhost:8080/
This is ApacheBench, Version 2.3 <$Revision: 1879490 $>
Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/
Licensed to The Apache Software Foundation, http://www.apache.org/
Benchmarking localhost (be patient)
Send request failed!
Send request failed!
Send request failed!
Send request failed!
Send request failed!
Send request failed!
Send request failed!
Send request failed!
Send request failed!
Send request failed!
Send request failed!
Send request failed!
Send request failed!
Send request failed!
Send request failed!
Send request failed!
Send request failed!
Send request failed!
apr_socket_recv: Connection reset by peer (104)
Total of 37 requests completed
【问题讨论】:
-
在 Twitter 上,有人建议在
ServerSocket上使用 100 的积压,所以我尝试了new ServerSocket(port, 100)并按照建议将-r标志添加到ab命令,结果更好但仍然超时。见以下结果Completed 100 requests apr_pollset_poll: The timeout specified has expired (70007) Total of 101 requests completed -
我从实验中学到的更多东西。似乎如果我将所有请求作为并发发送,它们就会成功。例如
ab -r -c 100 -n 100 http://127.0.0.1:8080/成功,ab -r -c 1000 -n 1000 http://127.0.0.1:8080/在超时前至少发送了大约 700 个请求。所以这似乎是类似于serverfault.com/questions/146605/… 的问题
标签: java kotlin jvm webserver apachebench