URL
url:统一资源定位符,用来表示从互联网上得到的的资源位置和访问这些资源的方法。格式为
<协议>://<主机>:<端口>/<路径>
最左边是协议,应用层协议有很多种,如FTP(文件传输协议),SMTP(简单邮件传送协议)等,最长使用的是HTTP(超文本传送协议),也是本篇博客重点讨论的。
https://www.baidu.com/
这是百度首页的URL,左侧使用https协议(比http多加一层保密措施,更加安全),然后是主机www.baidu.com即该主机在互联网上的域名。端口号一般省略(http:80, https:443)。因为没有
进行查找任何东西,所以路径为web根路径。
HTTP
超文本传送协议,客户端与服务器端通过该协议在互联网上实现通信。
特点
- http协议是无状态的,即每一次客户端对服务器端进行访问时都是一个全新的请求,服务器不会记录该客户的任何信息,即使在同一时间内该客户多次访问该服务器,对于服务器来说可以认为是多个客户访问该服务器。
- http协议是无连接的,客户端与服务器端通信依靠tcp协议,建立连接与释放连接都是tcp要做的工作,http不关心,只关心发送数据与接收数据,所以http的无连接的。
http报文结构
- 请求报文
第一行为请求行,用来区分是请求报文还是响应报文.
请求行第一部分为方法,表示此次http请求的目的,常见的方法有:
方法(操作) | 意义 | 支持的http版本 |
---|---|---|
GET | 请求读取由URL所标志的信息 | http1.0、1.1 |
POST | 给服务器传送信息 | http1.0、1.1 |
HEAD | 请求读取由URL所标志的信息的首部 | http1.0、1.1 |
DELETE | 删除指明的URL所标志的资源 | http1.0、1.1 |
PUT | 在指明的URL下存储一个文档 | http1.0、1.1 |
OPTION | 请求一些选项的信息 | http1.1 |
TRACE | 用来进行环回测试的请求报文 | http1.1 |
CONNECT | 用于代理服务器 | http1.1 |
LINK | 建立和资源之间的联系 | http1.0 |
UNLINK | 断开链接联系 | http1.0 |
第二部分为URL,表示请求的方法;
第三部分为http版本。
接下来是首部行,首部行包含多行,用来说明浏览器、服务器或报文主体的一些信息,每一行都有首部字段名和它的值。
常见首部字段:
首部字段名 | 说明 |
---|---|
Cache-Control | 控制缓存的行为 |
Connection | 逐跳首部、连接的管理 |
Accept | 用户代理可处理的媒体类型 |
Host | 请求资源所在服务器 |
Referer | 请求中URL的原始获取方 |
Location | 令客户端重定向至指定URl |
Content-Lenth | 实体主体的大小(字节) |
Content-Type | 实体主体的媒体类型 |
一个http请求报文
GET /favicon.ico HTTP/1.1
Host: 121.4.220.117:8080
Connection: keep-alive
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.93 Safari/537.36
Accept: image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
Referer: http://121.4.220.117:8080/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
该请求报文没有实体部分。
- 响应报文格式
响应报文只有第一行与请求行不一样,响应报文的第一行分别为版本,状态码,状态码描述。
版本就是http版本,状态码是指这个响应报文所对应的请求的状态。状态码描述是对状态码的一个简单描述。
状态码分为5类
类别 | 描述 | |
---|---|---|
1xx | 信息型状态码 | 接受的请求正在处理 |
2xx | 成功状态码 | 请求正常处理完毕 |
3xx | 重定向状态码 | 需要进行附加操作以完成请求 |
4xx | 客户端错误状态码 | 服务器无法处理请求 |
5xx | 服务器错误状态码 | 服务器处理请求出错 |
常见状态码:
状态码 | 描述 |
---|---|
200 | OK(请求处理成功,请求资源作为实体返回) |
204 | NO Content(请求处理成功,但没有资源返回) |
206 | Partial Content(请求处理成功,返回资源的一部分) |
301 | Moved Permanently(资源被永久重定向) |
302 | Found(临时重定向) |
304 | Not Modified(资源找到,但请求条件不符合) |
400 | Bad Request(错误请求) |
401 | Unauthorized(请求需要认证) |
403 | Forbidden(请求被服务器拒绝) |
404 | Not Found(服务器没有请求资源) |
500 | Internal Server Error(服务器内部错误) |
503 | Service Unavailable(服务器忙) |
cookie与session
前面提到过http是无状态的,即每一次请求对于服务器来说都是新的请求,所以在有些要输入账号密码的网站上,在这个网站内每一次发送请求理论上都要重新输入账号密码来让服务器验证客户的身份,但在实际体验中却并非如此,这就是cookie与session的作用,在与服务器进行通信时,服务器使用session机制为客户生成唯一表示符sid,然后将sid放在响应报文的首部字段里面,客户端拿到响应报文后将sid保存在本地就是cookie,下一次访问同样的服务器时会把本地保存的sid添加到请求报文的首部字段中,服务器端拿到sid后就可以识别客户端,而不需要再次认证。
一个简单的http服务器
httpServer.hpp
#pragma once
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#define BACKLOG 5
class httpServer{
public:
httpServer(int port):_port(port){}
~httpServer(){
if(_sock > 0){
close(_sock);
}
}
void httpEcho(int sock){
char request[2048];
size_t ret = recv(sock, request, sizeof(request) - 1, 0);
if(ret > 0){
std::cout << request <<std::endl;
std::string echo;
echo += "HTTP/1.0 200 OK\r\n";
echo += "Content-Type: text/html; charset=UTF-8\r\n";
echo += "\r\n";
echo += "<!DOCTYPE html>\
<html>\
<body>\
<h1>我的第一个标题</h1>\
<p>Hello world!</p>\
</body>\
</html>";
if(send(sock, echo.c_str(), echo.size(), 0) < 0){
std::cerr << "send error" << std::endl;
exit(4);
}
}
close(sock);
}
void start(){
sockaddr_in addr;
while(true){
socklen_t len = sizeof(addr);
int sock = accept(_sock, (sockaddr *)&addr, &len);
if(sock < 0){
continue;
}
std::cout << "get a new link" << std::endl;
if(fork() == 0){
close(_sock);
httpEcho(sock);
exit(0);
}
close(sock);
}
}
void httpInit(){
signal(SIGCHLD, SIG_IGN);
_sock = socket(AF_INET, SOCK_STREAM, 0);
if(_sock < 0){
std::cerr << "socket error" << std::endl;
exit(1);
}
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(_port);
addr.sin_addr.s_addr = INADDR_ANY;
if(bind(_sock, (sockaddr*)&addr, sizeof(addr)) < 0){
std::cout << "bind error" << std::endl;
exit(2);
}
if(listen(_sock, BACKLOG) < 0){
std::cerr << "listen error" << std::endl;
exit(3);
}
}
private:
int _port;
int _sock;
};
httpServer.cc
#include "httpServer.hpp"
int main(){
httpServer *p = new httpServer(8080);
p->httpInit();
p->start();
delete p;
return 0;
}
通过套接字编程实现简单的http服务器,启动后通过·浏览器访问对应IP地址和端口号即可获取响应。
HTTP与HTTPS
使用http时请求报文与响应报文都是通过明文传送,很容易被第三方截获并修改,所以处于安全性的考录,出现了https。https并非是新的协议,而是http + ssl/tls。ssl是介于http与tcp之间的一个协议,负责http的加密与解密。tls是ssl的标准版,更加规范,但是ssl术语更加常用一些,所以尽管现在大都使用tls但还是习惯称为ssl。
https就是加了ssl的http。
SSL加密方式
- 对称式加密,即客户端与服务器端使用同一个密钥,发送请求与响应时先用密钥加密,然后对端再用密钥进行解密,以此达到加密通信。但此种方法有缺陷,就是在通信之前客户端与服务器端需要发送密钥来确保双方使用同一种密钥,所以最开始发送报文是会含有密钥,而此时如果黑客已经截获了请求或响应报文,那么黑客也就拿到了密钥,那加密也就失去了意义。
- 非对称式加密,为了解决上述问题,加密时采用一组密钥(公钥和私钥),公钥一般用来加密,私钥一般用来解密。私钥永远保存在服务器端,而公钥是公开的。发送方向对方发送数据时,先用对方的公钥进行加密,接收方收到后再用私钥进行解密。这样一来私钥永远不会在互联网中出现,黑客也无法通过密文和公钥来破解密文,达到安全通信。
- SSL使用两种方式混合,即通信时还是使用对称式加密,因为非对称加密消耗大。在最初发送对称密钥时采用非对称式加密方式确保密钥是对外不可见的,然后再使用对称密钥进行通信。
这样就一定安全了吗?
即使采用了非对称式加密,也不一定是安全的。黑客可以假装成服务器,设置属于自己的公钥和私钥,然后与客户端进行通信,获取客户端的私钥,然后以客户的身份与原服务器通信,在客户端看来他是与服务器直接通信,但实际上中间通过了第三方。
那么如何确定自己拿到的公钥是服务器的公钥呢?这就需要第三方CA机构了。
CA机构专门用于给各个网站签发数字证书,从而保证浏览器可以安全地获得各个网站的公钥。服务器首先向CA机构发送自己的公钥,CA机构也有自己的公钥与私钥,使用私钥对服务器的公钥以及其他相关数据(标识该服务器)进行加密,然后将加密过后的信息发送给服务器,该信息也就是证书。客户端会内置CA机构的公钥,与服务器通信时,服务器会把自己的证书发送给客户端,客户端用CA机构的公钥对证书进行解密,如果解密成功,则可以证明该服务器就是要访问的服务器了。
HTTP1.0、HTTP1.1、HTTP2.0的区别
HTTP1.0与HTTP1.1区别
- HTTP1.0只支持短连接,即每一次请求与响应之后连接(tcp连接,http是无连接的)。这使得同一时间内重复访问同一个服务器时会重复建立与释放多个连接,消耗资源。
- HTTP1.1支持长链接,请求报文在首部字段中添加要保持长连接的信息,服务器收到后发送响应报文,但是不会立即断开连接,而是将连接继续保持。
- HTTP1.1增加Host字段,指明客户端的IP地址。
- HTTP1.1支持断点续传,如传输数据时一部分丢失,下次重传时只需要传输丢失的部分而不是全部。
HTTP1.1与HTTP2.0区别
- HTTP2.0使用了多路复用的技术,做到同一个连接并发处理多个请求,而且并发请求的数量比HTTP1.1大了好几个数量级。HTTP1.1也可以多建立几个TCP连接,来支持处理更多并发的请求,但是创建TCP连接本身也是有开销的。
- HTTP1.1不支持header数据的压缩,HTTP2.0支持头部压缩,报文体积更小在网络上传输就会更快。
- 服务器除了对最初请求的响应外,服务器还可以额外的向客户端推送资源,而无需客户端明确的请求。