http基本原理与客户端请求

本文深入讲解HTTP协议的基础概念、工作流程及客户端请求处理机制。详细分析了HTTP在TCP/IP协议栈中的位置、请求响应模型,并介绍了Cookie和Session的实现原理与区别。此外,还探讨了Web缓存的工作机制及优势。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


一、http基础概念与工作流程

1、介绍

HTTP是Hyper Text Transfer Protocol(超文本传输协议)的缩写。它的发展是万维网协会(World Wide Web Consortium)和Internet工作小组IETF(Internet Engineering Task Force)合作的结果,(他们)最终发布了一系列的RFC,RFC 1945定义了HTTP/1.0版本。其中最著名的就是RFC 2616。RFC 2616定义了今天普遍使用的一个版本——HTTP 1.1。

HTTP协议(HyperText Transfer Protocol,超文本传输协议)是用于从WWW服务器传输超文本到本地浏览器的传送协议。它可以使浏览器更加高效,使网络传输减少。它不仅保证计算机正确快速地传输超文本文档,还确定传输文档中的哪一部分,以及哪部分内容首先显示(如文本先于图形)等。

HTTP是一个应用层协议,由请求和响应构成,是一个标准的客户端服务器模型。HTTP是一个无状态的协议

2、在TCP/IP协议栈中的位置

HTTP协议通常承载于TCP协议之上,有时也承载于TLS或SSL协议层之上,这个时候,就成了我们常说的HTTPS。如下图所示:
在这里插入图片描述
默认HTTP的端口号为80,HTTPS的端口号为443。

3、HTTP的请求响应模型

HTTP协议永远都是客户端发起请求,服务器回送响应。见下图:
在这里插入图片描述
这样就限制了使用HTTP协议,无法实现在客户端没有发起请求的时候,服务器将消息推送给客户端。

HTTP协议是一个无状态的协议,同一个客户端的这次请求和上次请求是没有对应关系。

4、工作流程

一次HTTP操作称为一个事务,其工作过程可分为四步:

  1. 首先客户机与服务器需要建立连接。只要单击某个超级链接,HTTP的工作开始。
  2. 建立连接后,客户机发送一个请求给服务器,请求方式的格式为:统一资源标识符(URL)、协议版本号,后边是MIME信息包括请求修饰符、客户机信息和可能的内容。
  3. 服务器接到请求后,给予相应的响应信息,其格式为一个状态行,包括信息的协议版本号、一个成功或错误的代码,后边是MIME信息包括服务器信息、实体信息和可能的内容。
  4. 客户端接收服务器所返回的信息通过浏览器显示在用户的显示屏上,然后客户机与服务器断开连接。

如果在以上过程中的某一步出现错误,那么产生错误的信息将返回到客户端,有显示屏输出。对于用户来说,这些过程是由HTTP自己完成的,用户只要用鼠标点击,等待信息显示就可以了。

5、抓包分析

1、建立TCP连接
2、在TCP连接,socket的基础上,发送HTTP协议请求
3、服务器在TCP链接socket,返回一个HTTP协议的response

在这里插入图片描述
在上图中,可清晰的看到客户端浏览器(ip为192.168.2.33)与服务器的交互过程:

  1. No1:浏览器(192.168.2.33)向服务器(220.181.50.118)发出连接请求。此为TCP三次握手第一步,此时从图中可以看出,为SYN,seq:X (x=0)
  2. No2:服务器(220.181.50.118)回应了浏览器(192.168.2.33)的请求,并要求确认,此时为:SYN,ACK,此时seq:y(y为0),ACK:x+1(为1)。此为三次握手的第二步;
  3. No3:浏览器(192.168.2.33)回应了服务器(220.181.50.118)的确认,连接成功。为:ACK,此时seq:x+1(为1),ACK:y+1(为1)。此为三次握手的第三步;
  4. No4:浏览器(192.168.2.33)发出一个页面HTTP请求;
  5. No5:服务器(220.181.50.118)确认;
  6. No6:服务器(220.181.50.118)发送数据;
  7. No7:客户端浏览器(192.168.2.33)确认;
  8. No14:客户端(192.168.2.33)发出一个图片HTTP请求;
  9. No15:服务器(220.181.50.118)发送状态响应码200 OK

二、http客户端请求

1、www.baidu.com --> 翻译为IP地址。(DNS)
2、创建sockfd并与服务器连接
3、发送http协议请求并接收

客户端发送一个 HTTP 请求到服务器的请求消息包括以下格式:请求行(request line)、请求头部(header)、空行和请求数据四个部分组成,下图给出了请求报文的一般格式。
在这里插入图片描述

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

#include <netdb.h>
#include <fcntl.h>


#define HTTP_VERSION		"HTTP/1.1"
#define CONNETION_TYPE		"Connection: close\r\n"

#define BUFFER_SIZE			4096

将域名翻译为IP地址(DNS)

char *host_to_ip(const char *hostname) {

	struct hostent *host_entry = gethostbyname(hostname); //dns

	// 14.215.177.39 --> 
	// inet_ntoa: (unsigned int) --> char *如下
	// 0x12121212 --> "18.18.18.18"
	if (host_entry) {
		return inet_ntoa(*(struct in_addr*)*host_entry->h_addr_list);//无符号int型数据转换成char*(字符串)
	} 

	return NULL;
}

创建sockfd并与服务器连接

int http_create_socket(char *ip) {

	int sockfd = socket(AF_INET, SOCK_STREAM, 0);

	struct sockaddr_in sin = {0};// 用来形容服务器的IP 地址
	sin.sin_family = AF_INET;// address family (地址族/网络协议栈/我们使用的协议栈),AT_INET代表的是TCP/IP协议族
	sin.sin_port = htons(80); //端口,大部分http协议默认80端口
 	sin.sin_addr.s_addr = inet_addr(ip);// IP地址,char*(字符串)转换为无符号int型数据,正好与inet_ntoa

	if (0 != connect(sockfd, (struct sockaddr*)&sin, sizeof(struct sockaddr_in))) {
		return -1;
	}

	fcntl(sockfd, F_SETFL, O_NONBLOCK);//把io设置成非阻塞

	return sockfd;//sockfd是文件的句柄,文件操作的fd

}

发送http协议请求并接收

char * http_send_request(const char *hostname, const char *resource) {

	char *ip = host_to_ip(hostname); // 获取IP地址
	int sockfd = http_create_socket(ip);//sockfd建立好之后,就已经和服务器之间建立好了连接

	char buffer[BUFFER_SIZE] = {0};//buffer里面的数据格式就是http的协议格式
	sprintf(buffer, 
"GET %s %s\r\n\
Host: %s\r\n\
%s\r\n\
\r\n",

	resource, HTTP_VERSION,
	hostname,
	CONNETION_TYPE
	);
//上面每行最后一个"\"是占位符,表示那不是换行
	send(sockfd, buffer, strlen(buffer), 0);

	
	// 下面是接收
	// select 
	// 来监听receive的io里有没有可读的数据(也可以同时监听多个io)

	fd_set fdread;// 集合,0/1标志位,1有0没有
	
	FD_ZERO(&fdread);// 置空
	FD_SET(sockfd, &fdread);// 把监听的io置为一个我们监听的状态

	struct timeval tv;
	tv.tv_sec = 5;
	tv.tv_usec = 0;

	char *result = malloc(sizeof(int));
	memset(result, 0, sizeof(int));//malloc后要memset,防止有脏数据
	
	while (1) {// 不断遍历select,一旦有数据,+1

		// select(maxfd + 1, &rset, &wset, &eset, NULL);
		int selection = select(sockfd+1, &fdread, NULL, NULL, &tv);//(判断有多少可读的fd,一个可读的集合,可写的集合,哪些io出错,多长时间轮询一次遍历所有io)
		if (!selection || !FD_ISSET(sockfd, &fdread)) {
			break;
		} else {

			memset(buffer, 0, BUFFER_SIZE);
			int len = recv(sockfd, buffer, BUFFER_SIZE, 0);
			if (len == 0) { // disconnect,对端已经关闭
				break;
			}

			result = realloc(result, (strlen(result) + len + 1) * sizeof(char));//因为buffer一次可能读不完,所以重新分配result,把数据放到result中存储
			strncat(result, buffer, len);// 把buffer数据copy到result里
		}

	}

	return result;
}

main

int main(int argc, char *argv[]) {

	if (argc < 3) return -1;

	char *response = http_send_request(argv[1], argv[2]);
	printf("response : %s\n", response);

	free(response);
	
}

三、深入了解

1、Cookie和Session

Cookie和Session都是为了用来保存状态信息,都是保存客户端状态的机制,他们都是为了解决HTTP无状态的问题二做的努力。
Session可以用Cookie来实现,也可以用URL回写的机制来实现。用Cookie来实现的Session可以认为是对Cookie的更高级应用。

1.1 两者比较

Cookie和Session有一家明显的不同点:

  1. Cookie将状态保存在客户端,Session将状态保存在服务器端
  2. Cookie是服务器在本地机器上存储的小段文本,并随每一个请求发送至同一个服务器。网络服务器用HTTP投向客户端发送cookies,在客户终端,浏览器解析这些cookies并将它们保存为一个本地文件,它会自动将同一服务器的任何请求缚上这些cookies。Cookie最早在RFC2109中实现,后续RFC2965做了增强。Session并没有在HTTP的协议中定义
  3. Session是针对每一个用户的,变量的值保存在服务器上,用一个sessionID来区分实发个用户session变量。这个值是通过用户浏览器在访问的时候返回给服务器的。当客户禁用cookie时,这个值也可能设置为由get来返回给服务器
  4. 就安全性来说:当你访问一个使用session的站点,同时在自己机子上建立一个cookie,在服务器端的session更安全些。因为它不会任意读取客户存储信息。

1.2 Session机制

Session机制是一种服务器端的机制,服务器使用一种类似于散列表的结构(也可能就是使用散列表)来保存信息。

当程序需要为某个客户端的请求创建一个session的时候,服务器首先检查这个客户端的请求里是否已包含了一个sessionID,如果已包含一个sessionID则说明以前已经为此客户端创建过session,服务器就按照sessionID把这个session检索出来使用(如果检索不到,可能会新建一个),如果客户端请求不包含sessionID,则为此客户端创建一个session并且生成一个与此session相关联的sessionID。sessionID的值应该是一个既不会重复,又不容易被找到规律以仿造的字符串,这个 sessionID将被在本次响应中返回给客户端保存

1.3 Session的实现方式

1.3.1使用Cookie来实现

服务器给每个Session分配一个唯一的JESSIONID,并通过Cookie发送给客户端。

当客户端发起新的请求的时候,将在Cookie头中携带这个JESSIONID。这样服务器能够找到这个客户端对应的Session。

流程如下图所示:
在这里插入图片描述

1.3.2 使用URL回写来实现

URL回写是指服务器在发送给浏览器页面的所有链接中都携带JSESSIONID的参数,这样客户端点击任何一个链接都会把JSESSIONID带会服务器。

如果直接在浏览器输入服务端资源的url来请求该资源,那么Session是匹配不到的。

Tomcat对Session的实现,是一开始同时使用Cookie和URL回写机制,如果发现客户端支持Cookie,就继续使用Cookie,停止使用URL回写。如果发现Cookie被禁用,就一直使用URL回写。jsp开发处理到Session的时候,对页面中的链接记得使用response.encodeURL() 。

1.4 与Cookie相关的HTTP扩展头

  1. Cookie:客户端将服务器设置的Cookie返回到服务器;
  2. Set-Cookie:服务器向客户端设置Cookie;
  3. Cookie2 (RFC2965)):客户端指示服务器支持Cookie的版本;
  4. Set-Cookie2 (RFC2965):服务器向客户端设置Cookie。

1.5 Cookie的流程

服务器在响应消息中用Set-Cookie头将Cookie的内容回送给客户端,客户端在新的请求中将相同的内容携带在Cookie头中发送给服务器。从而实现会话的保持。

流程如下图所示:
在这里插入图片描述

2、缓存的实现原理

2.1 什么是Web缓存

WEB缓存(cache)位于Web服务器和客户端之间

缓存会根据请求保存输出内容的副本,例如html页面,图片,文件,当下一个请求来到的时候:如果是相同的URL,缓存直接使用副本响应访问请求,而不是向源服务器再次发送请求。

HTTP协议定义了相关的消息头来使WEB缓存尽可能好的工作。

2.2 缓存的优点

  1. 减少相应延迟:因为请求从缓存服务器(离客户端更近)而不是源服务器被相应,这个过程耗时更少,让web服务器看上去相应更快。
  2. 减少网络带宽消耗:当副本被重用时会减低客户端的带宽消耗;客户可以节省带宽费用,控制带宽的需求的增长并更易于管理。

2.3 客户端缓存生效的常见流程

服务器收到请求时,会在200OK中回送该资源的Last-Modified和ETag头,客户端将该资源保存在cache中,并记录这两个属性。当客户端需要发送相同的请求时,会在请求中携带If-Modified-Since和If-None-Match两个头。两个头的值分别是响应中Last-Modified和ETag头的值。服务器通过这两个头判断本地资源未发生变化,客户端不需要重新下载,返回304响应。常见流程如下图所示:

在这里插入图片描述


【注】:
1、如果socket是阻塞的,当我们read阻塞io的时候,一旦socket中没有数据,整个线程就会挂起,等待数据的到来
如果是非阻塞的,我们read的时候,即使没有数据,他也会立马返回。所以一般会选择非阻塞的io
2、realloc(void *ptr,size_t new_size); ptr是指向原来地址的指针,这个函数用于修改一个原先已经分配内存块的大小。
如果在该存储区后有足够的空间可供扩充,则可在原存储区位置上向高地址方向扩充,并返回传送给它的同样的指针值。如果在原存储区后没有足够的空间,则realloc分配另一个足够大的存储区,将现存的内容复制到新分配的存储区。
如果realloc中的第一个参数如果为空则和malloc一样。
3、gethostbyname()返回对应于给定主机名的包含主机名字和地址信息的hostent结构指针。结构的声明与gethostbyaddr()中一致。
4、

struct hostent
{
char *h_name;
char ** h_aliases;
short h_addrtype;
short h_length;
char ** h_addr_list;
};

5、inet_ntoa()将一个十进制网络字节序转换为点分十进制IP格式的字符串。
6、inet_addr()若字符串有效则将字符串转换为32位二进制网络字节序的IPV4地址
7、

struct sockaddr_in
{
	sa_family_t sin_faily;		// 地址族
	uint16_t sin_port;			// 16位TCP/UDP/……端口号
	strcut in_addr sin_addr;	// 32位IP地址
	char sin_zero[8];			// 不使用
};
strcut addr_in
{
	In_addr_t s_addr;			// 32位IP地址
};

8、socket(int af, int type, int protocol)函数用于根据指定的地址族、数据类型和协议来分配一个套接口的描述字及其所用的资源。如果协议protocol未指定(等于0),则使用缺省的连接方式。
af:一个地址描述。仅支持AF_INET格式,也就是说ARPA Internet地址格式。
type:指定socket类型。新套接口的类型描述类型,如TCP(SOCK_STREAM)和UDP(SOCK_DGRAM)。常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等。
protocol:顾名思义,就是指定协议。套接口所用的协议。如调用者不想指定,可用0。常用的协议有,IPPROTO_TCP、IPPROTO_UDP、IPPROTO_STCP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。
9、send(int sockfd, const void *buf, size_t len, int flags)是一个计算机函数,功能是向一个已经连接的socket发送数据,如果无错误,返回值为所发送数据的总数,否则返回SOCKET_ERROR。
10、int select (int maxfd + 1,fd_set *readset,fd_set *writeset, fd_set *exceptset,const struct timeval * timeout);
该函数用于监视文件描述符的变化情况——读写或是异常。
参数一:最大的文件描述符加1。
参数二:用于检查可读性,
参数三:用于检查可写性,
参数四:用于检查带外数据,
参数五:一个指向timeval结构的指针,用于决定select等待I/o的最长时间。如果为空将一直等待。
11、strncat(result, buffer, len);把buffer数据copy到result里

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值