URL格式
URL即'网址'
![](https://i-blog.csdnimg.cn/blog_migrate/a8002c0c386cb25a18fb17fe1298b8dd.png)
- 服务器地址实际上是指ip地址,这里用域名表示,便于记忆
- http服务器端口号默认80,可以不使用80
- 带层次的文件路径不同,访问服务器上的文件就不同
- 查询字符串 : 都是以键值对的方式存储的, 键和值之间使用等号分隔,每一组键值对之间用 & 分隔
- 片段标识符 : 表示要跳转到本页面的某一部分
urlencode和urldencode
即 '编码' 和 '解码'
我们可以看到,在上面的url格式中,有一些我们固定使用的字符,比如 / ? : 等,这些字符在url中有着特殊的含义,所以我们不可以随便的使用他
如果某个参数中带有这样的特殊字符,我们就必须对他进行转义
转义规则:
将需要转码的字符转为16进制,然后从右到左,取4位(不足四位直接丢掉),没两位做一位,前面加上%,编码成 %XY 的格式
例如百度搜索C++时:
![](https://i-blog.csdnimg.cn/blog_migrate/206960994091b431b7ff7723db78df24.png)
我们可以看到, + 号被转化为了 %2B
HTTP报文
用于HTTP协议交互的信息我们称之为报文,客户端的HTTP报文我们叫做请求报文,服务器端的HTTP报文我们叫做响应报文
HTTP报文大致可分为 报文首部,Header,空行,Body
通过下面我们使用抓包工具抓的HTTP包可以看出来,
http协议是纯文本协议,行文本
HTTP请求报文
GET请求 :
![](https://i-blog.csdnimg.cn/blog_migrate/536cab476434db7a48908866e13ca8e9.png)
首行: 以下三部分信息都用空格分隔
- GET : http协议的方法,表示浏览器要从服务器上获取某一个数据
- http://baidu.com/ : 一个url,表示具体访问服务器上哪一个资源
- HTTP/1.1 : 当前HTTP版本号
Header: 以键值对的方式存储,每一对键值对换一行,键和值以 [冒号][空格] 分隔(请求的Header字段有很多,这里主要讲解一下上面出现过的字段)
- Host : 表示访问主机名,也就是ip地址的别名,即域名
- Connection: 逐跳首部,连接的管理
- User-Agent : 包含操作系统信息和浏览器信息
- Accept: 用户代理可处理的类型
- Accept-Encoding: 优先的内容编码
- Accept-Language: 优先的语言(自然语言)
- Cookie : 用来保存一个从服务器返回回来的字符串,并且保存到本地,下一次访问同一个网站,会将这个信息带回去
最常见的保存的是用户身份信息 : 例如用户登录信息,一旦在首页登陆过一次,访问其他页面就不需要登录
空行 : Header的结束标志
POST请求:
- 首行 : [方法]+[url]+[版本]
- Header :
Content-Type : 如果最后面是 -from-urlencoded,那么body的格式就和querystr一致
Content-Length : 表示Body一共占多少字节
Referer : 表示当前页面是从哪一个页面跳转过来的
- 空行 : 表示Header结束
- Body :
格式很有可能和querystr格式一样,主要取决于Header中的Content-Type ( querystr : 键值对形式存储,键值对之间用 & 隔开,键和值之间用 = 相连)
GET和POST请求的区别 : POST有Body,并且在Header中用Content-Type和Content-Length来表示Body的类型和大小
HTTP响应报文
![](https://i-blog.csdnimg.cn/blog_migrate/6644d0c691687c5cceddd2809def7150.png)
首行 : 以空格分隔
- [版本号] + [状态码] + [状态码解释]
Header : 以键值对的方式存储,每一对键值对换一行,键和值以 [冒号][空格] 分隔
- Sever: HTTP服务器安装信息
- Content-Type : text/html 表示body的格式是一个xml的文本格式; charset说明字符集为utf-8
- Content-Length : Body部分的字节数
- Vary: 代理服务器缓存的管理信息
- Location: 令客户端重定向到指定的URL
- Set-Cookie : 和请求中的Cookie对应.服务器利用Set-Cookie向客户端返回用来表示身份信息的的字符串,下次访问时就会重新带入,避免再次登录.
Cookie 是以域名为维度进行存储;
Cookie 的存储能力有限,浏览器默认4k,所以一般只保存身份标识,避免网络交互过程中占用过多的带宽;
空行 : Header结束标志
Body :
- 浏览器见到这一部分内容,就会按照Content-Type的格式展示出来.
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 |
UNLINK | 断开连接关系 | 1.0 |
1.0 是一个基于请求-响应的短连接
HTTP状态码
类别 | 原因短语 | 经常见到的状态码 | 描述 | |
1XX | Informational(信息性状态码) | 接收的请求正在处理 | ||
2XX | Success(成功状态码) | 请求正常处理完毕 | 200 OK | 请求成功(其后是对GET和POST请求的应答文档。) |
3XX | Redirection(重定向状态码) | 需要进行附加操作以完成请求 |
302 Found
|
所请求的页面已经临时转移至新的url。
|
4XX | Client Error(客户端错误状态码) | 服务器无法处理请求 |
404 Not Found
|
服务器无法找到被请求的页面。
|
5XX | Server Error(服务器错误状态码) | 服务器处理请求出错 |
504 Gateway Timeout
|
网关超时。
|
实现简单地HTTP服务器
这里是一个最简易版本的http服务器,不管客户端发出什么请求,服务器都相应一个hello world 页面
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <pthread.h>
int ServerInit(short port)
{
int fd = socket(AF_INET,SOCK_STREAM,0);
if(fd < 0)
{
perror("socket");
return -1;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(port);
int ret = bind(fd,(struct sockaddr*)&addr,sizeof(addr));
if(ret < 0)
{
perror("bind");
return -2;
}
ret = listen(fd,3);
if(ret < 0)
{
perror("listen");
return -3;
}
return fd;
}
void* ThreadEntry(void* arg)
{
int* pnew_sock = (int*)arg;
int new_sock = *pnew_sock;
// 构造一个足够大的响应空间,获取HTTP请求
char buf[1024 * 10] = {0};
read(new_sock,buf,sizeof(buf)-1);
printf("[Request] %s\n",buf);
// 构造HTTP响应
const char* first_line = "HTTP/1.1 200 OK\n"; //此处构造http响应首部
const char* blank_line = "\n"; //header和body之间的空行
const char* body_line = "<html><h1>Hello World<h1></html>"; //此处构造Body,最简单的页面
char header_buf[128] = {0};
//构造header,这里仅包含body的类型和长度
sprintf(header_buf,"Content-Type: text/html;\nContent-Length: %lu\n",strlen(body_line));
write(new_sock,first_line,strlen(first_line));
write(new_sock,header_buf,strlen(header_buf));
write(new_sock,blank_line,strlen(blank_line));
write(new_sock,body_line,strlen(body_line));
close(new_sock);
return NULL;
}
int main(int argc,char* argv[])
{
if(argc != 2)
{
printf("Usage : ./http_server [port]\n");
return 1;
}
// 创建 listen_sock
int listen_sock = ServerInit(atoi(argv[1]));
if(listen_sock < 0)
{
perror("ServerInit Faild\n");
return 2;
}
printf("ServerInit OK\n");
// 进入事件循环
while(1)
{
struct sockaddr_in peer;
socklen_t len = sizeof(peer);
int new_sock = accept(listen_sock,(struct sockaddr*)&peer,&len);
if(new_sock < 0)
{
perror("accept");
return 3;
}
// 使用线程完成多用户可访问模式
pthread_t tid;
pthread_create(&tid,NULL,ThreadEntry,(void*)&new_sock);
pthread_detach(tid);
}
return 0;
}
执行指令: ./http_server 9090
演示结果:
![](https://i-blog.csdnimg.cn/blog_migrate/850ec73ecfe1c125a3432916613b06e2.png)
我们接收到的http请求
![](https://i-blog.csdnimg.cn/blog_migrate/23267dc244b505b0d4b98264fbb6e691.png)