前言
HTTP(超文本传输协议)是万维网的核心,我们日常所接触最多的协议之一,只要你打开浏览器,便无时无刻不在通过http协议从万维网上获取与提交信息,可能你还没意识到,当你在浏览器输入链接并按下回车时,HTTP协议便不断地帮我们从服务器获取消息了。

HTTP协议位于应用层,它想要传输数据必须需要传输层的协议做支持,HTTP为了保证可靠连接,利用TCP协议实现浏览器和服务器之间的交互。具体的流程是:
每个万维网站点都有一个服务器进程,它不断地监听(轮询监听:这里涉及到网络IO,可以参考博主的前一篇文章)tcp的80端口,以便发现是否有浏览器向它发出连接请求,一旦建立连接之后,浏览器就向万维网服务器发出浏览某个页面的<请求>,服务器接着返回所请求的页面作为<响应>。最后TCP连接会被释放。在浏览器和服务器请求和响应的交互中,必须遵循的规则和格式就是超文本传输协议HTTP。
接下来我们分别介绍从客户访问万维网请求,到服务器响应的全过程如下:
1. 客户端浏览器根据用户输入的URL,向域名服务器(DNS)查询网站的IP地址。
2. 浏览器根据DNS返回的地址,与服务器端口80建立TCP连接
3. 浏览器向服务器提交HTTP请求,如 GET 127.0.0.1/index.html
4. 基于该请求的内容,服务器找到对应的文件回送给浏览器,返回响应报文 HTTP1.1 200 OK
1 干掉域名,拿到IP,才知道HTTP协议到底是谁和谁建立了连接。
用户访问万维网的第一步,即是客户浏览器通过用户输入的URL,向dns查询对应网站的IP。
域名这东西是干嘛的呢?
一般来说,大厂为了提高自己网址的易记忆性,都会租用一个域名来代替实际的干巴巴的IP地址,如baidu.com,你在浏览器下按下F12,即可进入到开发者工具看到他的实际IP地址。输入域名和输入IP地址的作用基本相同
为了标识互联网上的每一台主机,IP协议给每一台主机分配了唯一一个逻辑地址—IP地址(由网络号和主机号组成),一般来说也就是给你提供服务的是哪台服务器。数字形式的地址对于计算机来说是方便了,但对于人类来说却既难以记忆又难以输入。于是“域名系统”(Domain Name System)出现了,用有意义的名字来作为 IP 地址的等价替代

实际上,这个从域名映射到IP地址的过程,就是DNS服务器帮你完成的,它还有一个功能,为了减少传输时延,互联网公司通常会在不同的地方布置多台服务器,这些服务器IP地址各不相同,但可都可以通过一个DNS服务器映射,也许现在的你看到baidu的IP地址和我的就完全不一样。
得到IP之后,便可以试着建立网络连接了
2. 建立TCP连接,为HTTP报文传输铺路。
一次完整的网页获取需要多少个报文?
HTTP 翻译成中文是“超文本传输协议”,是一个应用层的协议,通常基于 TCP/IP,能够在网络的任意两点之间传输文字、图片、音频、视频等数据。既然基于TCP/IP,那便能通过抓包来分析HTTP的运行流程,让我们分析下一个HTTP协议建立连接,tcp都做了哪些工作。
Wireshark是著名的网络抓包工具,能够截获在 TCP/IP 协议栈中传输的所有流量,并按协议类型、地址、端口等任意过滤,功能非常强大,是学习网络协议的必备工具。也是作者接下来给大家展示的工具。
假设你写了一个简单的服务器程序程序,当浏览器访问主页是,服务器会返回最简单的状态码HTTP1.1 200 OK,当你把你的服务器部署好之后,(这里代码下面半段教大家写,相当于一个简单的TOMCAT服务器)尝试利用抓包工具抓包,如下

在一次最简单的HTTP请求响应过程中,我们可以看到TCP协议一共收发了11个包,我给大家总结成了4个过程,如下图

除开浏览器和服务器的三次握手和四次挥手之外,实际最关键的包只有四个,浏览器发送请求报文等待服务器回复,服务器发送响应报文等待浏览器回复。
需要注意的是,由于NGINX代理的存在,不论你端口设置的是多少,最后都会由负载均衡转到80端口去,因为NGINX和HTTP默认的都是80。
3. 浏览器如何向服务器提交请求?(附代码)
HTTP协议的格式是什么?
以上,我们只讲了http协议的传输过程,但没有讲解它的组成,
HTTP 协议的请求报文和响应报文的结构基本相同,由三大部分组成:
**起始行:**描述请求或响应的基本信息;分为请求行和响应行
头部字段:使用 key-value 形式更详细地说明报文;
消息正文:实际传输的数据,可能是纯文本,也可以是图片、视频等二进制数据。
头部字段主要是约定双方通信的编码,语言,字段等信息,暂且略过。
先看请求行,由三部分构成:
请求方法:是一个动词,常用 GET/POST,表示对资源的操作; 大家记住 POST,DELETE,PUT,GET对应数据的增删改查即可
请求目标:通常是一个 URI,标记了请求方法要操作的资源;
版本号:表示报文使用的 HTTP 协议版本。
如下面一行请求指令,GET是请求方法,127.0.0.1/index是请求目标,HTTP/1.1是版本号
GET 127.0.0.1/index HTTP/1.1//这句话和在浏览器输入http://127.0.0.1:80/index 是一样的
响应行要简单一些,同样也是由三部分构成:
版本号:表示报文使用的 HTTP 协议版本;
状态码:一个三位数,用代码的形式表示处理的结果,比如 200 是成功,500 是服务器错误;
原因:作为数字状态码补充,是更详细的解释文字,帮助人理解原因。
一般最想看到的就是
HTTP/1.1 200 OK

响应状态码比较简单,就以下几类,大家可以查一下

附代码,如何利用TCP协议实现http请求
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
public class TestClient {
public static void main(String[] args)throws Exception{
Socket socket = new Socket("localhost", 8001);
OutputStream output = socket.getOutputStream();
//建立TCP连接,向客户端发送GET /index HTTP/1.1并接收响应
output.write("GET /index HTTP/1.1".getBytes());
socket.shutdownOutput();
InputStream input = socket.getInputStream();
byte[] buffer = new byte[2048];
int length = input.read(buffer);
StringBuilder response = new StringBuilder();
for (int j=0; j<length; j++) {
response.append((char)buffer[j]);
}
System.out.println(response.toString());
socket.shutdownInput();
socket.close();
}
}
你在浏览器输入127.0.0.1:80的过程,其实就是上面的代码实现的过程
4. 服务器如何响应报文?(附代码)
你看到的网页是怎么从服务器传输过来的?
服务器的响应主要分为静态响应和动态响应,比如说你要访问网站首页,那服务器就直接返回给你一个HTML文件,这就是静态响应,如果你要访问个人空间,那服务器就要根据你的个人信息返回独特的响应,这里面要用到servlet,这里放一个静态响应的代码。
当浏览器访问index界面时,返回这个界面给浏览器。http底层采用tcp实现,也就是说文件也都被转为了字节流发送出去。
服务器建立TCP连接
package WebServer.Connector;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
public class ConnectServer implements Runnable{
public ConnectServer (int PORT) throws IOException {
this.PORT = PORT;
}
private int PORT = 8001;
private static ServerSocket server;
private void close(ServerSocket server) {
try {
server.close();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("关闭serverSocket");
}
@Override
public void run() {
try {
server = new ServerSocket(PORT);
System.out.println("启动服务器,监听端口:" + PORT );
while (true) {
Socket socket = server.accept();
InputStream inputStream = socket.getInputStream();
OutputStream outputStream = socket.getOutputStream();
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream));
Request request = new Request(inputStream);
Response response = new Response(outputStream);//这里是processor的实现
response.sendStaticResource(request);
socket.shutdownOutput();
}
} catch (IOException e) {
e.printStackTrace();
}
finally {
close(server);
}
}
public static void main(String[] args) throws IOException {
ConnectServer cs = new ConnectServer(8001);
cs.run();
}
}
request文件,处理解析http请求,得到请求地址
package WebServer.Connector;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
public class Request {
public Request(InputStream input) {
this.input = input;
}
public String getUri() {
return uri;
}
private InputStream input;
private String uri;
public String handle()
{
int length = 0;
byte[] buffer =new byte[1024];
try {
length = input.read(buffer);
} catch (IOException e) {
e.printStackTrace();
}
StringBuilder request = new StringBuilder();
for (int j=0;j<=length;j++)
{request.append((char)buffer[j]);}
int freq =0;
int flag=0;
for (int i =0;i<request.length();i++)
{
if(request.charAt(i)==' ')
{
freq += 1;
if(freq == 2)
{//已经经过俩空格,截取浏览器要get的内容
uri = request.substring(flag+1,i).toString();
System.out.println(uri);
return uri;
}
flag =i;
}
}
return "";
}
}
response文件,给浏览器返回一个静态界面
package WebServer.Connector;
import com.sun.jdi.connect.Connector;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
public class Response {
public Response(OutputStream output)
{
this.output = output;
}
public OutputStream output;
public void sendStaticResource(Request request)
{
File file = new File(HttpUtils.WEB_ROOT,request.handle());
try{
write(file,HttpStatus.SC_OK);
}
catch (Exception e)
{
System.out.println("失败");
}
}
private void write(File resource,HttpStatus status) throws FileNotFoundException {
try(FileInputStream fis = new FileInputStream(resource))
{
//将文件转为字节流发送给浏览器 output.write(HttpUtils.renderStatus(status).getBytes(StandardCharsets.UTF_8));
byte[] buffer = new byte[1024];
int length = 0;
while((length = fis.read(buffer,0,1024))!=-1)
{
output.write(buffer,0,length);
}
// System.out.println(output);
}
catch (Exception e)
{}
}
}
HTTPUtils文件:记载一些http状态
package WebServer.Connector;
import java.io.File;
public class HttpUtils {
public static final String WEB_ROOT =
System.getProperty("user.dir") + File.separator + "webstatic";
public static final String PROTOCOL = "HTTP/1.1";
public static final String CARRIAGE = "\r";
public static final String NEWLINE = "\n";
public static final String SPACE = " ";
public static String renderStatus(HttpStatus status) {
StringBuilder sb = new StringBuilder(PROTOCOL)
.append(SPACE)
.append(status.getStatusCode())
.append(SPACE)
.append(status.getReason())
.append(CARRIAGE).append(NEWLINE)
.append(CARRIAGE).append(NEWLINE);
return sb.toString();
}
}
enum HttpStatus {
SC_OK(200, "OK"),
SC_NOT_FOUND(404, "File Not Found");
private int statusCode;
private String reason;
HttpStatus(int code, String reason) {
this.statusCode = code;
this.reason = reason;
}
public int getStatusCode() {
return statusCode;
}
public String getReason() {
return reason;
}
}
index.html,主页,意思一下就行
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>hello</title>
</head>
<body>
<h4 STYLE="color: red"> hello </h4>
</body>
</html>
最后,将tcp服务器跑起来,然后在浏览器输入以下网址(相当于 GET /index.html http1.1),就可以看到我们写的tcp服务器给浏览器返回个html界面啦。
这就是tomcat内部实现的功能。

总结
在这里附上我总结的HTTP知识点


1321

被折叠的 条评论
为什么被折叠?



