在这一系列的第二篇博客中,我遇到了一个很严重的问题。当我通过浏览器访问图片的时候,图片可能会无法完全展示出来,尽管多次刷新有可能会完整的显示出来,但是通常还是会只是显示一部分的图片。 我一开始认为这是因为网络的原因,但是本地测试,其实网络的原因基本上可以排除了。我把程序挂在服务器上给别人演示,几乎所有人都无法查看完整的图片,我给的说法也就是你多刷新几次就行了。现在想一想,还是太想当然了。
先来看一看这个错误吧!
错误及其解决方法
错误演示
因为第二篇博客的那个文章的代码有些多余了,所以这里提供一个简化的代码来演示。
注意,我注释的代码部分!
package com.dragon.learn;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.Charset;
import java.nio.file.Files;
public class Server {
public static void main(String[] args) throws IOException {
try (ServerSocket server = new ServerSocket(10000)) {
while (true) {
Socket client = server.accept();
System.out.println("用户连接。。。");
//这里只写不读。
OutputStream out = new BufferedOutputStream(client.getOutputStream());
// InputStream in = new BufferedInputStream(client.getInputStream());
// StringBuilder request = new StringBuilder(80);
// while (true) {
// int c = in.read();
// if (c == '\r' || c == '\n' || c == -1) break;
// request.append((char)c);
// }
File file = new File("D:/DragonFile/target/attitude.jpg");
long length = file.length();
byte[] data = Files.readAllBytes(file.toPath());
String header = "HTTP/1.0 200 OK\r\n"
+ "Server: crazy\r\n"
+ "ContentType-length: " + length + "\r\n"
+ "Content-Type: " + "image/jpg" + "; charset=" + "UTF-8" + "\r\n\r\n";
out.write(header.getBytes(Charset.forName("UTF-8"))); //响应头
out.write(data); //响应体
out.flush(); //刷新
client.close();
}
}
}
}
启动服务,使用浏览器访问
浏览器的调试窗口,状态会显示失败,并且下方的数据传输也不对,这种图片大概是1.2M左右。
刷新
再刷新
注意,不是越刷新显示越多,它是无规律的,可能多可能少。如果图片比较小的话,可能会显示完全,我现在使用的这张图片大概是1.2MB。所以只能显示一部分,不过这也好,把问题给完全重现出来了。
一个严重的问题是图片无法下载!
取消注释代码,再次访问
访问成功,各项数据都是对的。重复访问,结果也是相同的,都是可以正确显示的。
注意:这里我没有做异常处理,所以它是可能发生第二篇博客中的那个错误,从而导致整个程序停止了。不过这个异常是因为客户端的突然关闭,不是编码的问题。
图片可以正常下载成功
错误的起因
之所以发生这个错误,是因为我认为我既然消灭了404,那就干脆不读取请求了。因为我不处理请求,所以索性就不去读取请求报文了,只给出响应报文。然后就产生了这个问题,并且我找了两天才发现和是否读取请求报文有关。因为这个在编码上确实是没有问题的,并且还是涉及到了浏览器方面的知识,所以我无法解决。
所以,我就借助了别人的帮助,我在StackOverFlow发了一个问题,几个小时就有人给我了解答。回答,大致说清楚了问题的原因
我在这里贴一下回答的截图:
翻译:
把Socket想象成两根吸管(一根朝一个方向,另一根朝另一个方向)。如果你把东西放在吸管里,而另一边不把它拿出来,你就会注意到,并且不能再往里面放东西了。HTTP被定义为“客户端将其请求放入,服务器读取请求,然后将响应放入另一根吸管”。你没有从吸管上拿走所有东西,就违反了合同,浏览器不喜欢这样。实际上,浏览器会注意到您的服务器甚至都不接受请求(即使它很短),并且假设非常正确,如果请求甚至都没有被读取,那么响应就不可能是好的。
Because you are violating the protocol.
所以就如这句话所言,我违反了HTTP协议的规定。不读取客户端请求,只是发送响应,看起来是一个很疯狂的事,我居然为了省事就去做了。不过这个知识点是我的盲区了,遇到的时候还是很头疼的。
随带一提
下面这段代码,我是完全读取了请求报文吗?
InputStream in = new BufferedInputStream(client.getInputStream());
StringBuilder request = new StringBuilder(80);
while (true) {
int c = in.read();
if (c == '\r' || c == '\n' || c == -1) break;
request.append((char)c);
}
如果你看了解析报文那篇博客,应该就会发现我这里只是读取请求报文的第一行。但是按照上面的解释,我应该完全读取整个请求报文才行呀!为什么我只是读取一行就行了呢?
这里涉及到了一个小的知识点:BufferedInputStream 缓冲流的为什么快的特点。缓冲流内部有一个8196大小的字节数组,它会先将数据读取到字节数组中,然后我们对应于流的操作,其实都是在操作内存中的缓冲数组内进行的,所以缓冲流的速度很快。当缓冲数据读取完了,还会再次读取填充到数组中的。 所以,虽然我们只是读取了几个字节的数据,但是缓冲流已经把所有的数据都读入内部的缓冲数组中了。但是只要请求头大于缓冲数组的大小,这样应该也是不行了。只是通常报文是不会有那么大的(除非携带了二进制数据部分)。
如果不使用缓冲流,只是使用如下的代码:
InputStream in = client.getInputStream();
访问结果
虽然有可能访问完整的图片,但是大部分情况下图片还是显示不完全,并且图片是无法下载的,这就说明了问题还是存在的。
说明
这个问题,表明我们所了解的东西还是太少了。对于这些基础的协议以及浏览器工作的方式,都不太了解。出现了这种底层的问题一般也很难解决,所以必要的时候还是要借助一下社区的力量–站在巨人的肩膀上!