我们程序员写的一个个解决我们实际问题, 满足我们日常需求的网络程序, 都是在应用层.
我们上一次写的网络版本计算器就是一个应用层的网络程序。
我们约定了数据的读取,一端发送时构造的数据, 在另一端能够正确的进行解析, 就是ok的. 这种约定, 就是应用层协议。
在应用层我们只负责将数据怎么读取,和怎么构造数据,然后将构造好的数据交给应用层的协议层传输层,再由传输层何其一下的网络协议栈来帮我们完成数据在网络中发送,至于数据在这之间是怎么发送的,什么时候发送的,我们不知道,不清楚,也不关心。
平时我们俗称的 "网址" 其实就是说的 URL.

服务器地址可以是一个IP地址,但是IP地址是点分十进制的字符串,为了方便记忆就有了和IP地址一 一对应的域名。
例如:
像 / ? : 等这样的字符, 已经被url当做特殊意义理解了. 因此这些字符不能随意出现.
比如, 某个参数中需要带有这些特殊字符, 就必须先对特殊字符进行转义.
转义的规则如下:
将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY格式,这个过程就是urlencode。

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

见一见http请求:
部分测试代码:
编写一个服务器,服务器多线程接受网络数据,使用浏览器在url栏框中输入服务端车程序的IP和端口,然后回车。此时浏览器就会构建一个http请求发送给我们的服务端程序,服务端程序直接将收到的数据直接以字符串的形式输出到终端。
- void serverIO(int fd)
- {
- string message;
- char buff[102400];
- // 读取一个完整的http请求报文
- int n = recv(fd, buff, sizeof(buff), 0);
- message = buff;
- // 直接将该请求以字符串的形式输出
- cout << "得到一个HTTP请求" << endl;
- cout << message << endl;
- close(fd);
- }
得到的http请求:


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

HTTP协议的格式大致有四部分组成:
| 状态码 | 类别 | 描述 |
| 1XX | lnformational(信息性状态码) | 接收的请求正在处理 |
| 2XX | Success(成功状态码) | 请求正常处理完毕 |
| 3XX | Redirection(重定向状态码) | 需要进行附加操作以完成请求 |
| 4XX | Client Error(客户端错误状态码) | 服务器无法处理请求 |
| 5XX | Server Error(服务器错误状态码) | 服务器处理请求出错 |
说明:
Client Error:
lnformational:
Redirection:
Success:
Server Error:
Header是以K/V形式存储的字符串,主要是一些协议的属性说明等。
介绍Content-Type:表明此次http报文的body传输的是什么内容。
对于服务器上的每一种资源,都是以文件的形式存在的,文件都会有自己的类型,例如(网页文件).html,(音频文件).mp3,(图片).jpg/.png,(视频).mp4。
每一种文件类型,都会有自己对应的Content-Type类型:
编写一个服务器,多线程处理请求,对于请求的处理,解析一个http请求反序列化,根据http的请求的资源,构建响应并返回。
Sock.hpp
- #pragma once
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include "Log.hpp"
- #define TCP SOCK_STREAM
- #define UDP SOCK_DGRAM
- const static int backlog = 32;
-
- enum
- {
- SOCK_ERR = 10,
- BING_ERR,
- LISTEN_ERR,
- CONNECT_ERR
- };
-
- class Udp
- {
- public:
- Udp(int SOCK)
- {
- _listensock = socket(AF_INET, SOCK, 0);
- if (_listensock == -1)
- {
- Logmessage(Fatal, "socket err ,error code %d,%s", errno, strerror(errno));
- exit(SOCK_ERR);
- }
- }
- Udp(uint16_t port, int SOCK)
- : _port(port)
- {
- _listensock = socket(AF_INET, SOCK, 0);
- if (_listensock == -1)
- {
- Logmessage(Fatal, "socket err ,error code %d,%s", errno, strerror(errno));
- exit(10);
- }
- }
-
- void Bind()
- {
- struct sockaddr_in host;
- host.sin_family = AF_INET;
- host.sin_port = htons(_port);
- host.sin_addr.s_addr = INADDR_ANY; // #define INADDR_ANY 0x00000000
- socklen_t hostlen = sizeof(host);
- int n = bind(_listensock, (struct sockaddr *)&host, hostlen);
- if (n == -1)
- {
- Logmessage(Fatal, "bind err ,error code %d,%s", errno, strerror(errno));
- exit(BING_ERR);
- }
- }
-
- int FD()
- {
- return _listensock;
- }
- ~Udp()
- {
- close(_listensock);
- }
-
- protected:
- int _listensock;
- uint16_t _port;
- };
-
- class Tcp : public Udp
- {
- public:
- Tcp(uint16_t port)
- : Udp(port, TCP)
- {
- }
-
- Tcp()
- : Udp(TCP)
- {
- }
-
- void Listen()
- {
- int n = listen(_listensock, backlog);
- if (n == -1)
- {
- Logmessage(Fatal, "listen err ,error code %d,%s", errno, strerror(errno));
- exit(LISTEN_ERR);
- }
- }
-
- int Accept(string *clientip, uint16_t *clientport)
- {
- struct sockaddr_in client;
- socklen_t clientlen;
- int sock = accept(_listensock, (struct sockaddr *)&client, &clientlen);
- if (sock < 0)
- {
- Logmessage(Warning, "bind err ,error code %d,%s", errno, strerror(errno));
- }
- else
- {
- *clientip = inet_ntoa(client.sin_addr);
- *clientport = ntohs(client.sin_port);
- }
- return sock;
- }
-
- void Connect(string ip, uint16_t port)
- {
- struct sockaddr_in server;
- memset(&server, 0, sizeof(server));
- server.sin_family = AF_INET;
- server.sin_port = htons(port);
- server.sin_addr.s_addr = inet_addr(ip.c_str());
- socklen_t hostlen = sizeof(server);
- int n = connect(_listensock, (struct sockaddr *)&server, hostlen);
- if (n == -1)
- {
- Logmessage(Fatal, "Connect err ,error code %d,%s", errno, strerror(errno));
- exit(CONNECT_ERR);
- }
- }
-
- ~Tcp()
- {
- }
- };
Server.hpp
- #pragma once
- #include
- #include
- #include
- #include
- #include "Sock.hpp"
-
- using fun_t = std::function<string(string &)>;
- class Httpserver;
- struct Args
- {
- Args(Httpserver *ser, string ip, uint16_t port, int fd)
- : _ip(ip), _port(port), _pserver(ser), _fd(fd)
- {
- }
- int _fd;
- uint16_t _port;
- string _ip;
- Httpserver *_pserver;
- };
-
- class Httpserver
- {
- public:
- Httpserver(fun_t func, uint16_t port)
- : _func(func)
- {
- tcp = new Tcp(port);
- tcp->Bind();
- tcp->Listen();
- cout << "服务器创建成功" << endl;
- }
- void start()
- {
- while (1)
- {
- string clientip;
- uint16_t clientport;
- cout << "start accept" << endl;
- int sock = tcp->Accept(&clientip, &clientport);
- cout << "get a new connect" << endl;
- // 多线程处理请求
- pthread_t t;
- Args *args = new Args(this, clientip, clientport, sock);
- pthread_create(&t, nullptr, ThreadRun, args);
- }
- }
-
- ~Httpserver()
- {
- delete tcp;
- }
-
- private:
- static void *ThreadRun(void *args)
- {
- pthread_detach(pthread_self());
- Args *ts = static_cast
(args); - ts->_pserver->serverIO(ts->_fd);
- delete ts;
- return nullptr;
- }
-
- void serverIO(int fd)
- {
-
- string message;
- char buff[102400];
- // 1.确信,读取一个完整的http请求报文
- int n = recv(fd, buff, sizeof(buff), 0);
- message = buff;
-
- string re = _func(message);
- send(fd, re.c_str(), re.length(), 0);
- close(fd);
- }
-
- private:
- Tcp *tcp;
- fun_t _func;
- };
Server.cc
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include "Server.hpp"
- #include "util.hpp"
-
- const string wwwroot = "./html/3";
- const string defaupath = "/index.html";
-
- class HttpRequest
- {
- public:
- HttpRequest()
- {
- }
-
- ~HttpRequest() {}
- void Print()
- {
- cout << method_ << ":" << url_ << ":" << httpVersion_ << endl;
-
- for (auto &e : body_)
- cout << e << endl;
- cout << "suffix:" << suffix_ << endl;
- cout << "path:" << path_ << endl;
- }
-
- public:
- std::string method_; // 请求方法
- std::string url_; // 资源地址
- std::string httpVersion_; // 协议版本
- std::vector
body_; // 报头 -
- std::string path_; // 请求资源路径
- std::string suffix_; // 资源类型
- };
-
- HttpRequest Deserialize(string &message)
- {
- HttpRequest req;
- // 1.拿到请求行
- string oneline = HeadOneLine(message, SEP);
- // 2.解析请求行
- DisposeOneLine(oneline, &req.method_, &req.url_, &req.httpVersion_);
- // 3.解析反序列化报头
- while (!message.empty())
- {
- string mes = HeadOneLine(message, SEP);
- req.body_.push_back(mes);
- }
- // 4.设置请求资源路径 url:/a/b/c
- if (req.url_[req.url_.length() - 1] == '/')
- {
- req.path_ = wwwroot + defaupath; //./html/index.html
- }
- else
- {
- req.path_ = wwwroot + req.url_; //./html/a/b/c
- }
-
- // 5.设置请求资源类型
- auto pos = req.url_.find('.');
- if (pos == string::npos)
- req.suffix_ = ".html";
- else
- req.suffix_ = req.url_.substr(pos);
-
- return req;
- }
-
- string Dispose(string &message)
- {
- // 这里我们一定读取的是一个完整的报文
-
- // 一个网页会有很多的资源,每一个资源(网页,图片,视频),都需要一次http请求来得到
- // 所以我们需要知道,每次请求的资源是什么,即需要知道请求的url是什么,url的类型是什么
- // 反序列化请求
- HttpRequest req = Deserialize(message);
- req.Print();
- // 响应————最简单的一个响应
- // 4.有效载荷
- string body = Readfile(req.path_) + SEP;
- // 1.响应的状态行————"HTTP版本 状态码 状态描述\r\n"
- string request_head = string("HTTP/1.0 200 OK") + SEP;
- // 2.响应报头————Content-Length,Content-Type
- request_head += string("Content-Length: ") + to_string(body.length()) + SEP;
- request_head += GetContentType(req.suffix_) + SEP;
- // 3.空行
- request_head += SEP;
- // 整体的响应报文
- string responce = request_head + body;
- return responce;
- }
-
- void daemonize()
- {
- // 1.忽略SIGPIPE信号
- signal(SIGPIPE, SIG_IGN);
- // 2.更改进程的工作目录
- // chdir();
-
- // 3.让自己不要成为进程组组长
- if (fork() > 0)
- exit(0);
- // 4.设置自己是一个独立的会话
- setsid();
- // 5.重定向0,1,2
- int fd = 0;
- if (fd = open("dev/null", O_RDWR) != -1)
- {
- dup2(fd, STDIN_FILENO);
- dup2(fd, STDOUT_FILENO);
- dup2(fd, STDERR_FILENO);
- // 6.关闭掉不需要的fd
- if (fd > STDERR_FILENO)
- {
- close(fd);
- }
- }
- }
-
- int main(int argc, char *argv[])
- {
- daemonize();
- uint16_t port = atoi(argv[1]);
- Httpserver httpser(Dispose, port);
- httpser.start();
-
- return 0;
- }
Util.hpp
- #pragma once
- #include
- #include
- #include
- #include
- #include
- #include
- #include
-
- #define SEP "\r\n"
- using namespace std;
-
- string Readfile(const string path)
- {
- // 1. 获取文件本身的大小
- string fileContent;
- struct stat st;
- int n = stat(path.c_str(), &st);
- if (n < 0)
- return "";
- int size = st.st_size;
- // 2. 调整string的空间
- fileContent.resize(size);
- // 3. 读取
- int fd = open(path.c_str(), O_RDONLY);
- if (fd < 0)
- return "";
- read(fd, (char *)fileContent.c_str(), size);
- close(fd);
- return fileContent;
- }
-
- string HeadOneLine(string &message, const string &sep)
- {
- auto pos = message.find(sep, 0);
- if (pos == string::npos)
- return "";
- string oneline = message.substr(0, pos);
- message.erase(0, pos + sep.size());
- return oneline;
- }
-
- void DisposeOneLine(const string &oneline, string *method, string *url, string *httpVersion)
- {
- stringstream line(oneline);
- line >> *method >> *url >> *httpVersion;
- }
-
- string GetContentType(const string &suffix)
- {
- std::string content_type = "Content-Type: ";
- if (suffix == ".html" || suffix == ".htm")
- content_type + "text/html";
- else if (suffix == ".css")
- content_type += "text/css";
- else if (suffix == ".js")
- content_type += "application/x-javascript";
- else if (suffix == ".png")
- content_type += "image/png";
- else if (suffix == ".jpg")
- content_type += "image/jpeg";
- else
- {
- }
- return content_type;
- }
效果展示:


HTTP的方法有很多但是最常用的只有两个:GET,POST。
我们要促使浏览器使用不同的方法进行资源请求,提交,要使用html的表单。
- html>
- <html lang="en">
-
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>CSDNtitle>
- head>
-
- <body>
-
- <h1>testh1>
- <form action="/a/b/c.exe" method="get">
- 姓名: <input type="text" name="myname" value=""><br />
- 密码: <input type="password" name="mypasswd"><br />
- <input type="submit" value="提交"><br />
- form>
-
- body>
-
- html>

尝试提交数据:



再次尝试提交数据:

区别: