网络层是程序员接触最多的一个层级,应用层是层级体系中的最上层的一级,是我们做逻辑处理最多的。
- 应用层的功能
- 什么是url
- http协议
应用层的功能
是程序员写的一个一个解决的实际的问题都是在应用层,是做逻辑运算和业务处理的地点。
什么是url
统一资源定位符。
包括了协议名称://用户名:密码@服务器地址:服务器端口/资源路径/查询字符串/片段标识符
https://blog.csdn.net/boke_fengwei
比如上面的一个网站就是一个url。
https:协议名称
bolg.csdn.net:就是域名,对应的就是IP地址
boke_fengwei:资源路径。
在上面的url中是包含一些特殊字符的,此时需要做出特殊的处理,采用编和解码。这是为了防止数据的二义性。
采用urlencode编码
转移规则:将数据转为16进制,然后从右到左,取4位(不足四位的时候直接处理),没两位作以为,前面加上%,编码成%XY的形式即可。%就是标识这是一个转义的url,在对端的时候就可以进行解码。
有兴趣的童鞋可以看看urlencode和urldecode的实现函数
HTTP协议
HTTP的请求格式
请求行:
- 常用的请求方法
GET:获取资源
POST:传输实体主体
PUT:传输文件
HEAD:获得报文首部
OPTIONS:询问支持的方法
DELETE:删除文件
LINK:建立和资源之间的联系
最常用的就是GET和POST
GET所提交的内容在url中,POST体检的内容在请求体中。 - 协议版本
HTTP1.1默认使用长连接,可有效减少TCP的三次握手开销。 HTTP
1.1支持只发送header信息(不带任何body信息),如果服务器认为客户端有权限请求服务器,则返回100,否则返回401。客户端如果接受到100,才开始把请求body发送到服务器。这样当服务器返回401的时候,客户端就可以不用发送请求body了,节约了带宽。另外HTTP还支持传送内容的一部分。这样当客户端已经有一部分的资源后,只需要跟服务器请求另外的部分资源即可。这是支持文件断点续传的基础。
HTTP1.0是没有host域的,HTTP1.1才支持这个参数。
HTTP2.0使用多路复用技术(Multiplexing),多路复用允许同时通过单一的 HTTP/2 连接发起多重的请求-响应消息。
“HTTP1.1在同一时间对于同一个域名的请求数量有限制,超过限制就会阻塞请求”。多路复用底层采用"增加二进制分帧层"的方法,使得不改变原来的语义、首部字段的情况下提高传输性能,降低延迟。
二进制分帧将所有传输信息分割为更小的帧,用二进制进行编码,多个请求都在同一个TCP连接上完成,可以承载任意数量的双向数据流。HTTP/2更有效的使用TCP连接,得到性能上的提升。
请求头
- 常用的头部信息
(1)Content-Type:数据类型(text/html)等等
(2)Content-Length:Body的长度
(3)Host:客户端告知服务器,所请求的资源在哪个主机的那个端口上
(4)User-Agent:声明用户的操作系统和浏览器的版本信息
(5)referer:当前页面是从那个页面跳转过来的
(6)location:转发信息
(7)Cookie:用户客户端存储少量的信息
空行:“\r\n”空行是必不可少的,是区分请求头和请求体的一个重要标志
HTTP的响应格式
响应行
- 协议版本:和请求行中的协议版本一样
- 状态码:是表示我们请求之后返回的状态,标志我们在请求之后所造的事情
1XX:信息性状态码
2XX:成功状态码
3XX:重定向状态码
4XX:客户端错误状态码
5XX:服务器处理请求错误
常见的状态码和状态码解释
200 OK:标志我们访问成功
404 NOT FOUND:标志我们没有查找到内容
403 FORBIDDEN:标志着被静止
302 REDIRECT:重定向。
504 BADGATEWAY:网关坏了
响应头:和请求信息中是相似的
空行:空行的作用也是标志响应头的结束,区分响应头和响应体
响应体:响应之后返回的内容。
实现一个简单的HTTP的服务器。就是在在访问我们的IP地址的时候显示一个hello word的语句。
连接上我们之前实现的tcp的c++头文件
1 #include <string>
2 #include <stdio.h>
3 #include <unistd.h>
4 #include <stdlib.h>
5 #include <errno.h>
6 #include <string.h>
7 #include <netinet/in.h>
8 #include <arpa/inet.h>
9 #include <sys/socket.h>
10
11 #define CHECK_RET(q) if((q) == false){return -1;}
12
13 typedef struct calculator_info_t{
14 int num1;
15 int num2;
16 char op;
17 }calculator_info;
18
19 class TcpSocket
20 {
21 public:
22 TcpSocket() : _sockfd(-1){
23 }
24 void SetSockFd(int fd){
25 _sockfd = fd;
26 }
27 int GetSockFd(){
28 return _sockfd;
29 }
30 bool Socket() {
31 _sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
32 if (_sockfd < 0) {
33 perror("socket error");
34 return false;
35 }
36 return true;
37 }
38 bool Bind(std::string &ip, uint16_t port){
39 struct sockaddr_in addr;
40 addr.sin_family = AF_INET;
41 addr.sin_port = htons(port);
42 addr.sin_addr.s_addr = inet_addr(ip.c_str());
43
44 socklen_t len = sizeof(struct sockaddr_in);
45 int ret = bind(_sockfd, (struct sockaddr*)&addr, len);
46 if (ret < 0) {
47 perror("bind error");
48 return false;
49 }
50 return true;
51 }
52 bool Listen(int backlog = 10) {
53 //int listen(int sockfd, int backlog);
54 //backlog:最大并发连接数--内核中已完成连接队列的最大节点数
55 int ret = listen(_sockfd, backlog);
56 if (ret < 0) {
57 perror("listen error");
58 return false;
59 }
60 return true;
61 }
62 bool Connect(std::string &ip, uint16_t port) {
63 //int connect(int fd, struct sockaddr *addr,socklen_t len);
64 //addr: 要连接的服务端地址信息
65 struct sockaddr_in addr;
66 addr.sin_family = AF_INET;
67 addr.sin_port = htons(port);
68 addr.sin_addr.s_addr = inet_addr(ip.c_str());
69 socklen_t len = sizeof(struct sockaddr_in);
70
71 int ret = connect(_sockfd, (struct sockaddr*)&addr, len);
72 if (ret < 0) {
73 perror("connect error");
74 return false;
75 }
76 return true;
77 }
78 bool Accept(TcpSocket &csock, struct sockaddr_in *addr = NULL){
79 //int accept(int sockfd, sockaddr *addr, socklen_t *len);
80 //addr: 客户端的地址信息
81 //len: 输入输出参数,既要指定接收长度,还要接收实际长度
82 //返回值:为新客户端新建的socket套接字描述符
83 //通过这个返回的描述符可以与指定的客户端进行通信
84 struct sockaddr_in _addr;
85 socklen_t len = sizeof(struct sockaddr_in);
86 int newfd = accept(_sockfd, (struct sockaddr*)&_addr, &len);
87 if (newfd < 0) {
88 perror("accept error");
89 return false;
90 }
91 if (addr != NULL) {
92 memcpy(addr, &_addr, len);
93 }
94 csock.SetSockFd(newfd);
95 //_sockfd--仅用于接收新客户端连接请求
96 //newfd----专门用于与客户端进行通信
97 return true;
98 }
99 bool Recv(std::string &buf) {
100 char tmp[4096] = {0};
101 //ssize_t recv(int sockfd, void *buf, size_t len, int flags)
102 //flags:0-默认阻塞接收 MSG_PEEK-获取数据但是不从缓冲区移除
103 //返回值:实际接收的数据长度 失败:-1 连接断开:0
104 int ret = recv(_sockfd, tmp, 4096, 0);
105 if (ret < 0) {
106 perror("recv error");
107 return false;
108 }else if (ret == 0) {
109 printf("peer shutdown\n");
110 return false;
111 }
112 buf.assign(tmp, ret);
113 return true;
114 }
115 bool Send(std::string &buf) {
116 //ssize_t send(int sockfd, void *buf, size_t len, int flags)
117 int ret = send(_sockfd, buf.c_str(), buf.size(), 0);
118 if (ret < 0) {
119 perror("send error");
120 return false;
121 }
122 return true;
123 }
124 bool Close() {
125 close(_sockfd);
126 _sockfd = -1;
127 }
128 private:
129 int _sockfd;
130 };
实现cpp文件
1 #include "tcpsocket.hpp"
2 #include <iostream>
3 #include <sstream>
4 int main(int argc,char* argv[]){
5 if(argc != 3){
6 std::cout<<"./http_server ip port"<<std::endl;
7 return -1;
8 }
9 std::string ip = argv[1];
10 uint16_t port = atoi(argv[2]);
11 TcpSocket sock;
12 //1.创建套接字
13 CHECK_RET(sock.Socket());
14 //2.绑定连接
15 CHECK_RET(sock.Bind(ip,port));
16 //设置监听
17 CHECK_RET(sock.Listen(10));
18 //开始通信
19 while(1){
20 //首先创建一个sockaddr_in
21 struct sockaddr_in client;
22 TcpSocket cli;
23 if(sock.Accept(cli,&client) == false){
24 continue;
25 }
26 //接受连接请求
27 std::string buf;
28 cli.Recv(buf);
29 std::cout<<"接受到的数据["<<buf<<"]"<<std::endl;
30 //发送一个简单的页面请求
31 //使用一个数据流来格式化数据
32 std::string body;
33 body = "<html><body><h1>hello world</h1></body></html>";
34 std::stringstream ss;
35 ss << "http/1.1 200 OK\r\n";//响应行
36 ss << "Content_Length: "<<body.size()<<"\r\n";
37 ss << "Content_Type: text/html"<<"\r\n";
38 ss << "Location: http://www.baidu.com\r\n";
39 ss<<"\r\n";
40 std::string header = ss.str();
41 cli.Send(header);
42 cli.Send(body);
43 cli.Close();
44 }
45 sock.Close();
46 return 0;
47 }