• Linux网络——HTTP


    一.应用层

    我们程序员写的一个个解决我们实际问题, 满足我们日常需求的网络程序, 都是在应用层.

    我们上一次写的网络版本计算器就是一个应用层的网络程序。

    我们约定了数据的读取,一端发送时构造的数据, 在另一端能够正确的进行解析, 就是ok的. 这种约定, 就是应用层协议。

    在应用层我们只负责将数据怎么读取,和怎么构造数据,然后将构造好的数据交给应用层的协议层传输层,再由传输层何其一下的网络协议栈来帮我们完成数据在网络中发送,至于数据在这之间是怎么发送的,什么时候发送的,我们不知道,不清楚,也不关心。

    二.认识URL

    平时我们俗称的 "网址" 其实就是说的 URL.

    1.域名

    服务器地址可以是一个IP地址,但是IP地址是点分十进制的字符串,为了方便记忆就有了和IP地址一 一对应的域名。

    例如:

    • 112.29.213.131 —— www.JD.com
    • 36.155.132.55   —— www.baidu.com

    2.urlencode和urldecode

    像 / ? : 等这样的字符, 已经被url当做特殊意义理解了. 因此这些字符不能随意出现.
    比如, 某个参数中需要带有这些特殊字符, 就必须先对特殊字符进行转义. 

    转义的规则如下:
    将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY格式,这个过程就是urlencode

     "+" 被转义成了 "%2B"。
    urldecode就是urlencode的逆过程。

    三.HTTP协议格式 

    1.请求格式http

    见一见http请求:

    部分测试代码:

    编写一个服务器,服务器多线程接受网络数据,使用浏览器在url栏框中输入服务端车程序的IP和端口,然后回车。此时浏览器就会构建一个http请求发送给我们的服务端程序,服务端程序直接将收到的数据直接以字符串的形式输出到终端。

    1. void serverIO(int fd)
    2. {
    3. string message;
    4. char buff[102400];
    5. // 读取一个完整的http请求报文
    6. int n = recv(fd, buff, sizeof(buff), 0);
    7. message = buff;
    8. // 直接将该请求以字符串的形式输出
    9. cout << "得到一个HTTP请求" << endl;
    10. cout << message << endl;
    11. close(fd);
    12. }

    得到的http请求:

    2.响应格式

    见一见http响应:

    使用telnet 向百度模拟发送一个http请求,接受返回的http响应。

    HTTP协议的格式大致有四部分组成:

    1. 首行:HTTP请求——[ 方法 ]+[ url ]+[ 版本 ];HTTP响应——[ 版本 ]+[ 状态码 ]+[ 描述 ];其中请求中url实际上就是服务器上的请求的资源路径。
    2. Header:请求/响应 的属性,冒号分割的键值对;每组属性之间使用\n分隔;遇到空行表示Header部分结束。例如:Content-Length:8899,标识 请求/响应 的有效载荷长度,有效载荷的长度是8899。
    3. 空行:用来分隔Header和Body。
    4. Body:就是有效载荷部分,空行后面的内容都是Body. Body允许为空字符串. 如果Body存在, 则在Header中会有一个Content-Length属性来标识Body的长度;

    四.HTTP响应状态码

    状态码类别描述
    1XX

    lnformational(信息性状态码)

    接收的请求正在处理

    2XX

    Success(成功状态码)

    请求正常处理完毕

    3XX

    Redirection(重定向状态码)

    需要进行附加操作以完成请求

    4XX

    Client Error(客户端错误状态码)

    服务器无法处理请求

    5XX

    Server Error(服务器错误状态码)

    服务器处理请求出错

    说明:

    Client Error:

    • 客户端连接的端口错了。
    • 客户端连接的域名或者IP地址错误。
    • 如果客户端使用了域名连接,域名可能指向了错误的服务器。
    • 当客户端发送了一个无效请求时,服务器可能会因为无法处理而返回 Client  Error。

    lnformational:

    • 主要作用是告知客户端请求已经被接受 。

    Redirection:

    • 是服务器在处理客户端请求时,为了保证流程的顺利进行而发出的一种信号。
    • 当客户端发送一个请求到服务器,服务器会根据需要将请求重定向到另一个URL。

     Success:

    • 表示客户端与服务器之间的通信已经成功完成,并且服务器已经处理了客户端的请求。

     Server Error:

    • 表示服务器在处理客户端请求时发生了错误。
    • 服务器遇到了一个未知的错误,无法完成请求的处理。
    • 服务器当前无法处理请求,因为过载或维护。

    五.HTTP常见Header

    Header是以K/V形式存储的字符串,主要是一些协议的属性说明等。

    1. Content-Type: 数据类型(text/html等)
    2. Content-Length: Body的长度
    3. Host: 客户端告知服务器, 所请求的资源是在哪个主机的哪个端口上;
    4. User-Agent: 声明用户的操作系统和浏览器版本信息;
    5. referer: 当前页面是从哪个页面跳转过来的;
    6. location: 搭配3xx(重定向)状态码使用, 告诉客户端接下来要去哪里访问;
    7. Cookie: 用于在客户端存储少量信息. 通常用于实现会话(session)的功能;

     介绍Content-Type:表明此次http报文的body传输的是什么内容。

    对于服务器上的每一种资源,都是以文件的形式存在的,文件都会有自己的类型,例如(网页文件).html,(音频文件).mp3,(图片).jpg/.png,(视频).mp4。

    每一种文件类型,都会有自己对应的Content-Type类型:

    • text/html:HTML 文档
    • text/css:CSS 样式表
    • text/javascript:JavaScript 脚本
    • image/jpeg:JPEG 图像
    • image/png:PNG 图像
    • image/gif:GIF 图像

    Content-Type 对照表

    六.简单的HTTP服务器

    编写一个服务器,多线程处理请求,对于请求的处理,解析一个http请求反序列化,根据http的请求的资源,构建响应并返回。

    Sock.hpp

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include "Log.hpp"
    11. #define TCP SOCK_STREAM
    12. #define UDP SOCK_DGRAM
    13. const static int backlog = 32;
    14. enum
    15. {
    16. SOCK_ERR = 10,
    17. BING_ERR,
    18. LISTEN_ERR,
    19. CONNECT_ERR
    20. };
    21. class Udp
    22. {
    23. public:
    24. Udp(int SOCK)
    25. {
    26. _listensock = socket(AF_INET, SOCK, 0);
    27. if (_listensock == -1)
    28. {
    29. Logmessage(Fatal, "socket err ,error code %d,%s", errno, strerror(errno));
    30. exit(SOCK_ERR);
    31. }
    32. }
    33. Udp(uint16_t port, int SOCK)
    34. : _port(port)
    35. {
    36. _listensock = socket(AF_INET, SOCK, 0);
    37. if (_listensock == -1)
    38. {
    39. Logmessage(Fatal, "socket err ,error code %d,%s", errno, strerror(errno));
    40. exit(10);
    41. }
    42. }
    43. void Bind()
    44. {
    45. struct sockaddr_in host;
    46. host.sin_family = AF_INET;
    47. host.sin_port = htons(_port);
    48. host.sin_addr.s_addr = INADDR_ANY; // #define INADDR_ANY 0x00000000
    49. socklen_t hostlen = sizeof(host);
    50. int n = bind(_listensock, (struct sockaddr *)&host, hostlen);
    51. if (n == -1)
    52. {
    53. Logmessage(Fatal, "bind err ,error code %d,%s", errno, strerror(errno));
    54. exit(BING_ERR);
    55. }
    56. }
    57. int FD()
    58. {
    59. return _listensock;
    60. }
    61. ~Udp()
    62. {
    63. close(_listensock);
    64. }
    65. protected:
    66. int _listensock;
    67. uint16_t _port;
    68. };
    69. class Tcp : public Udp
    70. {
    71. public:
    72. Tcp(uint16_t port)
    73. : Udp(port, TCP)
    74. {
    75. }
    76. Tcp()
    77. : Udp(TCP)
    78. {
    79. }
    80. void Listen()
    81. {
    82. int n = listen(_listensock, backlog);
    83. if (n == -1)
    84. {
    85. Logmessage(Fatal, "listen err ,error code %d,%s", errno, strerror(errno));
    86. exit(LISTEN_ERR);
    87. }
    88. }
    89. int Accept(string *clientip, uint16_t *clientport)
    90. {
    91. struct sockaddr_in client;
    92. socklen_t clientlen;
    93. int sock = accept(_listensock, (struct sockaddr *)&client, &clientlen);
    94. if (sock < 0)
    95. {
    96. Logmessage(Warning, "bind err ,error code %d,%s", errno, strerror(errno));
    97. }
    98. else
    99. {
    100. *clientip = inet_ntoa(client.sin_addr);
    101. *clientport = ntohs(client.sin_port);
    102. }
    103. return sock;
    104. }
    105. void Connect(string ip, uint16_t port)
    106. {
    107. struct sockaddr_in server;
    108. memset(&server, 0, sizeof(server));
    109. server.sin_family = AF_INET;
    110. server.sin_port = htons(port);
    111. server.sin_addr.s_addr = inet_addr(ip.c_str());
    112. socklen_t hostlen = sizeof(server);
    113. int n = connect(_listensock, (struct sockaddr *)&server, hostlen);
    114. if (n == -1)
    115. {
    116. Logmessage(Fatal, "Connect err ,error code %d,%s", errno, strerror(errno));
    117. exit(CONNECT_ERR);
    118. }
    119. }
    120. ~Tcp()
    121. {
    122. }
    123. };

    Server.hpp

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include "Sock.hpp"
    7. using fun_t = std::function<string(string &)>;
    8. class Httpserver;
    9. struct Args
    10. {
    11. Args(Httpserver *ser, string ip, uint16_t port, int fd)
    12. : _ip(ip), _port(port), _pserver(ser), _fd(fd)
    13. {
    14. }
    15. int _fd;
    16. uint16_t _port;
    17. string _ip;
    18. Httpserver *_pserver;
    19. };
    20. class Httpserver
    21. {
    22. public:
    23. Httpserver(fun_t func, uint16_t port)
    24. : _func(func)
    25. {
    26. tcp = new Tcp(port);
    27. tcp->Bind();
    28. tcp->Listen();
    29. cout << "服务器创建成功" << endl;
    30. }
    31. void start()
    32. {
    33. while (1)
    34. {
    35. string clientip;
    36. uint16_t clientport;
    37. cout << "start accept" << endl;
    38. int sock = tcp->Accept(&clientip, &clientport);
    39. cout << "get a new connect" << endl;
    40. // 多线程处理请求
    41. pthread_t t;
    42. Args *args = new Args(this, clientip, clientport, sock);
    43. pthread_create(&t, nullptr, ThreadRun, args);
    44. }
    45. }
    46. ~Httpserver()
    47. {
    48. delete tcp;
    49. }
    50. private:
    51. static void *ThreadRun(void *args)
    52. {
    53. pthread_detach(pthread_self());
    54. Args *ts = static_cast(args);
    55. ts->_pserver->serverIO(ts->_fd);
    56. delete ts;
    57. return nullptr;
    58. }
    59. void serverIO(int fd)
    60. {
    61. string message;
    62. char buff[102400];
    63. // 1.确信,读取一个完整的http请求报文
    64. int n = recv(fd, buff, sizeof(buff), 0);
    65. message = buff;
    66. string re = _func(message);
    67. send(fd, re.c_str(), re.length(), 0);
    68. close(fd);
    69. }
    70. private:
    71. Tcp *tcp;
    72. fun_t _func;
    73. };

    Server.cc

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include
    11. #include
    12. #include
    13. #include "Server.hpp"
    14. #include "util.hpp"
    15. const string wwwroot = "./html/3";
    16. const string defaupath = "/index.html";
    17. class HttpRequest
    18. {
    19. public:
    20. HttpRequest()
    21. {
    22. }
    23. ~HttpRequest() {}
    24. void Print()
    25. {
    26. cout << method_ << ":" << url_ << ":" << httpVersion_ << endl;
    27. for (auto &e : body_)
    28. cout << e << endl;
    29. cout << "suffix:" << suffix_ << endl;
    30. cout << "path:" << path_ << endl;
    31. }
    32. public:
    33. std::string method_; // 请求方法
    34. std::string url_; // 资源地址
    35. std::string httpVersion_; // 协议版本
    36. std::vector body_; // 报头
    37. std::string path_; // 请求资源路径
    38. std::string suffix_; // 资源类型
    39. };
    40. HttpRequest Deserialize(string &message)
    41. {
    42. HttpRequest req;
    43. // 1.拿到请求行
    44. string oneline = HeadOneLine(message, SEP);
    45. // 2.解析请求行
    46. DisposeOneLine(oneline, &req.method_, &req.url_, &req.httpVersion_);
    47. // 3.解析反序列化报头
    48. while (!message.empty())
    49. {
    50. string mes = HeadOneLine(message, SEP);
    51. req.body_.push_back(mes);
    52. }
    53. // 4.设置请求资源路径 url:/a/b/c
    54. if (req.url_[req.url_.length() - 1] == '/')
    55. {
    56. req.path_ = wwwroot + defaupath; //./html/index.html
    57. }
    58. else
    59. {
    60. req.path_ = wwwroot + req.url_; //./html/a/b/c
    61. }
    62. // 5.设置请求资源类型
    63. auto pos = req.url_.find('.');
    64. if (pos == string::npos)
    65. req.suffix_ = ".html";
    66. else
    67. req.suffix_ = req.url_.substr(pos);
    68. return req;
    69. }
    70. string Dispose(string &message)
    71. {
    72. // 这里我们一定读取的是一个完整的报文
    73. // 一个网页会有很多的资源,每一个资源(网页,图片,视频),都需要一次http请求来得到
    74. // 所以我们需要知道,每次请求的资源是什么,即需要知道请求的url是什么,url的类型是什么
    75. // 反序列化请求
    76. HttpRequest req = Deserialize(message);
    77. req.Print();
    78. // 响应————最简单的一个响应
    79. // 4.有效载荷
    80. string body = Readfile(req.path_) + SEP;
    81. // 1.响应的状态行————"HTTP版本 状态码 状态描述\r\n"
    82. string request_head = string("HTTP/1.0 200 OK") + SEP;
    83. // 2.响应报头————Content-Length,Content-Type
    84. request_head += string("Content-Length: ") + to_string(body.length()) + SEP;
    85. request_head += GetContentType(req.suffix_) + SEP;
    86. // 3.空行
    87. request_head += SEP;
    88. // 整体的响应报文
    89. string responce = request_head + body;
    90. return responce;
    91. }
    92. void daemonize()
    93. {
    94. // 1.忽略SIGPIPE信号
    95. signal(SIGPIPE, SIG_IGN);
    96. // 2.更改进程的工作目录
    97. // chdir();
    98. // 3.让自己不要成为进程组组长
    99. if (fork() > 0)
    100. exit(0);
    101. // 4.设置自己是一个独立的会话
    102. setsid();
    103. // 5.重定向0,1,2
    104. int fd = 0;
    105. if (fd = open("dev/null", O_RDWR) != -1)
    106. {
    107. dup2(fd, STDIN_FILENO);
    108. dup2(fd, STDOUT_FILENO);
    109. dup2(fd, STDERR_FILENO);
    110. // 6.关闭掉不需要的fd
    111. if (fd > STDERR_FILENO)
    112. {
    113. close(fd);
    114. }
    115. }
    116. }
    117. int main(int argc, char *argv[])
    118. {
    119. daemonize();
    120. uint16_t port = atoi(argv[1]);
    121. Httpserver httpser(Dispose, port);
    122. httpser.start();
    123. return 0;
    124. }

    Util.hpp

    1. #pragma once
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #define SEP "\r\n"
    10. using namespace std;
    11. string Readfile(const string path)
    12. {
    13. // 1. 获取文件本身的大小
    14. string fileContent;
    15. struct stat st;
    16. int n = stat(path.c_str(), &st);
    17. if (n < 0)
    18. return "";
    19. int size = st.st_size;
    20. // 2. 调整string的空间
    21. fileContent.resize(size);
    22. // 3. 读取
    23. int fd = open(path.c_str(), O_RDONLY);
    24. if (fd < 0)
    25. return "";
    26. read(fd, (char *)fileContent.c_str(), size);
    27. close(fd);
    28. return fileContent;
    29. }
    30. string HeadOneLine(string &message, const string &sep)
    31. {
    32. auto pos = message.find(sep, 0);
    33. if (pos == string::npos)
    34. return "";
    35. string oneline = message.substr(0, pos);
    36. message.erase(0, pos + sep.size());
    37. return oneline;
    38. }
    39. void DisposeOneLine(const string &oneline, string *method, string *url, string *httpVersion)
    40. {
    41. stringstream line(oneline);
    42. line >> *method >> *url >> *httpVersion;
    43. }
    44. string GetContentType(const string &suffix)
    45. {
    46. std::string content_type = "Content-Type: ";
    47. if (suffix == ".html" || suffix == ".htm")
    48. content_type + "text/html";
    49. else if (suffix == ".css")
    50. content_type += "text/css";
    51. else if (suffix == ".js")
    52. content_type += "application/x-javascript";
    53. else if (suffix == ".png")
    54. content_type += "image/png";
    55. else if (suffix == ".jpg")
    56. content_type += "image/jpeg";
    57. else
    58. {
    59. }
    60. return content_type;
    61. }

    效果展示:

    七.HTTP的方法

    HTTP的方法有很多但是最常用的只有两个:GET,POST。

    1.GET方法

    我们要促使浏览器使用不同的方法进行资源请求,提交,要使用html的表单。

    1. html>
    2. <html lang="en">
    3. <head>
    4. <meta charset="UTF-8">
    5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
    6. <title>CSDNtitle>
    7. head>
    8. <body>
    9. <h1>testh1>
    10. <form action="/a/b/c.exe" method="get">
    11. 姓名: <input type="text" name="myname" value=""><br />
    12. 密码: <input type="password" name="mypasswd"><br />
    13. <input type="submit" value="提交"><br />
    14. form>
    15. body>
    16. html>

    尝试提交数据:

    2.POST方法

    再次尝试提交数据:

     区别:

    1. GET能获取一个静态网页,GET也能提交参数,通过URL的方式提交参数。
    2. POST请求,提交的参数的时候,是通过正文的部分提交的参数。
    3. GET方法提交参数,不私密(没有不安全的说法),安全对HTTP来说本身就没有保障。
    4. POST提交参数比较私密一些。
    5. 由于GET使用url提交参数,所以大小一般会受限。
    6. POST使用正文提交参数,正文理论上可以非常大。
  • 相关阅读:
    SSM毕设项目 - 基于SSM的毕业设计管理系统(含源码+论文)
    解析Spring中的循环依赖问题:初探三级缓存
    netdata邮件告警配置
    解决rosbag播放‘[FATAL] [1662351033.122111074]: Expected INDEX_DATA record‘
    Spring 篇
    从 Python 程序中运行 PowerShell 脚本
    外刊30篇合集
    hashMap不同版本的区别
    denied: requested access to the resource is denied报错解决
    什么是粘包和拆包,Netty如何解决粘包拆包?
  • 原文地址:https://blog.csdn.net/qq_63943454/article/details/134428711