文章目录
前言
虽然说应用层协议是我们程序猿自己来定的!!!
但实际上,已经有大佬们定义了一些现成的,又非常好用的应用层协议,可以供我们直接参考使用.HTTP(超文本传输协议)就是其中的一个.
正文开始!
一、认识URL
但是在我们现在使用中,端口号大多都被省略了!
如下:
这是因为在使用确定协议的时候,一般显示的时候,会缺省端口号(有端口号被省略了).
所以,浏览器访问指定的URL的时候,浏览器必须给我们自动添加port;
浏览器如何得知,URL匹配的port是谁呢? 特定的众所周知的服务,端口号必须是确定的!!!
http->80
https->443
sshd->22
注意:
我们自己写的网络服务bind端口的时候,只能绑定[1024,n]范围内的端口号.
http就是获取网页资源的,视频,音频等的也都是文件!!!也就是向特定的服务器申请特定的"资源",然后获取到本地,进行展示和某种使用的!
如果client没有获取的时候,资源在哪里呢?—>远端的服务器!!!—>服务器都是Linux系统!—>这些资源都是文件!—>通过路径来访问文件!
urlencode和urldecode
像 / ? : 等这样的字符,已经被url当做特殊意义理解了.因此这些字符不能随意出现.
比如,某个参数中需要带有这些字符,就必须先对特殊字符进行转义.
转义的规则如下:将需要转码的字符转为16进制,然后从左到右,取4位(不足4位直接处理),每2位做1位,前面加上%,编码成%XY的格式.
例如:
“+“就被转义为”%2B”.
urldecode就是urlencode的逆过程.
二、http协议格式
任何协议的request or response:
报头+有效载荷
那么http如何保证自己的报头和有效载荷被全部读取呢?----无论是请求还是响应.
- 读取完整报头:按行读取,直到读取到空行!
- 你又如何保证,你能读取到完整的正文呢?(报头能读取完毕,请求或者响应属性中,"一定"要包含正文的长度!)
server.hpp
#include<iostream>
#include<string>
#include<cassert>
#include<unistd.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<sys/socket.h>
#include<string.h>
#include<arpa/inet.h>
#include<fstream>
using namespace std;
#define CRLF "\r\n"
#define SPACE " "
#define SPACE_LEN strlen(SPACE)
#define HOME_PAGE "index.html" //首页字段
#define ROOT_PATH "wwwroot"
string GetPath(string httpRequest)
{
size_t pos = httpRequest.find(CRLF);
if(pos == string::npos) return "";
string requestLine = httpRequest.substr(0,pos);
//GET /a/b/index.html http/1.0
size_t first = requestLine.find(SPACE);
if(first == string::npos) return "";
size_t second = requestLine.rfind(SPACE);
if(second == string::npos) return "";
string path = requestLine.substr(first+SPACE_LEN,second-(first+SPACE_LEN));
if(path.size()==1 && path[0]=='/') path+=HOME_PAGE;
return path;
}
string readFile(const string& recource)
{
// int fd = open(recource.c_str(),O_RDONLY|O_APPEND,0666);
ifstream in(recource);
if(!in.is_open()) return "404";
string content;
string line;
while(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<<endl;
}
string path = GetPath(buffer);
cout<<path<<endl;
// path = "/a/b/index.html";
// recource = "./wwroot"; //我们的web根目录
// recource += path; // ./wwwroot/a/b/index.html
// 1.文件在哪里? 在请求的请求行中,第二个字段
// 2.如何读取?
string recource = ROOT_PATH;
recource += path;
cout<<recource<<endl;
string html = readFile(recource);
size_t pos = recource.rfind('.');
string suffix = recource.substr(pos);
//开始响应
string response;
response = "HTTP/1.0 200 OK\r\n";
if(suffix == ".jpg")
response += "Content-Type: image/jpeg\r\n";
else
response += "Content-Type: text/html\r\n";
response += ("Content-Length: "+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, string ip = "")
: _listenSock(-1), _port(port), _ip(ip)
{}
~ServerTcp()
{
}
public:
void init()
{
// 1.创建socket
_listenSock = socket(AF_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 = AF_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 sockaddr *)&local, sizeof local) < 0)
{
exit(2);
}
// 3.监听socket,为何要监听呢?tcp是面向连接的!
if (listen(_listenSock, 5 /*后面再说*/) < 0)
{
exit(3);
}
// 允许别人来连接你了
}
void loop()
{
while (true)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
// 4.获取连接,accept的返回值是一个新的socket fd??
// 4.1 _listenScok:监听&&获取新的连接--->sock
// 4.2 serviceSock:给用户提供新的socket服务
int serviceSock = accept(_listenSock, (struct sockaddr *)&peer, &len);
if (serviceSock < 0)
{
continue;
}
// //5.1 V1.1---多进程版本
//爷爷进程
pid_t id=fork();
assert(id != -1);
if(id==0)
{
//爸爸进程
close(_listenSock);
//又进行了一次fork
if(fork()>0) exit(0);
//孙子进程--就没有爸爸进程了--孤儿进程--被系统领养了--回收问题就交给了系统来回收
handlerHttpRequest(serviceSock);
exit(0);
}
close(serviceSock);
//爸爸进程直接终止,立马得到退出码,释放僵尸进程状态
pid_t ret=waitpid(id,nullptr,0);//就用阻塞式等待
(void)ret;
}
}
private:
int _listenSock;
uint16_t _port;
string _ip;
};
serverTcp.cc
#include"server.hpp"
static void Usage(string proc)
{
cerr << "Usage\n\t" << proc << " port ip" << endl;
cerr << "Example\n\t" << proc << " 8080 127.0.0.1\n"
<< endl;
}
// ./serverTcp local_port [local_ip]
int main(int argc, char *argv[])
{
if (argc != 2 && argc != 3)
{
Usage(argv[0]);
exit(4);
}
uint16_t port = stoi(argv[1]);
string ip;
if (argc == 3)
{
ip = argv[2];
}
ServerTcp svr(port, ip);
svr.init();
svr.loop();
return 0;
}
备注:此处我使用了8080端口号启动了HTTP服务器,虽然HTTP服务器一般使用80端口!
但这只是一个通用的习惯.并不是说HTTP服务器就不能使用其他的端口号.
以上就是一个表单!
我们的网络行为无非有两种:
- 我想把远端的资源拿到你的本地:GET /index.html htpp/1.1
- 我们想把我们的属性字段提交给远端. GET or POST
用抓包软件进行抓包!
三、http的请求方法
方法 | 说明 | 支持的HTTP协议版本 |
---|---|---|
GET | 获取资源 | 1.0、1.1 |
POST | 传输实体主体 | 1.0、1.1 |
PUT | 传输文件 | 1.0、1.1 |
HEAD | 获得报文首部 | 1.0、1. |
DELETE | 删除文件 | 1.0、1.1 |
OPTIONS | 询问支持的方法 | 1.1 |
TRACE | 追踪路径 | 1.1 |
CONNECT | 要求用隧道协议连接代理 | 1.1 |
LINK | 建立和资源之间的联系 | 1.0、1.1 |
UNLINE | 断开连接关系 | 1.0、1.1 |
其中最常用的就是GET方法和POST方法!
GET和POST的区别
区别:
- GET通过url传参
- POST通过正文传参
- GET方法传参不私密
- POST方法通过正文传参,所以相对私密一些.
- GET通过url传参,POST通过正文传参,所以一般一些比较大的内容都是通过POST方式传参的!
三、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).
重定向
301:永久重定向
302:临时重定向
四、HTTP常见的Header
- Content-Type:数据类型(text/html等)
- Content-Length:body的长度
- Host:客户端告知服务器,所请求的资源是在那个主机的那个端口上面;
- User-Agent:声明用户的操作系统和浏览器版本信息;
- referer:当前页面是从哪个页面跳转过来的;
- Location:搭配3xx状态码使用,告诉客户端接下来要去哪里访问;
- cookie:用于在客户端存储少量信息.通常用于实现会话(session)的功能;
cookie
http协议特点之一:无状态
举个栗子:比如你刚才访问一个网页之后,http是不会帮你做记录的,也就是说http不知道你之前访问的是那个网页.
那么用户需要一个功能:会话保持.
一旦登陆,会有各种会话保持的策略.
使用B站举个栗子:
删除这些字段以后
刷新一下页面
这个网页就不认识这个用户了!
cookie:存储用户名&&密码
以后发起http请求访问该网站就会自动携带cookie文件中的内容.
cookie:浏览器维护的文件.真正的存在磁盘&&内存级文件.
注意: cookie的安全问题,比如中间人可能通过某种手段(植入病毒等)获取到了我们的cookie文件中的用户名和密码,获取到我们的信息,那么就可以以我们的身份访问网站了???
接下来引入session字段,解决这个问题!
session
Connection
Connection:keep-alive 长连接
用户所看到的的完整的页面内容–背后可能是无数次http请求
http底层主流采用的就是tcp协议!
一个tcp链接会有多个http请求!(根据请求报头中的Content-Length字段读取多个请求!)
http协议是无连接的!
总结
(本章完!)