HTTP协议与web服务器
浏览器与web服务器在应用层通信使用的是HTTP协议80号端口(超文本传输协议)(https443端口),HTTP协议在传输层使用的是TCP协议,那么浏览器需要和web服务器三次握手建立连接后,才可以发送HTTP请求报文,服务器收到请求报文后,向浏览器回复HTTP应答报文。IP 地址是由数字组成的,例如,难以记忆。而域名如baidu.com更便于人们记忆和使用。用户在浏览器地址栏输入域名,就能访问
一、浏览器与服务器通信过程
浏览器与web服务器在应用层通信使用的是HTTP协议80号端口(超文本传输协议)(https443端口),HTTP协议在传输层使用的是TCP协议,那么浏览器需要和web服务器三次握手建立连接后,才可以发送HTTP请求报文,服务器收到请求报文后,向浏览器回复HTTP应答报文。


IP 地址是由数字组成的,例如 192.168.1.1 ,难以记忆。而域名如 baidu.com 更便于人们记忆和使用。用户在浏览器地址栏输入域名,就能访问对应的网站或服务器,无需记住复杂的 IP 地址。
1.1 域名解析与建立连接
例如:浏览器要访问 “www.baidu.com”。
域名解析:通过 DNS(域名系统)将域名解析为对应的 IP 地址。
连接请求:使用 “connect()” 函数,指定解析出的 IP 地址和 HTTP 协议默认端口 “80” (现在也常用 443 端口用于 HTTPS )来发起连接请求。
建立连接:浏览器与 HTTP 服务器通过三次握手建立 TCP 连接。三次握手过程为:客户端发送 SYN 报文段;服务器收到后,回复 SYN + ACK 报文段;客户端再发送 ACK 报文段,至此连接建立成功。
1.2 数据交互
浏览器发送请求报文:连接建立后,浏览器向 HTTP 服务器发送 HTTP 请求报文,请求报文中包含请求方法(如 GET、POST 等 )、请求资源路径、协议版本等信息。
服务器应答报文:服务器接收到请求报文后,进行处理并回复 HTTP 应答报文,应答报文中包含状态码(如 200 表示成功 )、响应头(包含内容类型、长度等信息 )和响应体(如网页内容 )。
1.3 连接管理
短连接:数据交互完成后,通过 “close” 关闭连接。这种方式适用于一次简单的请求 - 响应交互场景,每次请求都要重新建立和关闭连接,开销相对较大。
长连接:保持连接不关闭,后续浏览器可在该连接上继续向服务器发送请求,避免了重复建立连接的开销,适用于需要频繁交互的场景,如网页中包含多个资源需多次请求的情况。
二、HTTP请求报头

HTTP请求报头的主要作用是为服务器提供关于客户端请求的详细信息,以便服务器能够正确地处理请求并返回合适的响应。这些报头信息对于确保HTTP通信的有效性和安全性至关重要。一个完整的HTTP请求由请求行、请求报头、空行和请求体(对于某些请求方法如POST)组成。
2.1 请求行
请求行包括三个部分:HTTP方法、请求的资源路径(URI)和HTTP协议版本。它们之间用空格分隔。
例如:GET /index.html HTTP/1.0\r\n
GET:这是一个HTTP方法,表示请求获取资源。其他常用的HTTP方法还包括POST(提交数据)、PUT(更新资源)、DELETE(删除资源)等。
/index.html:这是请求的资源路径,表示客户端希望从服务器获取/index.html这个文件。
HTTP/1.0:这是HTTP协议的版本,表示客户端使用的是HTTP 1.0版本。HTTP协议已经发展到1.1和2版本,每个版本都有一些改进和新特性。
\r\n:这是回车换行符,用于分隔HTTP请求的各个部分。在HTTP协议中,回车换行符(CRLF)是一个重要的分隔符。
2.2 请求报头
请求报头由多个字段组成,每个字段都包含一个名称和一个值,名称和值之间用冒号(:)分隔,字段之间用换行符(CRLF,即\r\n)分隔。请求报头提供了客户端环境、请求体信息、缓存控制等数据。
1.User-Agent:这个头部字段告诉服务器,发起请求的客户端应用程序是什么。在这个例子中,Wget/1.12表示这是一个使用Wget工具(一个命令行下载工具)发起的请求,版本是1.12。
2.Host:这个头部字段指定了请求的目标主机。在这个例子中,192.168.141.128是目标服务器的IP地址。这个字段在服务器使用虚拟主机时特别重要,因为它帮助服务器确定请求应该路由到哪个网站。
3.Connection:这个头部字段指定了连接的管理方式。在这个例子中,close表示请求完成后,服务器应该关闭连接。另一个常用的值是keep-alive,表示服务器应该保持连接打开,以便可以复用这个连接来发送更多的请求。
2.3 空行
请求报头之后是一个空行,用于分隔请求报头和请求体(如果有的话)。这个空行不包含任何内容,仅由CRLF组成。(\r\n)。
2.4 请求体
请求体包含客户端向服务器发送的数据。请求体通常用于POST、PUT等方法,GET请求通常不包含请求体。
1.GET:
用于请求指定资源的数据。
不应包含请求体(请求体在GET请求中被忽略)。
可被缓存、收藏为书签和保留历史记录。
2.POST:
用于向指定资源提交数据,请求服务器进行处理(例如提交表单或上传文件)。
可以包含请求体,其中包含要提交给服务器的数据。
不可被缓存,也不会保存到历史记录中。
3.PUT:
上传某个资源
可以包含请求体,其中包含要更新的资源数据。
有幂等性,多次执行相同操作结果相同。
4.DELETE:
删除指定的资源。
可以包含请求体,尽管通常不包含。
有幂等性。
5.HEAD:
类似于GET请求,但不返回响应体。
用于获取资源的元数据,如响应头。
不应包含请求体。
6.OPTIONS:
用于描述目标资源的通信选项。
可以包含请求体。
7.PATCH:
用于对已知资源进行部分修改。
可以包含请求体,其中包含要应用的部分修改。
不像PUT那样要求幂等性。
8.CONNECT:
用于将连接改为管道方式的代理服务器。
通常用于SSL加密服务器的连接(通过HTTP隧道技术)。
可以包含请求体。
三、HTTP应答报头

3.1 http应答报文头部信息
1. 状态行
HTTP/1.0 200 OK\r\n
HTTP/1.0:指定了HTTP协议的版本,这里是1.0版本。
200:状态码,表示请求已成功处理。
OK:状态码的描述,进一步说明了状态码的含义。在这里,它表明请求成功并且服务器返回了请求的资源。
\r\n:回车换行符,用于分隔状态行和后续的响应报头。
2. 服务器名称
Server: MYWEB/1.0\r\n
Server:这是一个通用报头字段,用于指定处理请求的服务器软件的信息。
MYWEB/1.0:服务器软件的名称和版本。在这个例子中,服务器软件名为MYWEB,版本为1.0。
3. 数据长度
Content-Length: 8024\r\n
Content-Length:这是一个实体报头字段,用于指定实体主体(即响应体)的长度,单位是字节。
8024:响应体的长度为8024字节。
4. 内容类型
Content-Type: text/html;charset=utf-8\r\n
Content-Type:这是一个实体报头字段,用于指定实体主体的媒体类型。
text/html:指定响应体的媒体类型为HTML文档。
charset=utf-8:指定字符编码为UTF-8,这意味着响应体中的文本数据使用UTF-8编码。
5. 分隔符
\r\n
这个回车换行符(CRLF)用于分隔HTTP响应报头和响应体。它标志着响应报头的结束和响应体的开始。
总的来说,这个HTTP响应报文头部提供了关于响应的基本信息,包括协议版本、状态码、服务器软件信息、响应体长度和内容类型。这些信息对于客户端(如浏览器)正确解析和处理响应内容至关重要。
3.2 应答状态
HTTP应答状态码是服务器对客户端请求的响应结果的数字代码。这些状态码帮助客户端理解请求是否成功,以及如果失败,失败的原因是什么。
HTTP状态码分为五类,每类状态码都表示不同的结果类型:
1. 信息性状态码(100-199)
这些状态码表示临时的响应,请求正在处理中,并且客户端可能需要执行一些操作才能继续。
100 Continue:服务器已收到请求的初始部分,客户端可以继续发送请求的剩余部分。
101 Switching Protocols:服务器同意客户端的请求,将交换协议,例如切换到WebSocket协议。
102 Processing:服务器已收到并正在处理请求,但没有响应可用。
2. 成功状态码(200-299)
这些状态码表示请求已成功处理。
200 OK:请求成功,服务器返回请求的资源。
201 Created:请求成功,服务器创建了新的资源。
202 Accepted:请求已接受,但尚未处理完成。
204 No Content:服务器成功处理了请求,但没有返回任何内容。
206 Partial Content:服务器成功处理了部分内容请求。
3. 重定向状态码(300-399)
这些状态码表示请求的资源已被移动到新的URL,客户端需要使用新的URL重新发起请求。
300 Multiple Choices:请求的资源有多个响应可供选择。
301 Moved Permanently:请求的资源已被永久移动到新的URL。
302 Found:请求的资源临时移动到新的URL。
304 Not Modified:自从上次请求后,资源没有被修改,客户端可以使用缓存的版本。
307 Temporary Redirect:请求的资源临时移动到新的URL,但应使用原始请求方法发起新请求。
4. 客户端错误状态码(400-499)
这些状态码表示客户端请求有错误,服务器无法处理。
400 Bad Request:服务器无法理解客户端的请求。
401 Unauthorized:请求需要用户的身份验证。
403 Forbidden:服务器理解请求,但拒绝执行。
404 Not Found:服务器找不到请求的资源。
405 Method Not Allowed:请求方法(如POST、GET等)不被允许。
5. 服务器错误状态码(500-599)
这些状态码表示服务器在处理请求时遇到错误,无法完成请求。
500 Internal Server Error:服务器遇到意外情况,无法完成请求。
503 Service Unavailable:服务器暂时过载或维护,无法处理请求
四、用C语言实现web服务器
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<pthread.h>
#include<fcntl.h>
#define PATH "/home/zgl/mycode/day22"
int socket_init();
char* get_filename(char buff[])
{
char* ptr=NULL;
char* s=strtok_r(buff," ",&ptr);
if(s==NULL)
{
return NULL;
}
printf("请求方法:%s\n",s);
s=strtok_r(NULL," ",&ptr);
return s;
}
void* fun(void* arg)
{
int* p=(int*) arg;
int c=*p;
free(p);
while(1)
{
int buff[1024]={0};
int n=recv(c,buff,1023,0);//浏览器发送过来的请求报文
if(n<=0)
{
break;
}
printf("recv: %s\n",buff);
char* filename = get_filename(buff);//获取请求报文中文件的名字
if(filename == NULL)
{
send(c,"404",3,0);
break;
}
char path[128] = {PATH};
if(strcmp(filename,"/") == 0)
{
strcat(path,"/index.html");
}
else
{
strcat(path,filename);
}
printf("path:%s\n",path);//组装文件的绝对路径
int fd=open(path,O_RDONLY);
if(fd==-1)
{
send(c,"404",3,0);
break;
}
int filesize = lseek(fd,0,SEEK_END);//获取文件大小
lseek(fd,0,SEEK_SET);
char head[128] = {"HTTP/1.1 200 OK\r\n"};//
strcat(head,"Server: myhttp\r\n");
sprintf(head+strlen(head),"Content-Length: %d\r\n",filesize);
strcat(head,"\r\n");//组装http应答报文
send(c,head,strlen(head),0);//发送http应答报文的报头给浏览器
char data[1024];
int num=0;
while((num = read(fd,data,1024)) >0)
{
send(c,data,num,0);//循环发送文件的数据,直到发完
}
close(fd);
}
close(c);
}
int main()
{
int sockfd=socket_init();
if(sockfd==-1)
{
exit(1);
}
while(1)
{
struct sockaddr_in caddr;
int len =sizeof(caddr);
int c=accept(sockfd,(struct sockaddr*)&caddr,&len);
if(c<0)
{
continue;
}
printf("accept c=%d\n",c);
int* p=(int*)malloc(sizeof(int));
if(p==NULL)
{
close(c);
continue;
}
*p=c;
pthread_t id;
pthread_create(&id,NULL,fun,p);
}
}
int socket_init()
{
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);//http端口
saddr.sin_addr.s_addr=inet_addr("127.0.0.1");
int res=bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
if(res==-1)
{
printf("bind err\n");
return -1;
}
res=listen(sockfd,10);
if(res==-1)
{
return -1;
}
return sockfd;
}
运行结果:

更多推荐


所有评论(0)