目录
一.HTTP协议
1.认识URL
(1)域名->必须被转化成为IP
(2)URL中可以省略的部分
①端口号可缺省
②登录信息可以省略
③当我们访问自己的服务器时,https可省略,端口号不可省
(3)特定的服务 与 特定端口 的关系
(4)http协议是做什么的?
(5)如果我们client没有获取的时候,资源在网络服务器上
(6)资源文件在Linux服务器上
(7)Linux要如何找到这个文件呢?——通过路径!
2.urlencode和urldecode
二.http协议的请求格式
1.http协议的请求 分为三部分(我们这里以四部分解析)
(1)请求行
(2)请求报头
(3)空行
(4)有效载荷
<5>.请求实例
2.http响应
(1)响应行
(2)请求报头
(3)空行
(4)有效载荷
3.send 写入函数
4.telnet 命令——远程以协议方式登录某服务
(1)telnet请求服务器
(2)百度上请求服务器
三.http协议的响应,初步使用html
1.html
2.响应内容,使用html
(1)
(2)Content-Length 保证能读取到完整的正文
(3)把html和服务器工作解耦——readFile 要请求的资源
①文件在哪里? ——在请求的请求行中,第二个字段就是你要访问的文件 。
②GET /a/b/c.html http/1.0 中的 / 是web目录,不是根目录
四.表单
1.我们的网络行为有两种
2.GET方法
3.POST方法
4.GET Vs POST
5.代码
server.hpp
index.html
一.HTTP协议
1.认识URL
https://www.baidu.com/ https://www.qq.com/
(1)域名->必须被转化成为IP
因为网络通信的本质: socket, IP+ port,所以(服务器地址)域名->必须被转化成为IP;访问网络服务,服务端必须具有port
(2)URL中可以省略的部分
①端口号可缺省
使用确定协议的时候,一般显示的时候,会缺省端口号:
浏览器访问指定的url的时候,浏览器或app必须给我们自动添加port
浏览器如何得知,url匹配的port是谁呢?——特定的众所周知服务,端口号必须是确定的! !
httpserver—> 80
httpsServer—>443 sshd—> 22
用户自己写的网络服务bind端口的范围:[1024,n];因为前1023个是给httpserver这些服务的
②登录信息可以省略
登录信息我们一般放在页面上登录,一般不放在URL中,所以可以省略。
③当我们访问自己的服务器时,https可省略,端口号不可省
当我们访问自己的服务器时,只需要IP+port:120.78.126.148:8080
https可省略,因为默认会选择https协议;登录信息一般放在页面上登录也可以省略;端口号不可省因为服务是我们自己写的,并不是众所周知服务。
(3)特定的服务 与 特定端口 的关系
——>警察 与 110;抢救服务 与 120;火警灭火服务 与 119
(4)http协议是做什么的?
答:用于查阅文档,看音视频,这些都是以网页的形式呈现的。网页实际就是一个 .htmI文件
http用途:获取网页资源的,视频,音频等也都是文件!
解释:http是向特定的服务器申请特定的”资源”的,把资源获取到本地(本地可以是浏览器/app/迅雷播放器)进行展示或者某种使用的!
(5)如果我们client没有获取的时候,资源在网络服务器上
就在你的网络服务器(软件)所在的服务器(硬件,计算机)上
(6)资源文件在Linux服务器上
服务器都是Linux系统的,这些资源都是文件,即资源文件在Linux服务器上。要打开资源文件,读取和发送会给客户端——前提:软件服务器,必须先找到这个文件! !
(7)Linux要如何找到这个文件呢?——通过路径!
/ 就是Linux下的路径分隔符!
https://new.qq.com/rain/a720230106A0fRHW00
2.urlencode和urldecode
二.http协议的请求格式
1.http协议的请求 分为三部分(我们这里以四部分解析)
每行以 \r\n 结尾
(1)请求行
第一部分只有一行叫 请求行:包含了①请求方法 method。②url 一般省略了域名和端口,只有路径。③版本 http/1.1
注意:http协议请求时大小写是忽略的,例如请求行的 GET / HTTP/1.1 和get / http/1.1 都一样
(2)请求报头
第二部分包含多行内容叫 请求报头:每一行包含很多请求属性,都是KV形式的,例如 Key: value(注意:和value中间有空格)
(3)空行
第三部分只有一行叫 空行:因为只包含了一个 \r\n ,用与做分隔符,把报头和有效载荷分离
(4)有效载荷
第四部分只有一行叫 有效载荷:包含了请求正文:①登陆账号和密码。②个人信息/音频/视频等等。
注意:前三部分(请求行,请求报头,空行)都为http协议的报头;有效载荷就是个人信息
<5>.请求实例
我们利用百度向我们的服务器发起一个请求:
然后我们的服务器仅仅把收到的请求打印出来如下:
因为我们这次请求没有请求正文,所以不显示正文。行下面是第二次请求,因为浏览器是多线程请求,会发送多次请求;或者请求失败会继续发送请求,所以我们会受到多次请求。
DEBUG| 16733361461 whb | accept: Invalid argument | 125. 76.203.191[5129],socket fd: 4
125.76.203.191: 5129
GET / HTTP/1.1
——GET:请求方法;/:是请求的资源。HTTP/1.1:(浏览器)版本
Host: 120. 78.126.148: 8080
——要请求哪个主机,这个主机的IP和port
Connection: keep-alive
——链接方式, keep-alive长链接
Cache- Control: max-age=0
——#cache缓存-暂时不管
Upgrade- Insecure- Requests: 1
——#协议升级-暂时不管
User- Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.3
——User- Agent浏览器版本
Accept: text/html,application/xhtml+xml,application/ xml;q=0.9,image/avif,image/webp , image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
——上面这个是可接受的资源种类
Accept- Encoding: gzip, deflate
——可接受的编码方式
Accept-Language: zh-CN, zh;q=0.9
——可接受的语言类型
2.http响应
也是每行以 \r\n 结尾
(1)响应行
第一部分只有一行叫 响应行:包含了①版本 http/1.1。②状态码。(例如404报错,200代表OK)③状态码描述。(例如404对应的“Not Found”描述)
(2)请求报头
第二部分包含多行内容叫 响应报头:每一行包含很多响应属性,都是KV形式的,例如 Key: value(注意:和value中间有空格)
(3)空行
第三部分只有一行叫 空行:因为只包含了一个 \r\n ,用与做分隔符,把报头和有效载荷分离
(4)有效载荷
第四部分只有一行叫 有效载荷:包含了响应正文:①htm/css/js/图片视频音频,自定义信息等——资源
http协议构建一个请求,响应
3.send 写入函数
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
把缓冲区buf中的len个长度数据写入 sockfd这个文件中,flags设为0,send和write函数等价
返回值:返回实际写入的字节数,错误返回-1错误码被设置
4.telnet 命令——远程以协议方式登录某服务
我们的服务器的响应内容:
(1)telnet请求服务器
先把./serverTcp 8080把服务器起来,然后telnet 127.0.0.1 8080,再 ctrl+],输入请求 GET / http/1. 0 ,就可以得到服务器的响应信息
(2)百度上请求服务器
IP+端口,就能得到响应
三.http协议的响应,初步使用html
1.html
HTML 教程 | 菜鸟教程 (runoob.com)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>菜鸟教程(runoob.com)</title>
</head>
<body>
<h1>我的第一个标题</h1>
<p>我的第一个段落。</p>
</body>
</html>
2.响应内容,使用html
示例:
HTTP/1.0 200 OK\r\n
Content-Type 标定正文的类型
(1)
解释下面:
——"HTTP/1.0 200 OK\r\n"; HTTP/1.0 版本,200状态码表示通过,OK状态码描述
——"Content-Type: text/html\r\n" ; Content-Type内容类型,正文的类型是html文本类型。(text-文本类型)
——"\r\n"; 这是空行
——"<html><h1>hello bite</h1></html>\r\n"; 正文内容是hello bite,<html>……</html>是html网页的格式,<h1>……</h1>是使正文成为大标题。(可以搜索html教程学习)
(2)Content-Length 保证能读取到完整的正文
任何协议的request or response:
报头+有效载荷
①http如何保证自己的报头和有效载荷被全部读取呢?
——无论是请求还是响应,读取完整报头:按行读取,直到读取到空行上
②你又如何保证 你能读取到完整的正文呢? ?
——报头能读取完毕,请求或者响应属性中”一定”要包含正文的长度!
response += ("Content-Length: " + std::to_string(html.size()) + "\r\n");
(3)把html和服务器工作解耦——readFile 要请求的资源
我要把特定的资源放到特定的目录下的文件中
①文件在哪里? ——在请求的请求行中,第二个字段就是你要访问的文件 。
例如:请求行:GET /a/b/c.html http/1.0 ,/a/b/c.html就是要访问的文件
②GET /a/b/c.html http/1.0 中的 / 是web目录,不是根目录
a前面的 / 不是根目录, web根目录,但可以设置成为根目录
path = "/a/b/index.html"; ——请求的人请求的文件路径
resource = "./wwwroot"; // 我们的web根目录,我们服务器内部给请求的路径自动加上前缀
resource += path; // ——> ./wwwroot/a/b/index.html
四.表单
1.我们的网络行为有两种
(1)我想把远端的资源拿到你的本地: GET /index.html http/1.1
(2)我们想把我们的属性字段,提交到远端,
提交到远端的两种方法:GET or POST
在HTTP中GET会以明文方式将我们对应的参数信息,拼接到url中
2.GET方法
在HTTP中GET会以明文方式将我们对应的参数信息,拼接到url中
<form action="/a/b/c.html" method="get">
——action文件路径; method打开方法
Username: <input type="text" name="user"><br>
——input type类型;"user"是KV中的K值;<br>是换行
Password: <input type="password" name="passwd"><br>
——input type类型是password类型;"passwd"是KV中的K值
<input type="submit" value="Submit">
——input type类型是"submit"类型;"Submit"是KV中的K值
</form>
3.POST方法
POST方法提交参数,会将参数以明文的方式,拼接到http的正文中来进行提交!
4.GET Vs POST
1. GET通过url传参
2. POST通过正文传参
3. GET方法传参不私密
4. POST方法因为通过正文传参,所以,相对比较私密- -些
5. GET通过url传参,POST通过正文传参,所以- -般- 些比较大的内容都是通过post方式传参的
5.代码
server.hpp
#pragma once
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <cstdio>
#include <cstring>
#include <signal.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <pthread.h>
#include <cerrno>
#include <cassert>
#define CRLF "\r\n"
#define SPACE " "
#define SPACE_LEN strlen(SPACE)
#define HOME_PAGE "index.html"
#define ROOT_PATH "wwwroot"
using namespace std;
std::string getPath(std::string http_request)
{
std::size_t pos = http_request.find(CRLF);
if(pos == std::string::npos) return "";
std::string request_line = http_request.substr(0, pos);
//GET /a/b/c http/1.1
std::size_t first = request_line.find(SPACE);
if(pos == std::string::npos) return "";
std::size_t second = request_line.rfind(SPACE);
if(pos == std::string::npos) return "";
std::string path = request_line.substr(first+SPACE_LEN, second - (first+SPACE_LEN));
if(path.size() == 1 && path[0] == '/') path += HOME_PAGE;
若客户端请求的只有一个web根目录,web根目录下那么多资源,不可能全给客户。
所以我们需要加上 index.html,让他只能访问到首页。
return path;
}
std::string readFile(const std::string &recource)
{
std::ifstream in(recource, std::ifstream::binary);
if(!in.is_open()) return "404";
std::string content;
std::string line;
while(std::getline(in, line)) content += line;
in.close();
return content;
}
void handlerHttpRequest(int sock)
{
char buffer[10240];
ssize_t s = read(sock, buffer, sizeof buffer);
if(s > 0) cout << buffer;
std::string path = getPath(buffer); 从客户端请求的请求行中拿出请求的文件路径
// path = "/a/b/index.html";
// recource = "./wwwroot"; // 我们的web根目录
// recource += path; // ./wwwroot/a/b/index.html
// 1. 文件在哪里? 在请求的请求行中,第二个字段就是你要访问的文件
// 2. 如何读取
std::string recource = ROOT_PATH;
recource += path; 给客户端请求的文件路径加上web根目录
std::cout << recource << std::endl;
std::string html = readFile(recource); 读取recource这个路径对应文件中的内容
std::size_t pos = recource.rfind(".");
std::string suffix = recource.substr(pos);
cout << suffix << endl;
//开始响应
std::string response;
response = "HTTP/1.0 200 OK\r\n"; 下面的if:若是图片,添加图片对应的文件后缀
if(suffix == ".jpg") response += "Content-Type: image/jpeg\r\n";
else response += "Content-Type: text/html\r\n";
response += ("Content-Length: " + std::to_string(html.size()) + "\r\n");
response += "\r\n";
response += html;
send(sock, response.c_str(), response.size(), 0);
}
class ServerTcp
{
public:
ServerTcp(uint16_t port, const std::string &ip = "")
: port_(port),
ip_(ip),
listenSock_(-1)
{
quit_ = false;
}
~ServerTcp()
{
if (listenSock_ >= 0)
close(listenSock_);
}
public:
void init()
{
// 1. 创建socket
listenSock_ = socket(PF_INET, SOCK_STREAM, 0);
if (listenSock_ < 0)
{
exit(1);
}
// 2. bind绑定
// 2.1 填充服务器信息
struct sockaddr_in local; // 用户栈
memset(&local, 0, sizeof local);
local.sin_family = PF_INET;
local.sin_port = htons(port_);
ip_.empty() ? (local.sin_addr.s_addr = INADDR_ANY) : (inet_aton(ip_.c_str(), &local.sin_addr));
// 2.2 本地socket信息,写入sock_对应的内核区域
if (bind(listenSock_, (const struct sockaddr *)&local, sizeof local) < 0)
{
exit(2);
}
// 3. 监听socket,为何要监听呢?tcp是面向连接的!
if (listen(listenSock_, 5 /*后面再说*/) < 0)
{
exit(3);
}
// 运行别人来连接你了
}
void loop()
{
signal(SIGCHLD, SIG_IGN); // only Linux
while (!quit_)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int serviceSock = accept(listenSock_, (struct sockaddr *)&peer, &len);
if (quit_)
break;
if (serviceSock < 0)
{
// 获取链接失败
cerr << "accept error ...." << endl;
continue;
}
// 5.1 v1 版本 -- 多进程版本 -- 父进程打开的文件会被子进程继承吗?会的
pid_t id = fork();
assert(id != -1);
if(id == 0)
{
close(listenSock_); //建议
if(fork() > 0) exit(0);
//孙子进程
handlerHttpRequest(serviceSock);
exit(0); // 进入僵尸
}
close(serviceSock);
wait(nullptr);
}
}
bool quitServer()
{
quit_ = true;
return true;
}
private:
// sock
int listenSock_;
// port
uint16_t port_;
// ip
std::string ip_;
// 安全退出
bool quit_;
};
index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>104 期测试</title>
</head>
<body>
<h3>hello my server!</h3>
<p>我终于测试完了我的代码</p>
<form action="/a/b/c.html" method="post">
Username: <input type="text" name="user"><br>
Password: <input type="password" name="passwd"><br>
<input type="submit" value="Submit">
</form>
<!-- <img border="0" src="https://img1.baidu.com/it/u=1691233364,820181697&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500" alt="Pulpit rock" width="304" height="228"> -->
</body>
</html>