今天在运行一个demo的时候,遇到了一些问题,debug后发现与http的长连接有关。
故改造了下代码,尝试捕捉”长连接“。
demo 来自于 《How Tomcat Works》
原代码
Request
public class Request {
private InputStream in;
private String uri;
public Request(InputStream inputStream) {
this.in = inputStream;
}
public void parse() {
StringBuffer request = new StringBuffer(2048);
int i;
byte[]buffer = new byte[4096];
try {
in.read(buffer);
} catch (IOException e) {
e.printStackTrace();
}
String s = new String(buffer, StandardCharsets.UTF_8);
System.out.println(s);
uri = parseUri(s);
}
public String parseUri(String requestString) {
String[] s = requestString.split(" ");
if(s.length > 2) {
return s[1];
}
return null;
}
public String getUri(){
return uri;
}
}
Response
public class Response {
private static final int BUFFER_SIZE = 10240;
private Request request;
private final OutputStream outputStream;
public Response(OutputStream outputStream) {
this.outputStream = outputStream;
}
public void setRequest(Request request) {
this.request = request;
}
public Request getRequest() {
return request;
}
public void sendStaticResource() {
byte[]bytes = new byte[BUFFER_SIZE];
FileInputStream fis = null;
try {
File file = new File(HttpServer.WEB_ROOT, request.getUri());
if(file.exists()) {
fis = new FileInputStream(file);
int ch = fis.read(bytes, 0, BUFFER_SIZE);
String html = new String(bytes, 0, ch);
String res = "";
res += "HTTP/1.1 200 OK" + "\r\n";
res += "Content-Type:text/html;charset=utf-8" + "\r\n";
res += "Content-Length:" + html.length() + "\r\n";
res += "\r\n";
res += html;
outputStream.write(res.getBytes(StandardCharsets.UTF_8));
outputStream.flush();
}else {
String errorMessage = "HTTP/1.1 404 File Not Found\r\n" +
"Content-Type: text/html\r\n"+
"Content-Length: 23\r\n"+
"\r\n"+
"<h1>File Not Found<h1>\r\n";
outputStream.write(errorMessage.getBytes());
}
}catch (Exception e) {
e.printStackTrace();
}finally {
if(fis != null) {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
HttpServer
public class HttpServer {
public static final String WEB_ROOT = System.getProperty("user.dir") + File.separator + "webroot";
private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";
private boolean shutdown = false;
public static void main(String[] args) throws IOException {
HttpServer httpServer = new HttpServer();
httpServer.await();
}
public void await() {
ServerSocket serverSocket = null;
int port = 8080;
try {
serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.01"));
}catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
while (!shutdown) {
Socket socket = null;
InputStream input = null;
OutputStream output = null;
try {
socket = serverSocket.accept();
input = socket.getInputStream();
output = socket.getOutputStream();
Request request = new Request(input);
request.parse();
Response response = new Response(output);
response.setRequest(request);
response.sendStaticResource();
socket.close();
shutdown = request.getUri().equals(SHUTDOWN_COMMAND);
}catch (Exception e) {
e.printStackTrace();
}
}
}
}
静态资源放在这个文件夹里:
问题分析
现象1:
一直用一个浏览器访问没有任何问题,但是一旦中途切换浏览器就会报错。
现象2:
理论上,每次处理完请求后,应该阻塞在accept这里
但实际上,每次一个请求处理完,都会阻塞在parse方法处, 准确点说是parse中的read方法处。
这就很奇怪了,我明明关闭了socket,理论上这个连接断开后,只要我不重新请求网页,就不会有新的网页了。
初步预计这个新的socket依然是和浏览器的连接。
所以我直接关闭了浏览器,发现不在阻塞了,连接也断开了。
说明,每次处理完请求后,服务器关闭了socket后,浏览器又主动的发起了连接(即使网页没有刷新)。
代码改造
上述只能说明,服务器关闭socket后,浏览器又主动发起了一个连接。
怎么能证明这个连接是长连接呢?
那就需要改下代码了,不accept放到循环外,且服务器端不主动关闭 socket。
public class HttpServer {
public static final String WEB_ROOT = System.getProperty("user.dir") + File.separator + "webroot";
private static final String SHUTDOWN_COMMAND = "/SHUTDOWN";
private boolean shutdown = false;
public static void main(String[] args) throws IOException {
HttpServer httpServer = new HttpServer();
httpServer.await();
}
public void await() {
ServerSocket serverSocket = null;
int port = 8080;
try {
serverSocket = new ServerSocket(port, 1, InetAddress.getByName("127.0.01"));
}catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
Socket socket = null;
InputStream input = null;
OutputStream output = null;
try{
socket = serverSocket.accept();
input = socket.getInputStream();
output = socket.getOutputStream();
}catch (Exception e) {
e.printStackTrace();
}
while (!shutdown) {
try {
Request request = new Request(input);
request.parse();
Response response = new Response(output);
response.setRequest(request);
response.sendStaticResource();
shutdown = request.getUri().equals(SHUTDOWN_COMMAND);
}catch (Exception e) {
e.printStackTrace();
}
}
}
}
启动后可以发现,即使处理完请求,连接也不会断,不断地在read()函数处阻塞。
这算是成功捕捉到了所谓的”http长连接“。
长连接的时间由服务器端决定,服务器计算连接时长,到期后断开连接。
浏览器默认会一直保持连接,除非关闭整个浏览器。即使我再后端添加了响应头,keep-alive timeout,到期后浏览器也不会主动断开连接。
如有问题,欢迎指正