在经过之前的socket网络编程的练习之后,最近开始学习HTTP协议
总结以下自己的学习笔记
HTTP协议是非常常用的应用层协议,协议其实就是一种约定
其中包含双方需要按照一定的约定来约定双方传送数据的格式和解析数据的格式。
其实也就是约定这部分内容:
- 客户端和服务器端都有哪些信息需要交互
- 如何将交互的这部分信息组织成字符串,这个过程也称为 (序列化)
- 如何将字符串解析成为结构化信息,这个过程也称为 (反序列化)
HTTP请求报文:
常用的方法有POST方法 、GET方法、DELETE方法等。
来看一个简单的例子
POST http://123.207.58.25/login/userLogin HTTP/1.1
Host: 123.207.58.25
Connection: keep-alive
Content-Length: 27
Origin: http://123.207.58.25
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Accept: */*
Referer: http://123.207.58.25/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie: PHPSESSID=b52hc1l3dddvm61i1cvkkmif93
name=775&passwd=18700798311
一个HTTP请求报文中包含的部分有
1.首行为:[方法] + [url] +[版本]
POST http://123.207.58.25/login/userLogin HTTP/1.1
这里为 POST方法(获取资源)
网址是 http://123.207.58.25/login/userLogin
版本为 HTTP/1.1
2.Header:请求的属性
是以冒号加空格分隔的键值对,且每组属性之间用 ‘\n’ 隔开;
结束标志为一个空行
- host 属性表示主机名
Host: 123.207.58.25
- Content-Length属性
Content-Length: 27
该属性是用来指定Body部分的长度
- User-Agent属性表示用户主机的操作系统信息和浏览器信息等
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36
- Content-Type 属性
Content-Type: application/x-www-form-urlencoded
该属性是用来指定body部分的格式
- referer属性:当前页面是从哪个页面跳转过来的
Referer: http://123.207.58.25/
- cookie属性
Cookie: PHPSESSID=b52hc1l3dddvm61i1cvkkmif93
该属性一般用于保存用户提交的身份信息等,比如,当我们登陆一个网站时,本次登陆之后,本地就会将我们的身份信息提交到服务器上,本地cookie就会将服务器返回的信息保存下来,用于下次用户连接该服务器就不用重复登陆,
cookie属性的大小上限为4k,按照域名维度来保存的,比如我们上淘宝网站或者是京东都会对应有不同的Cookie来保存我们的身份信息。
3.Body部分
Herder结束后,空行的后面就是Body部分了,Body部分允许为空字符串,多数情况下为html格式的信息,存在没有Body的方法,例如上面的GET方法。
再来看一个简单的响应报文
HTTP/1.1 200 OK
Server: nginx
Date: Sat, 30 Jun 2018 10:45:42 GMT
Content-Type: text/html; charset=UTF-8
Connection: keep-alive
Vary: Accept-Encoding
Vary: Accept-Encoding
X-Powered-By: PHP/5.4.45
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Set-Cookie: PHPSESSID=b52hc1l3dddvm61i1cvkkmif93; expires=Sat, 30-Jun-2018 16:45:42 GMT; path=/
Set-Cookie: loginStatus=yes; expires=Sat, 30-Jun-2018 16:45:42 GMT; path=/
Content-Length: 27
一个HTTP响应报文中包含以下部分:
首行 :[版本号] + [状态码] + [状态码解释]
HTTP/1.1 200 OK
状态码分类表:
状态码 | 类别 | 原因短语 |
---|---|---|
1XX | Informational(信息性状态码) | 接收到请求正在处理 |
2XX | Success(成功状态吗) | 请求正常处理完毕 |
3XX | Redirection(重定向状态码) | 需要进行附加操作以完成请求 |
4XX | Client Error(客户端状态错误码) | 服务器无法处理请求 |
5XX | Server Error(信息性状态码) | 服务器处理请求错误 |
常用状态码表:
状态码 | 状态码解释 | 描述 |
---|---|---|
200 | OK | 请求成功(其后是对GET和POST请求的应答文档) |
302 | Found | 所请求的页面已经临时转移至新的url |
400 | Bad Reques | 服务器未能理解请求 |
403 | Forbidden | 对被请求页面的访问被禁止 |
404 | Not Found) | 服务器无法找到被请求的页面 |
502 | Bad Gateway | 请求未完成。服务器从上游服务器收到一个无效的响应 |
504 | Gateway Timeout | 网关超时 |
Header:
不同于HTTP请求报文的属性有:
- Set-Cookie属性
从请求报文中获取信息来设置Set-Cookie,再给客户端发送响应时,就会将其发送回去,并且Set-Cookie是和Cookie 搭配使用,Cookie中的值,就是从响应报文中的Set-Cookie中保存的
Set-Cookie: PHPSESSID=b52hc1l3dddvm61i1cvkkmif93; expires=Sat, 30-Jun-2018 16:45:42 GMT; path=/
Set-Cookie: loginStatus=yes; expires=Sat, 30-Jun-2018 16:45:42 GMT; path=/
location:搭配3XX状态码使用,告诉用户接下来要去哪里访问;
Body
报文的内容,多数是以网页展示
下面是一个简单的例子,来模拟HTTP服务器
这里只是简单的按照HTTP协议来构造数据
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include <arpa/inet.h>
//处理连接的请求响应
void connect_process(sockaddr_in addr , int new_socket)
{
(void)addr;
char read_buf[1024 * 10] = {0};
char buf[1024 * 10] = {0};
while(1)
{
//读取客户端请求
if((read(new_socket ,read_buf ,sizeof(read_buf)-1))<0)
{
perror("read");
}
else
{
printf("%s",read_buf);
printf("###################################\n");
//将客户端的请求报文打印出来
}
//此处不用关心对方发来的是什么请求
//只要要按照http响应报文格式回复过去
const char * body = "<html>\n<h1>love and peace</h1>\n</html>";
const char * first_line = "HTTP/1.1 200 OK";
const char * header_content_type = "Content-Type: text.html;charset=UTF-8";
//构造http响应报文
sprintf(buf,"%s\n%s\nContent-Length: %lu\n\n%s",first_line,header_content_type,strlen(body),body);
write(new_socket,buf,strlen(buf));
}
}
int main(int argc,char * argv[])
{
//判断命令行参数是否合法
if(argc != 3)
{
printf("Usage: ./server [IP] [Port]\n");
return 1;
}
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(argv[1]);
addr.sin_port = htons(atoi(argv[2]));
socklen_t addr_len = sizeof(addr);
//创建套接字
int fd = socket(AF_INET,SOCK_STREAM,0);
//绑定IP和端口号
int bind_ret = bind(fd,(sockaddr *)&addr,addr_len);
if(bind_ret < 0)
{
perror("bind");
return 2;
}
//使服务器处于监听状态
if(listen(fd,5) < 0 )
{
perror("listen");
return 3;
}
//服务器开始时间循环
while(1)
{
//服务器接收连接请求
int new_socket = accept(fd ,(sockaddr *)&addr,&addr_len);
if(new_socket < 0)
{
perror("accept");
continue;
}
pid_t pid = fork();
if(pid < 0)
{
perror("fork");
return 4;
}
if(pid == 0)
{
//在子进程的执行逻辑中,不用监听socket ,可以直接将其文件关闭
close(fd);
pid_t id = fork();
if(id < 0)
{
perror("fork");
return 5;
}
if(id > 0)
{//二代子进程
//为一个已经建立连接的用户提供服务
connect_process(addr , new_socket);
close(new_socket);
exit(0);
}
else
{ //一代子进程
//一代子进程直接退出,使二代子进程成为孤儿进程
//被一号进程收养,避免成为僵尸进程,造成内存泄漏
//并且直接退出,此处可以不用手动关闭文件
exit(0);
}
}
else
{//父进程
//在父进程的执行逻辑中,不用new_socket 必须要将其关闭
//因为父进程是一直在执行中,为了防止文件描述符泄漏
close(new_socket);
waitpid(pid,NULL,0);
}
}
close(fd);
}
在经过之前的socket网络编程的练习之后,最近开始学习HTTP协议
总结以下自己的学习笔记
HTTP协议是非常常用的应用层协议,协议其实就是一种约定
其中包含双方需要按照一定的约定来约定双方传送数据的格式和解析数据的格式。
其实也就是约定这部分内容:
- 客户端和服务器端都有哪些信息需要交互
- 如何将交互的这部分信息组织成字符串,这个过程也称为 (序列化)
如何将字符串解析成为结构化信息,这个过程也称为 (反序列化)
认识URL
平时大家俗称的网址,就是URL