http协议简析及C++实现HTTP请求

http是超文本传输协议,无状态协议(不同批次无法相互识别),无连接协议,工作在应用层,用于完成从万维网服务器传输超文本到本地浏览器的传输协议,完成了文档的快速传输,还能确定传输文档的哪一部分,以及控制哪一部分内容首先显示。

整个http过程由请求和响应构成,a通常承载与TCP协议上。
HTTP完整流程包括四部分:
1.客户机与服务器建立连接;2.客户机发送请求给服务器;3.服务器接收到客户端的请求,发出响应;4.客户端收到请求并处理。
http请求由三部分组成:请求行,首部,请求正文。


请求报文格式为:请求行+首部+空行+实体组成

响应报文格式为:状态行+首部+空行+实体组成

<1>请求行以方法开头,以空格分开,紧随其后的是路径和版本(包括http请求的种类,请求资源的路径,协议的版本号),
格式为:Method Request-URL HTTP-Version CLRF

Method为请求方法(get,post等),Request-URL是统一的资源标识符,HTTP-Version为HTTP协议的版本,CRLF为回车和换行(回车和换行只能出现在结尾,不能再其他地方出现)。

请求方法有:

    GET请求服务器的文档;POST向服务器发送信息;

    PUT从服务器向客户端发送文档;DELETE为删除web网页;

    TRACE为把到达的请求回送;HEAD为请求关于文档的信息,但不是文档本身;OPTIONS询问关于可用的选项;

<2>请求首部为http的头部信息,每一个用\r\n分割;

常用的首部有:

-----Connection:允许客户端和服务器指定与请求/响应连接有关的选项(长连接,短连接等)

-----Data:提供日期和时间标志

-----MIME-Version:给出了发送端使用的MIME版本

-----Client-IP:描述客户端机器的IP

-----From:提供了客户端用户的E-mail地址

-----Host:给出请求的主机名和端口号

-----Referer:提供了包含当前请求的URL的文档的URL

-----cookie:向服务器传送一个令牌

<3>请求正文为发送给服务器的查询信息(使用get时,body是空的,get只能读取,而post可以写入信息);

"GET / HTTP/1.1\r\nHost:www.dytt8.net\r\nConnection:Close\r\n\r\n"---请求行\r\n请求首部1\r\n请求首部2\r\n\r\n报文实体内容

响应报文

HTTP/1.1 200 OK
Content-Type: text/html
Content-Location: http://www.dytt8.net/index.htm
Last-Modified: Sat, 07 Apr 2018 02:27:11 GMT
Accept-Ranges: bytes
ETag: "8029cfee17ced31:30d"
Server: Microsoft-IIS/6.0
Date: Sat, 07 Apr 2018 06:18:47 GMT
X-Via: 1.1 localhost.localdomain (random:320992 Fikker/Webcache/3.7.5)
Content-Length: 70109
Connection: close





电影天堂_免费电影_迅雷电影下载

响应报文也包括请求行,请求首部,空行,实体

请求行格式为:http版本+状态码+短语+\r\n  短语与状态码相对应

请求首部格式为:一系列首部名称:值\r\n的组合

请求实体为:服务端真正返回的信息

状态编码为:

100-199指定客户端响应的一些动作,请求已被服务接收 200-299表示请求被接受,处理成功 

300-399表示已经移动的文件,重定向,需进一步处理

400-499指定客户端的错误,有语法错误无法实现 500-599指定服务端的错误,服务器未能实现合法的请求


HTTP1.1之后使用了长连接,长连接使数据传输完成后继续保持TCP连接不间断,等待相同域名继续使用这个通道进行数据传输,HTTP1.0使用首部Connection:Keep-alive进行长连接的试验,HTTP1.1之后使用Connection:Close来告诉服务端不使用长连接。但是使用了Connection:Keep-alive这个首部并不代表采用长连接,

在短连接中:每一个请求/响应都需要建立一次TCP连接(三次握手),

长连接模式下:判断数据接收完成的方法有 --1--服务器关闭连接;--2--首部Content-Length判断是否传输完毕,指定了实体正文的长度。

cookie:通过在客户端记录信息确定用户身份,是客户端保存用户信息的一些机制,用于记录用户信息,每次HTTP请求,都会发送响应的cookie到服务端;服务器单从网络请求无法确定用户身份,客户端请求服务器,如果服务器需要记录用户的状态,就通过response向客户端浏览器颁发cookie,浏览器将其保存起来,再次访问时会携带cookie信息,服务器就能通过该信息辨识客户端,进一步可以修改cookie的内容。

session:通过服务端的记录确定用户身份,是在服务端保存到数据结构,用来追踪用户的状态。cookie是通过通行证来检查客户身份,session是通过检查服务器端的客户明细表来确定客户身份,他是客户第一次请求时创建的。一般在浏览完的一定时间内被服务器销毁。

TCP连接的三次握手:

第一次握手:建立连接时客户端发送syn(同步序列编号)包到服务器,进入syn_send状态,等待服务器的确认;

第二次握手:服务器收到syn包,确认客户的syn包(通过发送ack的方式,ack=syn+1),并自己发送一个syn包到客户端,服务器进入syn_recv状态;

第三次握手:客户端收到服务器的syn+ack包,自己向服务端发送确认ack包,ack=syn+1;发送完毕进入ESTABLISH状态,服务端收到确认后也进入到ESTABLISH状态,完成三次握手,客户端开始发送数据到服务端;

采用三次握手是为了避免已失效的连接请求被服务端收到,服务端误以为新连接到来,发送确认ack和syn到客户端,这是客户端收到就可以不作出响应,也就没有新的连接建立。

TCP断开的四次握手:

第一次握手:主动关闭的一方A向对方B发送FIN消息报文,想要关闭连接;

第二次握手:对方B收到这个FIN标志后先向主机A发送确认序号ACK(为了避免对方多次发送FIN消息),然后通知自己对方要关闭连接;

第三次握手:主机B处理完成,向A发送FIN消息,表示自己也愿意关闭这个连接;

第四次握手:主机A收到FIN报文,向B发送一个ACK确认消息,并进入TIME_WITE状态,时间满后自己关闭;

A需要在发送后等待2MSL的时间,一方面是为了怕自己发送的ACK B收不到,B会再次发送FIN+ACK,这样如果提前关闭可能B一直在发这个东西,另一方面避免已经失效的连接请求再次出现,经过2MSL的时间,所以自己时间内产生的报文都将消失。

C++发送HTTP请求:

1.初始化套接字:

        WSADATA wsaData;
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) == 0)
		return true;
	else{
		cout << "初始化套接字失败" << endl;
		return false;
	}

2.发送请求:

        //step2:通过域名解析主机信息
	//包括主机名,别名,地址类型,地址长度和地址列表
	struct hostent *hp = gethostbyname(host.c_str());//返回的结构记录了主机的信息
	if (hp == NULL){
		cout << "域名"<h_addr, 4);

	//建立连接
	if (0 != connect(sock, (SOCKADDR*)&sa, sizeof(sa))){
		cout << "连接失败" << endl;
		return false;
	}

	int timeout = 60000; //3s
	int m = setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char*)&timeout, sizeof(timeout));
	int n = setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout));

	//step5:向socket发送请求
	//准备http请求
	string request = "GET " + resource + " HTTP/1.1\r\nHost:" + host + "\r\nConnection:Close\r\n\r\n";
	//请求数据
	if (SOCKET_ERROR == send(sock, request.c_str(), request.size(), 0)){
		cout << "请求发送失败" << endl;
		closesocket(sock);//需要关闭套接字
		return false;
	}
	cout <<"访问 "<0){
		//本次读取到的长度,存储位置为上次读取结束的位置,长度为总长度减去上次的长度
		ret = recv(sock, buff + bytesRead, len - bytesRead, 0);
		if (ret > 0){//本次读到了数据
			bytesRead += ret;
		}
		if (len - bytesRead < 100){//长度不能满足了,需要重新分配
			len = len * 2;
			char*newbuff = new char[len];
			memset(newbuff, 0, len);
			memcpy(newbuff, buff, len / 2);
			delete[] buff;
			buff = newbuff;
		}
		cout << "*";
	}
	buff[bytesRead] = '\0';//添加结束标志
	res = buff;
	readSize = bytesRead;
	//step7:关闭socket
	closesocket(sock);
	cout << endl;
	cout << "获取 " << host + resource << " 数据成功" << endl;
	return true;

你可能感兴趣的:(网络编程,HTTP请求,请求报文,TCP握手,C,HTTP请求)