网络基础(二)应用层HTTP协议

应用层: 负责应用程序之间的数据传输,我们程序员就工作在这一层上

TCP协议的面向字节流特性以及引申出来的粘包问题

从套接字编程角度对于传输层TCP协议特点里的—面向字节流解读
从创建一个TCP的socket起, 同时在内核中创建一个发送缓冲区和一个接收缓冲区;
对于发送:
1.调用write时, 数据会先写入发送缓冲区中;
2.如果发送的字节数太长, 会被拆分成多个TCP的数据包发出;
3.如果发送的字节数太短, 就会先在缓冲区里等待, 等到缓冲区长度差不多了, 或者其他合适的时机发送出去;
对于接收:
接收数据的时候, 数据也是从网卡驱动程序到达内核的接收缓冲区;然后应用程序可以调用read从接收缓冲区拿数据(由于数据可能是被拆分的,如何处理完整正确的合并?)。

由于缓冲区的存在, TCP程序的读和写不需要一一匹配, 例如:
写数据时, 在不超过传输上限的情况下可以调用一次write直接一次写完, 也可以调用多次次write, 每次写一段字节;对于读数据也是同上情况。这就是面向字节流特征的体现。
但是完成了面向字节流的数据传输后,另一边该如何完整正确的整合数据便是个问题:
TCP粘包问题
首先要明确, 粘包问题中的 “包” , 是指的应用层的数据包。
对于应用层的数据,传输层负责数据的在主机之间的传输,而对于TCP协议来说,是面向字节流传输的,就如上面所分析,对于一条数据来说是可能被分段的,传输过程中如果不加标识,可能会出现数据揉在一起,有悖于原来的意思的问题,即粘包问题。

具体分析:

  • 在TCP的协议头中, 没有如同UDP一样的 “报文长度” 这样的字段, 但是有一个序号(包序)这样的字段.
  • 站在传输层的角度, TCP是一个一个报文过来的. 按照序号排好序放在缓冲区中.
  • 但是站在应用层的角度, 看到的只是一串连续的字节数据,那么应用程序看到了这么一连串的字节数据, 就不知道从哪个部分开始到哪个部分, 是一个完整的应用层数据包.

那么如何避免粘包问题呢?

  • 1.约定数据长度
    对于定长的包, 保证每次都按固定大小读取即可; 例如把数据放到一个结构体A, 是固定大小的, 那么就从缓冲区从头开始按sizeof(A)依次读取即可,但是这就和数据结构里学的静态定长数组一样,不能应对复杂情况,例如一个通信连接当中, 有多个定长的数据结构或者无法定长的数据(取决于对端)是无法具体确定准确的固定长度的。
  • 2.数据包头+分隔符
    对于变长的包, 可以在包头的位置, 约定一个包总长度的字段, 从而就知道了包的结束位置,但是对于下一条数据怎么找到包头?
    所以还可以在包和包之间使用明确的分隔符来明确两个包之间的边界(应用层协议, 是程序猿自己来定的, 只要保证分隔符不和正文冲突即可)。判断什么时候是包头的时候,只需要判断当中接收到的数据里面有没有分隔符,分隔符后面便是下一条数据包的包头。
    这样确定了包与包之间的边界后,粘包问题便顺利解决。
    另外注意
    分隔符不一定就是一个字节的字符,也可以是一个字符串(看程序员自己定),通常情况下工业上都是使用\r\n来进行分隔; 其十六进制表示为0D 0A

对于UDP协议来说, 是否也存在 “粘包问题” 呢?
对于UDP, 如果还没有往上层交付数据, UDP的报文长度仍然在. 同时, UDP是一条一条把数据交付给应用层. 就有很明确的数据边界.
站在应用层的站在应用层的角度, 使用UDP的时候, 要么收到完整的UDP报文, 要么不收. 不会出现"半个"的情况.

HTTP协议介绍

web客户端和服务器之间用来交互的基于文本的应用层级协议(标准),也称超文本传输协议。且是基于传输层的tcp协议。而HTTP使用TCP而不是UDP的原因在于一般打开一个网页必须传送很多数据,而TCP协议提供可靠传输,按顺序组织数据,错误纠正等功能。
基本流程:一个web客户端(浏览器)打开一个到服务器的因特网连接,并且请求某些内容。服务器响应所请求的内容(可能响应的是请求的文件、错误消息等),然后关闭连接。浏览器读取这些内容,并显示到屏幕上。
而且web内容可以用一种叫做HTML的超文本标记语言来编写。一个html程序(页)包含指令(标记),他们告诉浏览器如何显示这页中的各种文本和图形对象,且还可以在一个页面上包含“指针”(超链接),这些指针可以指向存放在任何因特网主机上的内容。
特点介绍:
1.基于请求/响应模型的协议。先有请求后有响应。
2.简单快速:客户向服务器请求服务时,只需传送请求方法和路径(由URL标识)。请求方法常用的有GET、HEAD、POST。每种方法规定了客户与服务器联系的类型不同。由于HTTP协议简单,使得HTTP服务器的程序规模小,因而通信速度很快。
3.灵活:HTTP允许传输任意类型的数据对象。正在传输的类型由Content-Type加以标记。
4.无连接:无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间。
5.无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。

通用资源定位符URL

web服务器以两种不同的方式向客户端提供内容:

  • 取一个磁盘文件,并将它的内容返回给客户端
    磁盘文件称为静态内容,而返回文件给客户端的过程叫做服务静态内容。
  • 运行一个可执行文件,并将它的输出返回给客户端
    运行时可执行文件产生的输出称为动态内容,而运行程序并返回它的输出到客户端的过程称为服务动态内容。

每条由web服务器但会的内容都是和它管理的某个文件相关联的。这些文件中的每一个都有唯一的一个名字,叫做URL(通用资源定位符)。
当你在浏览器的地址框中输入一个URL或是单击一个超链接时,URL就确定了要浏览的地址。浏览器通过超文本传输协议(HTTP),将Web服务器上站点的网页代码提取出来,即展现网页。

简单的URL格式分析:

例如:http://www.baidu.com:80/index.html
表示因特网主机www.baidu.com即百度的服务器主机上一个称为/index.html的HTML文件,它是由一个监听端口80(知名端口)的web服务器进程管理的。而可执行文件的URL可以在文件名后面包括程序参数,"?"字符分隔文件名和参数,而且每个参数都用“&”字符分隔开。
**注意:**在事务过程中,客户端和服务器使用的是URL的不同部分,例如上面的静态服务例子,客户端适用前缀http://www.baidu.com:80 来决定与哪类服务器联系,及服务器在哪,以及它的侦听端口号是多少。而服务器使用后缀 /index.html来发现在它文件系统中的文件,并确定是静态内容还是动态内容。
服务器如何解释一个URL后缀指向的静态内容还是动态内容:
可以试着将一个服务器配置成这样:所有静态内容存放在目录/user/httpd/html,而所有动态内容都存放在目录/user/httpd/cgi-bin下。

URL具体解析:
http://user;passwd@www.baidu.com:80/dir/index.htm?id=1&wd=C%2B%2B #ch1

1.http:// :协议方案名
2.登录信息:用户名和密码(一般都是隐藏的)
3.域名(服务器的ip地址经DNS域名协议转换)
4.80 :服务端侦听端口
5.带层次的文件路径:浏览器要请求的资源路径,"/” 并不是linux操作系统当中的根目录,而是http服务器被请求内容类型的主目录
6.浏览器给http服务器提交的数据(或参数)即:?分隔符后面的id=1&wd=C%2B%2B:

  • 6.1提交的单个数据是按照key=value的形式,多个数据中间使用&进行分隔;

  • 6.2在提交的数据当中如果不加以区分,有可能造成歧义,例如说,单纯的+号 C++字符串的字符’+‘ ,存在二义性;所以在传输特殊字符的时候,需要进行url编码( urlencode ) ,编码的方式就是将特殊的字符按照16进制进行传输;为了区别编码之后的字符,需要在编码之后的内容前面加上%,用来区分;例如 c++编码之后 C%2B%2B

  • 6.3对于服务器而言,在接收到url编码之后的查询字符串,需要进行url解码( urldecode )

7.片段标识符:表示当前页面被浏览器加载之后,定位到什么位置了,应用例子:比如检测到浏览到底部之后,页面会出现一个回到顶部的按钮。

Http协议的组成

Http协议由Http请求和Http响应组成,当在浏览器中输入网址访问某个网站时,你的浏览器会将你的请求封装成一个Http请求发送给服务器站点,服务器接收到请求后会组织响应数据封装成一个Http响应返回给浏览器。即客户端发起HTTP请求,服务器返回HTTP响应,然后关闭连接。
http协议的版本
HTTP/1.0:发送请求,创建一次连接(短连接),获得一个web资源(执行一个HTTP事务),连接断开。
HTTP/1.1:发送请求,创建一次连接(支持长连接),在此持久连接上获得多个web资源(执行多个HTTP事务),连接断开。
说明:两个版本是互相兼容的。

1.HTTP请求

一个HTTP请求的组成:请求行 + 请求报头+ 请求体(正文)
请求行的形式:[请求方法] + [URl] + [版本]
请求行必须在http请求的第一行,又叫请求首行。
请求方法:
GET:从服务器上面获取一个资源的方法(大多数HTTP请求都是这个类型)

  • 1.GET并不是只能向服务器获取资源,其实也可以在查询字符串当中提交数据到浏览器当中
  • 2.GET请求提交的数据都是在URL当中(相应的后缀,包括文件名和可选参数)。

POST :向服务器提交数据的方法

  • 1.POST提交数据是在正文当中

小结:
POST方法比GET方法更加私密;
POST比GET更加安全。

请求报头:
请求头从第二行开始,到第一个空格结束。请求头和请求体之间存在一个空行(用来终止报头)。
具有多行数据,每行数据的格式都是key:value的形式,每行数据使用\r\n分隔。(回车加换行)

  • Content-Length:正文长度,防止粘包
  • Content-Type:正文的编码格式
    text/html:HTML格式
    text/plain:纯文本格式
    text/png:png图片的格式
    application/json:json数据格式
    application/msword:word文档格式
  • referer:当前的页面是从哪一个页面跳转过来的;
  • Cookie:向服务器提交浏览器本地保存的认证信息,认证信息是之前登陆服务器的时候,服务器返回回来的
  • Tranfer-Encoding :针对于正文而言,可以支持分块传输;
  • Location :和重定向搭配使用, http服务器会告诉浏览器,你刚刚请求的页面应该去哪一个地址上面重新申请下;
  • User-Agent:生命操作系统和浏览器版本信息;
  • Connection: keep-alive保持长连接
    http协议应用协议,传输层使用的是tcp协议,早期http是无状态的协议,使用的是tcp短连接;

请求报头演示
host报头中的数据指示了原始服务器中的域名(没域名就是ip地址),使得代理能够判断它是否在本地缓存中已经拥有被请求内容的副本了。
在这里插入图片描述

2.HTTP响应

一个HTTP响应组成: 响应行+响应报头+ 响应体
响应行的格式: [协议版本] + [状态码] + [状态码解释]
状态码:由3位数字组成,第一个数字定义了响应的类别
状态码:状态码解释
1xx:指示信息,表示请求已接收,继续处理
2xx:成功,表示请求已被成功接受,处理。

  • 200 OK:客户端请求成功
  • 204 No Content:无内容。服务器成功处理,但未返回内容。一般用在只是客户端向服务器发送信息,而服务器不用向客户端返回什么信息的情况。不会刷新页面。
  • 206 Partial Content:服务器已经完成了部分GET请求(客户端进行了范围请求)。响应报文中包含Content-Range指定范围的实体内容

3xx:重定向

  • 301 Moved Permanently:永久重定向,表示请求的资源已经永久的搬到了其他位置。
  • 302 Found:临时重定向,表示请求的资源临时搬到了其他位置
  • 303 See Other:临时重定向,应使用GET定向获取请求资源。303功能与302一样,区别只是303明确客户端应该使用GET访问
  • 307 Temporary Redirect:临时重定向,和302有着相同含义。POST不会变成GET

4xx:客户端错误

  • 400 Bad Request:客户端请求有语法错误,服务器无法理解。
  • 401 Unauthorized:请求未经授权,这个状态代码必须和WWW-Authenticate报头域一起使用。
  • 403 Forbidden:服务器收到请求,但是拒绝提供服务
  • 404 Not Found:请求资源不存在。比如,输入了错误的url

5xx:服务器端错误,服务器未能实现合法的请求。

  • 500 Internal Server Error:服务器发生不可预期的错误。
  • 503 Server Unavailable:服务器当前不能处理客户端的请求,一段时间后可能恢复正常

响应报头
响应报头提供了响应的附加信息,两个最重要的报头是Content-Type和Content-Length.
响应头部当中也是具有多行数据,每行数据是key-value的形式,行与行之间使用\r\n进行分隔;且响应头部的字段也是和请求头部大同小异。
响应报头演示
在这里插入图片描述
响应体
响应体是服务器回写给客户端的页面正文,浏览器将正文加载到内存,然后解析渲染 显示页面内容。

总结

  • http协议是应用层的协议,在传输层使用tcp协议,网络层使用ip协议
  • http协议的本身是为了处理大量的请求,一开始在传输层使用tcp连接为短连接,现在版本已经支持长连接
  • http是没有加密版本的http协议,加密版本可以使用https,s表示ssl(非对称加密)

简单的服务静态内容测试代码

利用之前的tcp客户端服务器通信的小程序为基础,服务器给浏览器响应静态文本。

#include "tcpclass.hpp"
#include <string.h>
#include <sstream>

int main(int argc, char* argv[])
{
    if(argc != 3)
    {
        printf("Start Server: ./svr [ip] [port]\n");
        return 0;
    }

    std::string ip  = argv[1];

    uint16_t port = atoi(argv[2]);  

    TcpSvr ts;
    if(!ts.CreateSocket())
    {
        return 0;
    }

    if(!ts.Bind(ip, port))
    {
        return 0;
    }

    if(!ts.Listen())
    {
        return 0;
    }

    //为accept函数接收回来的新创建的socket准备的
    TcpSvr ser_ts;

    while(1)
    {
        struct sockaddr_in clientaddr;
        if(!ts.Accept(&clientaddr, &ser_ts))
        {
            return 0;
        }

        std::string buf;
        ser_ts.Recv(&buf);
        printf("chrome say: %s\n", buf.c_str());

        //C风格的格式化字符串
        //char buffer[1024] = {0};
        响应首行\r\n  包含协议版本、状态码、状态码解释
        响应头部
          Content-Length: \r\n
          Content-Type: \r\n
        \r\n
        //正文信息
        //std::string body = "<html><h1>linux-study</h1></html>";
        //snprintf(buffer, sizeof(buffer) - 1, "%s %s %s\r\nContent-Length: %lu\r\nContent-Type: %s\r\n\r\n", "HTTP/1.1", "200", "OK", body.size(), "text/html");
        //printf("buffer:%s\n", buffer);
        //std::string header;
        //header.assign(buffer, strlen(buffer));

        //C++风格
        //std::string body = "<html><h1>linux-study</h1></html>";
        //std::stringstream ss; //输入流对象
        //ss << "HTTP/1.1 200 OK\r\n";
        //ss << "Content-Type: text/html\r\n";
        //ss << "Content-Length: " << body.size() << "\r\n";
        //ss << "\r\n";
        //std::string header = ss.str();
        
        //状态演示
        //302
        //std::string body = "<html><h1>linux-study</h1></html>";
        //std::stringstream ss;
        //ss << "HTTP/1.1 302 Found\r\n";
        //ss << "Content-Type: text/html\r\n";
        //ss << "Content-Length: " << body.size() << "\r\n";
        //ss << "Location: https://www.baidu.com\r\n";
        //ss << "\r\n";
        //std::string header = ss.str();
        
        //404
        //std::string body = "<html><h1>linux-study</h1></html>";
        //std::stringstream ss;
        //ss << "HTTP/1.1 404 Page Not Found\r\n";
        //ss << "Content-Type: text/html\r\n";
        //ss << "Content-Length: " << body.size() << "\r\n";
        //ss << "\r\n";
        //std::string header = ss.str();

        //502
        std::string body = "<html><h1>linux-study</h1></html>";
        std::stringstream ss;
        ss << "HTTP/1.1 502 Bad Gateway\r\n";
        ss << "Content-Type: text/html\r\n";
        ss << "Content-Length: " << body.size() << "\r\n";
        ss << "\r\n";
        std::string header = ss.str();

        ser_ts.Send(header);
        ser_ts.Send(body);
    }
    ser_ts.Close();//关闭新创建出来的套接字
    ts.Close();//关闭掉了侦听套接字
    return 0;
}

运行结果
在这里插入图片描述
对应不同的状态码与状态码解释
在这里插入图片描述

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值