• 计算机网络--应用层(http)


                                    

           今天我们正式来介绍计算机网络协议的第一层--应用层(http协议)。当然,针对https协议博主在下一篇博客再来讲解,毕竟https是针对网络安全工程师来说是比较重要的,博主是后端开发的,所以今天着重介绍http协议。

    目录

    再谈OSI七层协议中上三层

    认识URL

    urlencode和urldecode

    HTTP协议格式

    Http.cc

    Sock.hpp

    ​编辑 HTTP的方法

    在Linux终端访问

    Http代码验证GET方法 

    stat函数

    Http.cc

    Sock.hpp

    ./WWWROOT/index.html

    Http代码验证POST方法 

    Http.cc

    Sock.hpp

    ./WWWROOT/index.html

    GET和POST方法总结

    第一批结论:概念问题

    第二批结论:区别

    第三批结论:如何选择

    HTTP的状态码

    301永久重定向

    Location

    Http.cc

     302/307临时重定向

     HTTP常见的Header

    Connect中长链接&&短连接

    cookie && session

    cookie

    单纯使用cookie带来的问题 

    session


    再谈OSI七层协议中上三层

    还记得博主上一篇博客写的CS模式的网络在线版本计算器吗,本质就是一个应用层网络服务。

    http协议,本质在定位上 和博主上次写的网络计算器没有区别,都是应用层协议。

    网络通信、序列化和反序列化、协议细节在http协议内部,它都要实现。

    认识URL

            我们请求的图片,html,css,js,视频,音频,标签,文档等这些都称之为"资源"。在服务器后台,是用Linux做的。

           我们也知道,IP + Port唯一的确定一个进程,但是无法唯一的确认一个资源!公网IP地址是唯一确认一台主机的,而我们所谓的网络"资源",都一定是存在于网络中的一台Linux机器上!Linux或者传统的操作系统,保存资源的方式,都是以文件的方式保存的。单Linux系统,标识一个唯一资源的方式,是通过路径!

    所以,IP + Linux路径,就可以唯一的确认一个网络资源。

    接下来,我们来认识一下URL。

    注意:/data/# games 中的 / 不是根目录,而是web根目录的首页,这个在后面我们来验证。

            所以URL就是我们平常所说的网址,通过URL,那么就可以IP+Linux路径,来确认网络中唯一的一个资源。

    urlencode和urldecode

    / ? : 等这样的字符, 已经被url当做特殊意义理解了。因此这些字符不能随意出现。
    比如, 某个参数中需要带有这些特殊字符, 就必须先对特殊字符进行转义。
    转义的规则如下:
    将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY格式。

    举个栗子:

    从上面我们发现+就被处理成了%2B。 

    同时对于解码,我们可以使用网页在线解码工具:

    这个呢就不是重点了,有兴趣的朋友可以在网上搜~ 

    HTTP协议格式

    简化认识:

    无论是请求还是响应,基本上http都是按照行(\n)为单位进行构建请求或者相应的!

    无论是请求还是相应,几乎都是由3或者4部分组成。

    博主画个图来形象展示一下:

    思考1:http如何解包,如何封装,如何分用?

    解包和封装都是使用空行(特殊字符来实现的),用空行将长字符串一分为二。

    至于如何分用的问题,不是http解决的,是具体的应用代码解决,http需要有接口来帮助上层获取参数。

    思考2:http请求或者响应,是如何被读取的?http请求是如何被发送的?

    我们可以把上面的问题转换成http request和http response被如何看待,我们可以把请求和响应整体看作是一个大的字符串!!!

    形如:

     

    思考3:如何理解普通用户的上网行为?是为了简单吗?

    1、向目标服务器上传你的数据。

    2、从目标服务器拿到你要的资源。

    这其实就是IO的过程。

    我们来写个代码来看一下Http的响应格式:

    Http.cc

    1. #include "Sock.hpp"
    2. #include
    3. #include
    4. using namespace std;
    5. void* HandlerHttpRequest(void* args)
    6. {
    7. //Htttp协议,如果自己写的话,本质是我们根据协议内容,来进行文本分析
    8. int sock = *(int*)args;
    9. delete (int*)args;
    10. pthread_detach(pthread_self());
    11. #define SIZE 1024*10
    12. char buffer[SIZE];
    13. memset(buffer, 0, sizeof(buffer));
    14. ssize_t s = recv(sock, buffer, sizeof(buffer)-1, 0);
    15. if(s > 0)
    16. {
    17. buffer[s] = 0;
    18. cout << buffer;//查看http的请求格式!
    19. //要构建响应,必须有协议!
    20. string http_response = "http/1.0 200 OK\n"; //协议版本 + 状态码 + 状态码描述
    21. http_response += "Content-Type: text/plain\n"; //text/plain 正文是普通的文本
    22. http_response += "\n"; //传说中的空行
    23. http_response += "hello world, hello cyq!"; //正文
    24. send(sock, http_response.c_str(), http_response.size(), 0);
    25. }
    26. close(sock);
    27. return nullptr;
    28. }
    29. void Usage(string proc)
    30. {
    31. cout << "Usage: " << proc << "port" << endl;
    32. }
    33. // ./Http port
    34. int main(int args, char* argv[])
    35. {
    36. if(args != 2)
    37. {
    38. Usage(argv[0]);
    39. return -1;
    40. }
    41. uint16_t port = atoi(argv[1]);
    42. int listen_sock = Sock::Socket();
    43. Sock::Bind(listen_sock, port);
    44. Sock::Listen(listen_sock);
    45. for(;;)
    46. {
    47. int sock = Sock::Accept(listen_sock);
    48. if(sock > 0)
    49. {
    50. pthread_t tid;
    51. int* parm = new int(sock);
    52. pthread_create(&tid, nullptr, HandlerHttpRequest, (void*)parm);
    53. }
    54. }
    55. return 0;
    56. }

    Sock.hpp

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. using namespace std;
    11. class Sock
    12. {
    13. public:
    14. static int Socket()
    15. {
    16. int sock = socket(AF_INET, SOCK_STREAM, 0);
    17. if(sock < 0)
    18. {
    19. cerr << "socket err" << endl;
    20. exit(2);
    21. }
    22. return sock;
    23. }
    24. static void Bind(int sock, uint16_t port)
    25. {
    26. struct sockaddr_in local;
    27. memset(&local, 0, sizeof(local));
    28. local.sin_family = AF_INET;
    29. local.sin_port = htons(port);
    30. local.sin_addr.s_addr = INADDR_ANY; //服务端 ip地址
    31. if(bind(sock, (struct sockaddr*)&local, sizeof(local)) < 0)
    32. {
    33. cerr << "bind error" << endl;
    34. exit(3);
    35. }
    36. }
    37. static void Listen(int sock)
    38. {
    39. if(listen(sock, 5) < 0)
    40. {
    41. cerr << "listen error" << endl;
    42. exit(4);
    43. }
    44. }
    45. static int Accept(int sock)
    46. {
    47. struct sockaddr_in peer; //输出型参数
    48. socklen_t len = sizeof(peer);
    49. int fd = accept(sock, (struct sockaddr*)&peer, &len);
    50. if(fd >= 0)
    51. {
    52. return fd;
    53. }
    54. return -1;
    55. }
    56. static void Connect(int sock, string ip, uint16_t port)
    57. {
    58. struct sockaddr_in server;
    59. memset(&server, 0, sizeof(server));
    60. server.sin_family = AF_INET;
    61. server.sin_port = htons(port);
    62. server.sin_addr.s_addr = inet_addr(ip.c_str()); //字符串转整型
    63. if(connect(sock, (struct sockaddr*)&server, sizeof(server)) == 0)
    64. {
    65. cout << "Connect Success" << endl;
    66. }
    67. else
    68. {
    69. cout << "Connect failed" << endl;
    70. exit(5);
    71. }
    72. }
    73. };

    实验结果:

    我们看一下服务端给客户端的响应:

    我们来看一下客户端的请求:

     HTTP的方法

     HTTP的方法有很多,在这里我们只需要学会GET和POST方法就可以了。

    在Linux终端访问

    注意:GET / HTTP/1.0 中的第一个/不是表示根目录,而是请求该网站的首页(web根目录)。即:index.html或index.htm。

    如图所示:

    Http代码验证GET方法 

    stat函数

    const char* payh:文件路径

    struct stat* buf:输出型参数,我们可以得到以下文件的属性信息。

    在这里我们使用这个系统调用来计算一个文本的大小。

    Http.cc

    1. #include "Sock.hpp"
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. using namespace std;
    8. #define WWWROOT "./WWWROOT/"
    9. #define HOME_PAGE "index.html"
    10. void* HandlerHttpRequest(void* args)
    11. {
    12. //Htttp协议,如果自己写的话,本质是我们根据协议内容,来进行文本分析
    13. int sock = *(int*)args;
    14. delete (int*)args;
    15. pthread_detach(pthread_self());
    16. #define SIZE 1024*10
    17. char buffer[SIZE];
    18. memset(buffer, 0, sizeof(buffer));
    19. ssize_t s = recv(sock, buffer, sizeof(buffer)-1, 0);
    20. if(s > 0)
    21. {
    22. buffer[s] = 0; //打印客户端请求
    23. cout << buffer;
    24. string html_file = WWWROOT;
    25. html_file += HOME_PAGE;
    26. struct stat st;
    27. stat(html_file.c_str(), &st); //计算index.html文件大小
    28. //构建响应
    29. //返回的时候,不仅仅是返回正文网页信息,而且还要包括http的请求
    30. string http_response = "http/1.0 200 OK\n";
    31. //正文部分的数据类型
    32. http_response += "Content-Type: text/html; charset=utf8\n";
    33. http_response += "Content-Length: ";
    34. http_response += to_string(st.st_size); //取出结构体中的这个变量
    35. http_response += "\n";
    36. http_response += "\n"; //空行
    37. //接下来才是正文
    38. ifstream in(html_file);
    39. if(!in.is_open())
    40. {
    41. cerr << "open html error!" << endl;
    42. }
    43. else
    44. {
    45. string content;
    46. string line;
    47. while(getline(in, line)) //可以读\n
    48. {
    49. content += line;
    50. }
    51. http_response += content;
    52. in.close();
    53. send(sock, http_response.c_str(), http_response.size(), 0);
    54. }
    55. }
    56. close(sock);
    57. return nullptr;
    58. }
    59. void Usage(string proc)
    60. {
    61. cout << "Usage: " << proc << "port" << endl;
    62. }
    63. // ./Http port
    64. int main(int args, char* argv[])
    65. {
    66. if(args != 2)
    67. {
    68. Usage(argv[0]);
    69. return -1;
    70. }
    71. uint16_t port = atoi(argv[1]);
    72. int listen_sock = Sock::Socket();
    73. Sock::Bind(listen_sock, port);
    74. Sock::Listen(listen_sock);
    75. for(;;)
    76. {
    77. int sock = Sock::Accept(listen_sock);
    78. if(sock > 0)
    79. {
    80. pthread_t tid;
    81. int* parm = new int(sock);
    82. pthread_create(&tid, nullptr, HandlerHttpRequest, (void*)parm);
    83. }
    84. }
    85. return 0;
    86. }

    Sock.hpp

    这一片段代码上面有,就不赋值过来了~

    ./WWWROOT/index.html

    1. html>
    2. <html>
    3. <head>
    4. <meta charset="utf-8">
    5. head>
    6. <body>
    7. <h2>hello 我是首页!h2>
    8. <h2>hello 我是表单!h2>
    9. <form action="/a/b/handler_from" method="GET">
    10. 姓名: <input type="text" name="name"><br/>
    11. 密码: <input type="password" name="passwd"><br/>
    12. <input type="submit" value="登陆">
    13. form>
    14. body>
    15. html>

    运行结果&&现象:

    Http代码验证POST方法 

    Http.cc

    和上面代码一样,这里就不复制了~

    Sock.hpp

    和上面代码一样,这里就不复制了~

    ./WWWROOT/index.html

    1. html>
    2. <html>
    3. <head>
    4. <meta charset="utf-8">
    5. head>
    6. <body>
    7. <h2>hello 我是首页!h2>
    8. <h2>hello 我是表单!h2>
    9. <form action="/a/b/handler_from" method="POST">
    10. 姓名: <input type="text" name="name"><br/>
    11. 密码: <input type="password" name="passwd"><br/>
    12. <input type="submit" value="登陆">
    13. form>
    14. body>
    15. html>

    前端页面代码,我们将GET方法改为POST方法。

    运行结果&&现象:

            注意,我们写的前端代码,作为正文响应给浏览器,浏览器会自动解析前端页面代码,这个就不需要我们去关系了。 

    GET和POST方法总结

    第一批结论:概念问题

    GET:方法叫做,获取,是最长用的方法,默认一般获取所有的网页,都是GET方法,但是如果GET是要提交参数(它能的!!),通过URL进行参数拼接,从而提交给server端。

    POST:方法叫做,推送,是提交参数比较常用的方法,但是如果要提交参数,一般是通过正文部分提交的,但是不要忘记,Content-Length:XXX表示参数的长度。

    第二批结论:区别

    提交参数的位置不同。

    1、参数提交的位置不同,POST方法比较私密(注意,私密 != 安全。安全一般是指经过加密处理的),不会回显到浏览器的url输入框中!get方法不私密,会将重要信息回显到url的输入框中,增加了被盗取的风险。

    2、GET是通过url传参的,而url是有大小限制的!和具体浏览器有关。

    POST方法是通过正文部分传参的,一般没有大小限制的。

    第三批结论:如何选择

    1、GET:如果要提交的参数不敏感、数量非常小,可以采用GET。

    2、否则,就使用POST方法

    总之,GET方法既可以获取参数,也可以提交参数。POST方法用来提交参数。

    HTTP的状态码

    最常见的状态码, 比如

    200(OK):表示请求成功。

    404(Not Found):请求的资源不存在。

    403(Forbidden):一般在公司内网访问某些资源时权限被禁止。

    302(Redirect, 重定向):临时重定向,除此之外还有307。

    301:表示永久重定向

    504(Bad Gateway) :服务器内部错误的状态码。

    针对上面的状态码,我们着重讲解3XX形式的状态码。

    301永久重定向

    Location

    搭配3xx状态码使用, 告诉客户端接下来要去哪里访问。该字段用于响应报头中。

    我们写一段代码来演示一下:

    Http.cc

    1. #include "Sock.hpp"
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. using namespace std;
    8. #define WWWROOT "./WWWROOT/"
    9. #define HOME_PAGE "index.html"
    10. void* HandlerHttpRequest(void* args)
    11. {
    12. //Htttp协议,如果自己写的话,本质是我们根据协议内容,来进行文本分析
    13. int sock = *(int*)args;
    14. delete (int*)args;
    15. pthread_detach(pthread_self());
    16. #define SIZE 1024*10
    17. char buffer[SIZE];
    18. memset(buffer, 0, sizeof(buffer));
    19. ssize_t s = recv(sock, buffer, sizeof(buffer)-1, 0);
    20. if(s > 0)
    21. {
    22. buffer[s] = 0;
    23. cout << buffer;
    24. string response = "http/1.1 301 Permanently moved\n"; //状态行
    25. response += "Location: https://www.qq.com/\n"; //server告诉浏览器去新的地址
    26. response += "\n";
    27. send(sock, response.c_str(), response.size(), 0);
    28. }
    29. close(sock);
    30. return nullptr;
    31. }
    32. void Usage(string proc)
    33. {
    34. cout << "Usage: " << proc << "port" << endl;
    35. }
    36. // ./Http port
    37. int main(int args, char* argv[])
    38. {
    39. if(args != 2)
    40. {
    41. Usage(argv[0]);
    42. return -1;
    43. }
    44. uint16_t port = atoi(argv[1]);
    45. int listen_sock = Sock::Socket();
    46. Sock::Bind(listen_sock, port);
    47. Sock::Listen(listen_sock);
    48. for(;;)
    49. {
    50. int sock = Sock::Accept(listen_sock);
    51. if(sock > 0)
    52. {
    53. pthread_t tid;
    54. int* parm = new int(sock);
    55. pthread_create(&tid, nullptr, HandlerHttpRequest, (void*)parm);
    56. }
    57. }
    58. return 0;
    59. }

    其他部分代码是不改变的,原来打开的网页内容时表单内容,现在我们给它重定向到qq官网中去。

    演示结果:

     302/307临时重定向

     对于Http.cc模块,我们只修改部分代码:

     HTTP常见的Header

    Content-Type: 数据类型(text/html等)
    Content-Length: Body的长度,帮助我们读取到完整的http请求or响应。同时,能够做到将报头和有效载荷进行分离(解包)。如果报头中没有该自描述字段,说明它就没有正文!
    Host: 客户端告知服务器, 所请求的资源是在哪个主机的哪个端口上;
    User-Agent: 声明用户的操作系统和浏览器版本信息;
    referer: 当前页面是从哪个页面跳转过来的;
    location: 搭配3xx状态码使用, 告诉客户端接下来要去哪里访问;
    Cookie: 用于在客户端存储少量信息. 通常用于实现会话(session)的功能

    Connect:keep-alive长链接, close表示短连接

    Set-Cookie:Key=value,服务端告诉客户端设置cookie文件来保存用户信息。

    Connect中长链接&&短连接

    http/1.0采用的网络请求的方案是短链接;http/1.1及以后采用的方案是长链接。

    短链接的过程:

           访问一个由多个元素构成的网页的时候,http/1.0,就需要多次进行http请求,http协议是基于tcp协议的,tcp要通信:建立链接 -> 传输数据 -> 断开链接

    对于大型网站而言,是需要进行很多次上面的请求过程,这是非常耗时的!

    长链接的过程:

    http/1.1支持长链接,通过长链接就可以通过一次链接把要访问的资源一起加载完成。

    好处:通过减少频繁建立tcp链接,来达到提高效率的目的!

    cookie && session

    经验:我们登录一次某个网站后,当我们进行各种页面跳转的时候,网站是认识我的,本质其实就是进行的各种http请求照样认识我!

    但是有一个很重要的概念:http协议是一种无状态的协议。(无状态协议的有点是简单)

            这时候就出现矛盾了,既然http是一种无状态的协议,那么每一次http请求对上下文是不记录的,不会获取历史请求信息,那么,为什么我们进行各种页面跳转的时候,网站竟然是认识我的?实际上这并不是http协议本身要解决的问题。而是http可以提供一些技术支持,来保证网站具有"会话保持"的功能。

    1、浏览器:cookie其实是一个文件,该文件里面保存的是我们用户的私密信息(往往是用户名和密码)。

    2、http协议:一旦该网站对应有cookie,再发起任何请求的时候,都会自动在request中携带该cookie信息!!

    单纯使用cookie带来的问题 

     如果别人盗取我们cookie文件,别人

    1、可以以我的身份进行认证访问特定的资源。

    2、如果保存的是我们用户名密码,那么就非常糟糕了!!!

    单纯使用cookie是具有一定的安全隐患的。

            所以又引入了session技术,注意,有session并不代表我们不用cookie,实际中,这两个技术是相辅相成的。

    session

    核心思路就是:将用户的私密信息,保存在服务器端。

    所以,在后续的http请求中,都会由浏览器自动携带cookie内容->当前用户的session id。

    后续,server依旧可以做到认识client,这是一种保持会话的功能!!

             但是,我们还有cookie文件被泄露的风险啊!!是的!!但是这是无法避免的,就好比这个世界上没有绝对的黑与白一样。网络安全工程师和黑客之间就是在不断地博弈过程中,相互进步。虽然被泄露的风险还存在,但是也有衍生出一些防御方案。

            例如:现在的许多网站或者qq、微信登录会自动检测当前IP,如果是异地登录,那么服务端就会清除session中对应的ID文件,并让用户再次认证。随着移动设备--手机的出现,出现了短信认证、新设备登录认证等等。所以,防止cookie文件不被盗取,就尽量不要点击不明链接,因为我们既然自己可以找到自己的cookie文件,那么别人通过恶意脚本程序也是可以找到的,这也是不安全的~

    本质上,cookie和session技术,是为了提高用户访问网站或者平台的体验!

    看到这里,给博主点个赞吧~

     

  • 相关阅读:
    DS线性表之链表
    每天一道算法题:46. 全排列
    Android 恢复出厂设置时间重置
    物联网TCP、UDP、CoAP、LwM2M、MQTT协议简单对比
    分布式事务
    同花顺_代码解析_技术指标_EJK
    牛客Mysql——SQL必知必会
    华为分享---手机往电脑发送失败的处理
    2309d用dmd重写dfmt
    Linux线程调度策略与优先级
  • 原文地址:https://blog.csdn.net/qq_58724706/article/details/127363263