HTTP协议介绍
HTTP(超文本传输协议)属于应用层协议,但是它在传输层使用的TCP协议,也就是说HTTP协议也是对TCP协议的一种应用,HTTP协议不光能传输本文信息,也能对视频、声音、图片都可以进行传输
浏览器与服务器通信过程
左边是我们的web服务器,右边是我们的浏览器,我们通过浏览器来访问一个网址,web服务器与浏览器直接传输层通信是TCP协议,也就是说需要有三次握手建立连接以及四次挥手断开连接 我们进行TCP连接需要被连接主机的IP以及端口号,而我们现在只有一个网址该如何进行连接呢?这需要我们首先通过DNS服务器对网址进行解析,得到ip与端口进行connect() 进行连接 等TCP连接建立完成,浏览器就会向服务器发送HTTP请求报文,web服务器收到HTTP的请求报文,就会回复HTTP应答报文,就像客户端向服务器发送一个“hello”,服务器向客户端回复一个“ok”
HTTP的长连接与短连接
- 如果我们在HTTP应答报文发送过后执行
close() 断开连接,这就叫HTTP的短连接,短连接需要不断的进行TCP连接,每次HTTP应答都使用了不同的TCP连接 - 如果我们在HTTP应答报文发送过后不断开连接,并且浏览器继续发送HTTP的请求,服务器也再向浏览器发送HTTP应答,再两次及以上的HTTP请求用了同一个TCP连接,我们就叫做它长连接,长连接更有利于我们对网络资源的使用
HTTP请求方法和应答状态码
下面是一个不完整的示例:
- GET方法,需要获取的资源 以及协议版本
- 客户端,浏览器的名字
- 访问的主机名
- 长连接还是短连接
如果我们是一个长连接的话就是keep-alive ,短连接是close
请求方法
例如:
GET 方法就是向web服务端以只读的方式,将我们需要的内容呈现出来,并不会对服务器产生什么影响HEAD 方法只需要返回应答的头部信息,而不需要数据部门的信息POST 方法是客户端向服务器提交数据,就像我们在网页中输入信息点击提交向服务器发送数据
应答状态码
HTTP应答的部分内容如下:
- HTTP协议版本,状态码(200 ok)
- 服务器名字
- 数据部分长度
- 类型等
- 后面才是数据内容
- 100 代表收到请求行与头部,告诉客户端继续发送数据
- 200 请求成功
- 3xx 资源重定向
- 4xx 客户端错误(404资源找不到、403无权限访问)
- 5xx 服务端错误
自己实现简单的HTTP服务器
我们先写这样一段代码来看看,当浏览器访问服务器发送的报文,以及服务器向浏览器回复“hello”会发送什么
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
int create_socket();
int main()
{
int sockfd = create_socket();
assert(sockfd != -1);
while(1)
{
struct sockaddr_in caddr;
int len = sizeof(caddr);
int c = accept(sockfd,(struct sockaddr*)&caddr,&len);
if(c<0)
{
continue;
}
char buff[1024] = {0};
int n = recv(c,buff,1023,0);
printf("n=%d,read:\n%s",n,buff);
send(c,"hello",5,0);
close(c);
}
}
int create_socket()
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd == -1)
{
return -1;
}
struct sockaddr_in saddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(80);
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
if(res == -1)
{
return -1;
}
res = listen(sockfd,5);
if(res == -1)
{
return -1;
}
return sockfd;
}
编译运行代码,会显示错误,这是因为我们使用的80号端口,在Linux上小于1024的端口属于系统保留端口,只有管理员权限才能使用
切换至管理员执行成功,代码阻塞等待浏览器的连接 我们打开浏览器输入“127.0.0.1”本机测试ip,可以看到来自服务器回复的“hello”,我们没有向浏览器回复正确格式的应答报文,所以浏览器将它直接打印出来 回到终端可以看到来自浏览器的请求报文 我们接着组装一个正确的回复报文,增加下面的代码
char sendbuff[512] = {0};
strcpy(sendbuff,"HTTP/1.1 200 ok\r\n");
strcat(sendbuff,"Server: myhttp\r\n");
strcat(sendbuff,"Content-Length: 5\r\n");
strcat(sendbuff,"\r\n");
strcat(sendbuff,"hello");
printf("send:\n%s\n",sendbuff);
send(c,sendbuff,strlen(sendbuff),0);
运行并且通过浏览器进行访问 这里的hello不同于上面的hello,我们可以通过查看页面源代码进行查看 我们现在修改代码,解析客户端请求方法以及需要访问的哪个文件
char *s = strtok(buff," ");
if(s == NULL)
{
close(c);
continue;
}
printf("请求方法:%s\n",s);
s = strtok(NULL," ");
if(s == NULL)
{
close(c);
continue;
}
printf("请求的资源:%s\n",s);
运行可以看到,以及成功分析了请求方法以及请求的资源 现在我们写一个简易的index.html 文件,提供浏览器访问
<html>
<head>
<meta charset = utf-8>
<title>这是测试页面</title>
</head>
<body>
<h3>欢迎访问
<br>
<hr>
</body>
</html>
对代码也进行修改,将该文件发送至浏览器
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<fcntl.h>
int create_socket();
int main()
{
int sockfd = create_socket();
assert(sockfd != -1);
while(1)
{
struct sockaddr_in caddr;
int len = sizeof(caddr);
int c = accept(sockfd,(struct sockaddr*)&caddr,&len);
if(c<0)
{
continue;
}
char buff[1024] = {0};
int n = recv(c,buff,1023,0);
printf("n=%d,read:\n%s",n,buff);
char *s = strtok(buff," ");
if(s == NULL)
{
close(c);
continue;
}
printf("请求方法:%s\n",s);
s = strtok(NULL," ");
if(s == NULL)
{
close(c);
continue;
}
printf("请求的资源:%s\n",s);
if(strcmp(s,"/")==0)
{
s = "/index.html";
}
char path[128] = {"/home/zyq/Linux/c208/http"};
strcat(path,s);
int fd = open(path,O_RDONLY);
if(fd == -1)
{
send(c,"404",3,0);
close(c);
continue;
}
int size = lseek(fd,0,SEEK_END);
lseek(fd,0,SEEK_SET);
char sendbuff[512] = {0};
strcpy(sendbuff,"HTTP/1.1 200 ok\r\n");
strcat(sendbuff,"Server: myhttp\r\n");
sprintf(sendbuff+strlen(sendbuff),"Content-Length: %d\r\n",size);
strcat(sendbuff,"\r\n");
printf("send:\n%s\n",sendbuff);
send(c,sendbuff,strlen(sendbuff),0);
char data[512] = {0};
int num = 0;
while((num = read(fd,data,512)) >0 )
{
send(c,data,num,0);
}
close(fd);
close(c);
}
}
int create_socket()
{
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd == -1)
{
return -1;
}
struct sockaddr_in saddr;
memset(&saddr,0,sizeof(saddr));
saddr.sin_family = AF_INET;
saddr.sin_port = htons(80);
saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
int res = bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
if(res == -1)
{
return -1;
}
res = listen(sockfd,5);
if(res == -1)
{
return -1;
}
return sockfd;
}
我们运行代码,并且在浏览器查看 我们可以通过修改index.html 文件来改变浏览器访问服务器得到的内容
|