网络http协议
Posted 华丞臧.
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了网络http协议相关的知识,希望对你有一定的参考价值。
🥁作者: 华丞臧.
📕专栏:【网络】
各位读者老爷如果觉得博主写的不错,请诸位多多支持(点赞+收藏+关注
)。如果有错误的地方,欢迎在评论区指出。
推荐一款刷题网站 👉 LeetCode刷题网站
文章目录
前言
在上一篇文章中我们见识了序列化和反序列化,其是处于应用层的,是进行网络通信双方进行一种约定;在网络发展至今,已经有了一些非常成熟的场景,并且有大佬自定义的一些协议,写的很好因此成为了应用层特定协议的标准,
一、http协议
虽然我们说,应用层协议是我们程序猿自己定的。但实际上,已经有大佬们定义了一些现成的,又非常好用的应用层协议,供我们直接参考使用,http(超文本传输协议)就是其中之一,底层主流采用的是tcp协议,http是无连接的。
1.1 认识URL
平时我们所说的网址就是URL。
服务器地址是域名,域名就是字符串类型的字段,域名在我们访问网网站的时候必须被转换称为IP地址,访问网络服务必须具有网络号即端口号,网络通信的本质就是socket,IP+port。
一般在使用确定协议的时候,再url上面会缺省端口号,在进行网络通信时端口号会被(浏览器)自动添加上,所以浏览器访问指定url时,浏览器必须给我们自动添加port,浏览器就必须知道这些端口,所以特定的众所周知的服务端口号都是并且必须是确定的。因此,在前面学习端口号时,我们说用户自己使用的套接字不能在0~1023。
http --> 80
https --> 443
sshd --> 22
http是做什么的
http是以网页的形式呈现的,比如说查阅文档、查看视频;所以http是获取网页资源的,视频、音频等也都是文件,而网页也是一个.html文件。
http是向特定服务器申请特定资源的,获取到本地进行展示或者某种使用的。
网页上的资源在该网页的网络服务器(软件)所在的服务器(硬件)上,而服务器都是Linux,资源文件存放在Linux服务器上;用户在网页上申请资源时,服务器需要根据路径找到该文件,所以URL中需要添加资源文件的路径。/
是Linux下的路径分隔符,但是需要注意这个/
不一定为根目录,因为软件层面可以做一些处理,比如在这个目录前添加一个路径。
urlencode和urldecode
像 / ? :等这样的字符,已经被url当做特殊意义理解了,因此这些字符不能随意出现。
比如, 某个参数中需要带有这些特殊字符,就必须先对特殊字符进行转义。在进行网络传输进行编码时,尤其是http中有些字段也会进行编码,服务端进行解码。编码是浏览器做的,解码是服务器做的。
转义的规则如下:
将需要转码的字符转为16进制,然后从右到左,取4位(不足4位直接处理),每2位做一位,前面加上%,编码成%XY格式。
“+” 被转义成了 “%2B”
urldecode就是urlencode的逆过程:
urlencode工具
网页的内容在开发者工具中可以看到,如下图:
1.2 http格式
http是基于行的协议。
http请求
- 首行:[方法] + [url] + [版本]
- Header:请求的属性, 冒号分割的键值对;每组属性之间使用\\n分隔;遇到空行表示Header部分结束 。
- Body:空行后面的内容都是Body,Body允许为空字符串。如果Body存在,则在Header中会有一个 Content-Length属性来标识Body的长度。
http响应
- 首行: [版本号] + [状态码] + [状态码解释]
- Header:请求的属性,冒号分割的键值对;每组属性之间使用\\n分隔;遇到空行表示Header部分结束。
- Body:空行后面的内容都是Body,Body允许为空字符串。如果Body存在, 则在Header中会有一个 Content-Length属性来标识Body的长度;如果服务器返回了一个html页面,那么html页面内容就是在 body中。
1.3 http的方法
方法 | 说明 | 支持的HTTP协议版本 |
---|---|---|
GET | 获取资源 | 1.0、1.1 |
POST | 传输实体主体 | 1.0、1.1 |
PUT | 传输文件 | 1.0、1.1 |
HEAD | 获取报文首部 | 1.0、1.1 |
DELETE | 删除文件 | 1.0、1.1 |
OPTIONS | 询问支持的方法 | 1.1 |
TRACE | 追踪路径 | 1.1 |
CONNECT | 要求用隧道协议链接代理 | 1.1 |
LINK | 建立和资源之间的联系 | 1.0 |
UNLINE | 断开连接关系 | 1.0 |
其中最常用的就是GET方法和POST方法.
1.4 http的状态码
类别 | 原因短语 | |
---|---|---|
1XX | Informational(信息状态码) | 接收的请求正在处理 |
2XX | Success(成功状态码) | 请求正常处理完毕 |
3XX | Redirection(重定向状态码) | 需要进行附加操作以完成请求 |
4XX | Client Error(客户端错误状态码) | 服务器无法处理请求 |
5XX | Server Error(服务器错误状态码) | 服务器处理请求出错 |
最常见的状态码, 比如 200(OK), 404(Not Found), 403(Forbidden), 302(Redirect, 重定向), 504(Bad Gateway)
1.5 http常见Header
- Content-Type: 数据类型(text/html等)
- Content-Length: Body的长度
- Host: 客户端告知服务器, 所请求的资源是在哪个主机的哪个端口上;
- User-Agent: 声明用户的操作系统和浏览器版本信息;
- referer: 当前页面是从哪个页面跳转过来的;
- location: 搭配3xx状态码使用, 告诉客户端接下来要去哪里访问;
- Cookie: 用于在客户端存储少量信息. 通常用于实现会话(session)的功能;
二、http服务器
2.1 最简单的http服务器
使用tcp协议的服务器端,在浏览器中可以连接该服务器,浏览器上默认使用http协议进行连接。在前几篇文章中已经编写了tcp套接字,这里只需要编写提供服务的代码即可。
#include <iostream>
#include <stdio.h>
#include <strings.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <pthread.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include<sys/types.h>
#include<sys/wait.h>
#include "Log.hpp"
using namespace std;
volatile bool quitSer = false;
void Usage(void *vgs)
std::cout << "Usage:./tcpserver port ip" << std::endl;
//提供服务
void Serverhttp(int sock)
char buffer[10240];
ssize_t s = read(sock, buffer, sizeof buffer);
cout << s << endl;
if(s > 0)
logMessage(DEBUG, "read success..");
std::cout << buffer;
class server
public:
server(int port, std::string ip = "")
: sockfd_(-1), ip_(ip), port_(port)
~server()
public:
void init()
// 1. 创建套接字
sockfd_ = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd_ < 0)
logMessage(FATAL, "socket:%s[%d]", strerror(errno), sockfd_);
exit(SOCK_ERR);
logMessage(DEBUG, "socket success..");
// 2.填充域
struct sockaddr_in local;
bzero(&local, sizeof(local));
local.sin_family = AF_INET;
local.sin_port = htons(port_);
ip_.empty() ? (local.sin_addr.s_addr = INADDR_ANY) : (inet_aton(ip_.c_str(), &local.sin_addr));
// 3. 绑定网络信息
if (bind(sockfd_, (const struct sockaddr *)&local, sizeof(local)) < 0)
logMessage(FATAL, "bind:%s[%d]", strerror(errno), sockfd_);
exit(BIND_ERR);
logMessage(DEBUG, "bind success...");
// 4. 监听套接字
if (listen(sockfd_, 5) < 0)
logMessage(FATAL, "listen:%s[%d]", strerror(errno), sockfd_);
exit(LISTEN_ERR);
logMessage(DEBUG, "listen success...");
// 完成
void start()
char inbuffer_[1024]; // 用来接收客户端发来的消息
// 提供服务
while (true)
quitSer = false;
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
// 5. 获取连接, accept 的返回值是一个新的socket fd
logMessage(DEBUG,"start1");
int serviceSock = accept(sockfd_, (struct sockaddr *)&peer, &len);
logMessage(DEBUG,"start2");
if (serviceSock < 0)
// 获取链接失败
logMessage(WARINING, "accept: %s[%d]", strerror(errno), serviceSock);
continue;
logMessage(DEBUG, "accept success");
pid_t id = fork();
if(id == 0)
pid_t tid = fork();
if(tid == 0)
uint16_t peerPort = htons(peer.sin_port);
std::string peerIp = inet_ntoa(peer.sin_addr);
Serverhttp(serviceSock);
exit(0);
exit(0);
close(serviceSock);
int ret = waitpid(id, nullptr, 0);
if(ret < 0)
logMessage(WARINING, "wait child error:%d", errno);
logMessage(DEBUG, "wait success...");
private:
int sockfd_;
uint16_t port_;
std::string ip_;
;
// ./tcpserver port ip
int main(int argc, char *argv[])
if (argc < 2 || argc > 3)
Usage(argv[0]);
exit(USAGE_ERR);
uint16_t port = atoi(argv[1]);
std::string ip;
if (argc == 3)
ip = argv[2];
std::cout << port << ":" << ip << std::endl;
server tcpSer(port, ip);
tcpSer.init();
tcpSer.start();
return 0;
服务端并没有提供服务,所以访问服务器时网页显示如下图:
在Linux服务端可以看到请求的相关属性,如下图:
2.2 简单的http服务器
服务器收到请求,提供服务向对应的套接字文件中写入一个“hello world”,注意响应的格式为首行+header+body,其中header和body之间需要空一行。
void Serverhttp(int sock)
char buffer[10240];
ssize_t s = read(sock, buffer, sizeof buffer);
cout << s << endl;
if(s > 0)
logMessage(DEBUG, "read success..");
std::cout << buffer;
//开始响应
string response;
response = "HTTP/1.0 200 OK\\r\\n"; //响应首行
response += "\\r\\n"; //空行
response += "hello world!!"; //正文
send(sock, response.c_str(), response.size(), 0);
服务器收到的请求报文如下图:
浏览器收到的响应如下图:
使用telnet
可以查看服务器给用户的响应。
//安装telnet
sudo yum install -y telnet
//使用格式
telnet ip port
//connected后
//出现escape character时使用ctrl+
//输入请求
2.3 html的http服务器
作为服务端,它并不关心html
的信息也不需要管html的格式是什么,服务器只负责网络请求并且把网页信息给用户推过去就行了,所以实际上在进行网站开发时,网页的资源都是通过html文件推送给用户的,请求的文件在请求行中,其第二个字段就是用户需要访问的文件,如下图:
其中/
并不是根目录,而是web根目录,可以设置成为根目录。
//获取资源路径
string getPath(string in)
size_t pos = in.find(CRLF);
string request = in.substr(0, pos);
cout << "request:" << request << endl;
// get path http/1.0
size_t first = request.find(SPACE); //第一个空格
cout << "first:" << first << endl;
if(first == string:: npos) return nullptr;
size_t second = request.rfind(SPACE); //第二个空格
cout << "second:" << second << endl;
if(second == string::npos) return nullptr;
string path = request.substr(first + SPACE_LEN, second- (first + SPACE_LEN));
if(path.size() == 1 && path[0] == '/') path += HOME_PAGE; //用户访问根目录,就返回主页
cout << "getPath:" << path << endl;
return path;
//获取资源
string readFile(string& recource)
ifstream in(recource);
if(!in.is_open()) return "404";
string content;
string line;
while(getline(in, line)) content += line;
return content;
//提供服务
void Serverhttp(int sock)
//std::cout << peerIp << ":" << peerPort << std::endl;
char buffer[10240];
ssize_t s = read(sock, buffer, sizeof buffer);
cout << s << endl;
if(s > 0)
logMessage(DEBUG, "read success..");
std::cout << buffer;
// 1. 获取路径
string path = getPath(buffer);
string recource = ROOT_PATH;
recource += path;
cout << recource << endl;
// 获取对应文件内容
string html = readFile(recource);
// 开始响应
string response;
response = "HTTP/1.0 200 OK\\r\\n";
response += "Content-Type:text/html\\r\\n";
response += ("Content-Length:" + std::to_string(html.size()) + "\\r\\n");
response += "\\r\\n";
//response += "<html><h1>hello world!!</h1><html>\\r\\n";
response += html;
send(sock, response.c_str(), response.size(), 0);
关于html的编写方法可以参考👉菜鸟教程
//index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"> //
<title>华丞臧(runoob.com)</title> //网页名称
</head>
<body> //正文
<h1>我的第一个标题</h1> //标题
<p>我的第一个段落。</p>
</body>
</html>
在浏览器中访问服务器可以看到网页显示如下图所示:
Liunx上收到的请求如下图,左为服务器收到的请求,右为客户端收到的响应。
2.4 表单
用户的网络行为无非有两种:
- 用户将自己的远端资源拿到本地:get /index.html http/1.1;
- 用户想将自己的属性字段提交到远端。
在浏览器上,网页通常会让用户注册一个账号,用来标识该用户,用户的存放在远端的资源也可以使用该账号标识,在html中这种端口称为表单。
get
在http中get会以明文方式将用户对应的参数信息拼接到url中。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>华丞臧(runoob.com)</title>
</head>
<body>
<h1>hello world</h1>
<p>我的第一个段落。</p>
<form action="html_form_action.php" method="get">
Username: <input type="text" name="user"><br>
Password: <input type="password" name="passwd"><br>
<input type="submit" value="Submit">
</form>
</body>
</html>
表单形式如下图所示:
post
在http中post会以明文方式将用户对应的参数信息拼接到正文中。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>华丞臧(runoob.com)</title>
</head>
<body>
<h1>hello world</h1>
<p>我的第一个段落。</p>
<form action="html_form_action.php" method="post">
Username: <input type="text" name="user"><br> //用户名,<br>标识换行
Password: <input type="password" name="passwd"><br> //密码
<input type="submit" value="Submit">
</form>
</body>
</html>
表单形式如下图所示:
在表单中填入用户名和密码后,可以看到url中并没有拼接用户参数,如下图:
使用progress telerik fiddler
可以查看用户发送的请求报文(Raw),可以看到用户参数被拼接到正文当中,如下图:
在服务端也可以收到用户发来的请求,如下图: