HTTP协议的底层TCP传输原理--你知道输入浏览器的网址经历了什么吗


前言

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知识点
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值