概述
转载自:https://blog.csdn.net/liubailiuestc/article/details/79828896
http协议简析及C++实现HTTP请求
liubailiuestc 2018-04-07 21:12:39
14436
收藏 22
分类专栏: 网络编程 文章标签: HTTP请求 请求报文 TCP握手 C HTTP请求
版权
网络编程 专栏收录该内容
1 篇文章 0 订阅
订阅专栏
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的头部信息,每一个用rn分割;
常用的首部有:
-----Connection:允许客户端和服务器指定与请求/响应连接有关的选项(长连接,短连接等)
-----Data:提供日期和时间标志
-----MIME-Version:给出了发送端使用的MIME版本
-----Client-IP:描述客户端机器的IP
-----From:提供了客户端用户的E-mail地址
-----Host:给出请求的主机名和端口号
-----Referer:提供了包含当前请求的URL的文档的URL
-----cookie:向服务器传送一个令牌
<3>请求正文为发送给服务器的查询信息(使用get时,body是空的,get只能读取,而post可以写入信息);
"GET / HTTP/1.1rnHost:www.dytt8.netrnConnection:Closernrn"---请求行rn请求首部1rn请求首部2rnrn报文实体内容
响应报文
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
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" " http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns=" http://www.w3.org/1999/xhtml">
<head>
<META http-equiv=Content-Type content="text/html; charset=gb2312">
<title>电影天堂_免费电影_迅雷电影下载</title>
<META content="免费电影下载,电影下载,最新电影" name=keywords>
<META content="最好的迅雷电影下载网,分享最新电影,高清电影、综艺、动漫、电视剧等下载!" name=description>
响应报文也包括请求行,请求首部,空行,实体
请求行格式为:http版本+状态码+短语+rn 短语与状态码相对应
请求首部格式为:一系列首部名称:值rn的组合
请求实体为:服务端真正返回的信息
状态编码为:
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 << "域名"<<host<<"解析不成功,可能是网址错误" << endl;
-
return false;
-
}
-
//step3:初始化socket
-
//指定了协议族(ipv4和16位端口),socket类型(面向连接),协议(TCP传输)
-
SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
-
if (sock == -1 || sock == -2){
-
cout << "套接字创建不成功" << endl;
-
return false;
-
}
-
//step4:与socket建立连接
-
SOCKADDR_IN sa;
-
sa.sin_family = AF_INET;
-
sa.sin_port = htons(80);
-
memcpy(&sa.sin_addr, hp->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.1rnHost:" + host + "rnConnection:Closernrn";
-
//请求数据
-
if (SOCKET_ERROR == send(sock, request.c_str(), request.size(), 0)){
-
cout << "请求发送失败" << endl;
-
closesocket(sock);//需要关闭套接字
-
return false;
-
}
-
cout <<"访问 "<<host+resource<<" 成功" << endl;
-
//step6:接收socket的请求
-
//接收数据
-
int len = MAX_LEN;
-
char*buff = new char[len];
-
memset(buff, 0, len);
-
int bytesRead = 0;//已经读到的长度
-
int ret = 1;
-
while (ret>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] = '