相信大家都知道 http 报文这东西吧?http 报文分两种,请求报文和响应报文。
请求报文
客户端向服务端发送的就是请求报文,它可告诉服务端自己需要什么样的资源,也可以将一些文件或者数据上传给服务端。
请求报文格式如下:
请求报文分为三个部分:
GET / HTTP/1.1
这个请求行表明了这次请求使用的是 GET 方法,访问的是网站的根目录,使用的 HTTP 协议版本是 1.1。
Host: www.baidu.com
Connection: keep-alive
用来携带数据
GET 方法
GET 方法是 HTTP 中最基础的方法,我们在浏览器地址栏输入网站浏览网页使用的都是 GET 方法:
http://www.islinjw.cn/okhttp_cookie_demo/checkverifycode.php
当然有时候服务器需要根据用户传递的信息去返回对应的数据,GET 方法用下面的形式传递信息给服务器:
http://www.islinjw.cn/okhttp_cookie_demo/checkverifycode.php?verifycode=qwjuy&format=json
这里告诉给服务器 verifycode=qwjuy 和 format=json ,服务器会根据用户传过来的信息返回不同的数据。
这个时候的请求行长这个样子,URL 上就携带了 GET 传递的数据:
GET /okhttp_cookie_demo/checkverifycode.php?verifycode=qwjuy&format=json HTTP/1.1
这里再说一句题外话,并不是说如果在 URL 里面没有见到 “?” 这个符号,客户端就没有传递数据给服务器。有一种叫做网页伪静态化的技术可以实现不带问号的 URL 使用 GET 方法传递数据。
POST 方法
GET 方法的参数都显示在 URl 上,这样对于诸如账户密码的敏感信息来说太不安全,而且也很难传递想图片这样的数据。所以就有了 POST 方法。
使用 POST 方法传递的数据并不会显示在 URL 上,而是保存在请求包体中,当然 HTTP 协议是明文传输的,所以把账户密码直接用 POST 传递也是不安全的,需要程序员自己进行加密处理。
HTTP 协议方法列表
| 序号 |
方法 |
描述 |
| 1 |
GET |
请求指定的页面信息,并返回实体主体。 |
| 2 |
HEAD |
类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头 |
| 3 |
POST |
向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改。 |
| 4 |
PUT |
从客户端向服务器传送的数据取代指定的文档的内容。 |
| 5 |
DELETE |
请求服务器删除指定的页面。 |
| 6 |
CONNECT |
HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。 |
| 7 |
OPTIONS |
允许客户端查看服务器的性能。 |
| 8 |
TRACE |
回显服务器收到的请求,主要用于测试或诊断。 |
| 9 |
PATCH |
实体中包含一个表,表中说明与该URI所表示的原内容的区别。 |
| 10 |
MOVE |
请求服务器将指定的页面移至另一个网络地址。 |
| 11 |
COPY |
请求服务器将指定的页面拷贝至另一个网络地址。 |
| 12 |
LINK |
请求服务器建立链接关系。 |
| 13 |
UNLINK |
断开链接关系。 |
| 14 |
WRAPPED |
允许客户端发送经过封装的请求。 |
| 15 |
Extension-mothed |
在不改动协议的前提下,可增加另外的方法。 |
响应报文
服务端接收到请求报文之后,了解到客户端需要什么样的服务之后就会返回响应报文给客户端。
响应报文格式如下:
HTTP/1.1 200 OK
Date: Fri, 04 Mar 2016 11:04:01 GMT
Server: Apache/2.4.7 (Ubuntu)
X-Powered-By: PHP/5.5.9-1ubuntu4.14
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0
Pragma: no-cache
Content-Length: 20
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html
{“result”:”success”}
状态码
状态码由三位数字组成,第一位数字表示响应的类型,常用的状态码有五大类如下所示:
1xx:表示服务器已接收了客户端请求,客户端可继续发送请求;
2xx:表示服务器已成功接收到请求并进行处理;
3xx:表示服务器要求客户端重定向;
4xx:表示客户端的请求有非法内容;
5xx:表示服务器未能正常处理客户端的请求而出现意外错误;
使用 Socket 发送 HTTP 请求报文
我们知道 HTTP 协议是基于 TCP 的,而我们可以使用 Socket 进行 TCP 连接,所以在充分理解 HTTP 报文之后我们就可以用 socket 实现自己的 HTTP 访问了。
访问网页
首先我们看看怎样用 socket 实现 http 访问网页,这里我们尝试使用 GET 方法访问 www.islinjw.cn。
流程如下:
- 使用 socket 连接服务器
- 发送请求报文
- 接收响应报文
- 断开 socket 连接
重点在于发送请求报文,其他步骤和一般的 socket 程序是没有什么区别的。
请求报文分为三个部分还记得吗?
- 请求行
使用 HTTP/1.1 协议的 GET 方法访问网站的根目录:
GET / HTTP/1.1
- 请求头部
Host 是请求头部唯一必须携带的数据,要不然能接收到数据,但服务器返回302、400这样的错误代码。原因是服务器可能使用了虚拟服务器技术,一台服务器托管了多个网站,即多个网站通过dns解析到同样的ip地址。像这里我们访问 www.islinjw.cn 主机:
Host: www.islinjw.cn
- 请求实体:
但我们这里因为只是单纯的获取页面,并没有传递数据给服务器,所以报文实体为空。
每个部分之间使用 “\r\n” 分割。但需要在请求报文的最后加多一个 “\n”。为什么?还记得请求头部和请求实体之间有一个什么东西吗?对,空行!因为这里没有请求实体,所以报文最后就是一个空行。如果没有它,服务器不会返回响应报文,程序就会一直阻塞在那里。
所以最终发送的报文就是:
GET / HTTP/1.1\r\nHost: www.islinjw.cn\r\n\n
代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| void TestRequest(){ void TestRequest(){ SOCKET sock_client = Connect(SERVER_IP.c_str());
string data = "";
data += "GET / HTTP/1.1\r\n";
data += "Host: www.islinjw.cn\r\n\n";
send(sock_client,data.c_str(),data.size(),0);
PrintRecvData(sock_client); Disconnect(sock_client); }
|
服务器返回的响应报文如下(对,这个网站就是一个 hello world 在那里而已):
使用 GET 方法
为了验证是否真的传送了数据给服务器,我写了一个 demo 页面 www.islinjw.cn/http_packet_demo/demo.php。这个页面的功能很简单,就是把接收到的 GET 数据和 POST 数据通过 json 格式打印出而已:
我们首先写一个函数用来把 map 转化成 GET 方法的参数格式:
1 2 3 4 5 6 7 8 9 10 11 12
| string MsgToString(const map<string,string>& msg){ string result = ""; map<string,string>::const_iterator i = msg.begin(); while(i!=msg.end()){ result += i->first + "=" + i->second; i++; if(i!=msg.end()){ result += "&"; } } return result; }
|
之前提到,GET 方法的数据是通过 URL 来传递的,所以只需要把得到的 GET 方法参数拼接到请求行的 URL 后面就行了:
1 2 3 4 5 6 7 8
| string url = "/http_packet_demo/demo.php"; url += "?" + MsgToString(msg); cout<<"url : "<<url<<endl;
string packet = "";
packet += "GET " + url + " HTTP/1.1\r\n";
|
其他的和刚刚讲的访问网页的方式一模一样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| void TestGet(const map<string,string>& msg){ SOCKET sock_client = Connect(SERVER_IP.c_str());
string url = "/http_packet_demo/demo.php"; url += "?" + MsgToString(msg);
cout<<"url : "<<url<<endl;
string packet = "";
packet += "GET " + url + " HTTP/1.1\r\n";
packet += "Host: www.baidu.com\r\n";
packet += "\n";
send(sock_client, packet.c_str(), packet.size(),0);
PrintRecvData(sock_client); Disconnect(sock_client); }
|
我们这样调用:
1 2 3 4 5
| map<string,string> msg; msg["abc"] = "123"; msg["def"] = "456";
TestGet(msg);
|
URL 长这个样子:
服务器返回的响应报文如下::
使用 POST 方法
使用 POST 方法会复杂那么一点点。首先请求行没有什么特别的,就是指定了 POST 方法和我们的页面,而且 URL 没有带数据:
1 2 3 4 5 6
| string url = "/http_packet_demo/demo.php";
string packet = "";
packet += "POST " + url + " HTTP/1.1\r\n";
|
但因为 POST 携带的数据不一定是字符串,有可能是图片等二进制图片,所以就需要在请求头部告诉服务器携带的数据的类型和数据的长度:
1 2 3 4
| packet += "Host: www.islinjw.cn\r\n"; packet += "Content-Type:application/x-www-form-urlencoded\r\n"; packet += "Content-Length: " + ss.str() + "\r\n";
|
之后就是一个空行和携带了数据的请求实体了:
1 2 3 4 5
| packet += "\n";
packet += data;
|
所以整个方法长这个样子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| void TestPost(const map<string,string>& msg){ string data = MsgToString(msg);
stringstream ss; ss<<data.length();
SOCKET sock_client = Connect(SERVER_IP.c_str());
string url = "/http_packet_demo/demo.php";
string packet = "";
packet += "POST " + url + " HTTP/1.1\r\n";
packet += "Host: www.islinjw.cn\r\n"; packet += "Content-Type:application/x-www-form-urlencoded\r\n"; packet += "Content-Length: " + ss.str() + "\r\n";
packet += "\n";
packet += data;
send(sock_client, packet.c_str(), packet.size(),0);
PrintRecvData(sock_client); Disconnect(sock_client); }
|
发送的数据如下:
1 2 3 4 5
| map<string,string> msg; msg["abc"] = "123"; msg["def"] = "456";
TestPost(msg);
|
服务器返回的响应实体如下:
demo 完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178
| #include "stdafx.h"
#include <Winsock2.h> #include <iostream> #include <map> #include <string> #include <sstream>
#pragma comment( lib, "ws2_32.lib" )
using namespace std;
const string SERVER_IP = "182.254.231.66";
SOCKET Connect(const char* ip){
WORD wVersionRequested; WSADATA wsaData; int err;
wVersionRequested = MAKEWORD( 1, 1 );
err = WSAStartup( wVersionRequested, &wsaData ); if ( err != 0 ) { return INVALID_SOCKET; }
if ( LOBYTE( wsaData.wVersion ) != 1 || HIBYTE( wsaData.wVersion ) != 1 ) { WSACleanup( ); return INVALID_SOCKET; }
SOCKET sock_client=socket(AF_INET,SOCK_STREAM,0);
SOCKADDR_IN addrSrv; addrSrv.sin_addr.S_un.S_addr=inet_addr(ip); addrSrv.sin_family=AF_INET; addrSrv.sin_port=htons(80); connect(sock_client,(SOCKADDR*)&addrSrv,sizeof(SOCKADDR));
return sock_client; }
void Disconnect(SOCKET sock_client){ closesocket(sock_client); WSACleanup(); }
void PrintRecvData(SOCKET sock_client){ cout<<"Recv data :"<<endl;
int len = 0; char recvBuf[1024]; while((len = recv(sock_client,recvBuf,1023,0))>0){ recvBuf[len] = '\0'; cout<<recvBuf; } cout<<endl; }
void TestRequest(){ SOCKET sock_client = Connect(SERVER_IP.c_str());
string data = "";
data += "GET / HTTP/1.1\r\n";
data += "Host: www.islinjw.cn\r\n\n";
send(sock_client,data.c_str(),data.size(),0);
PrintRecvData(sock_client); Disconnect(sock_client); }
string MsgToString(const map<string,string>& msg){ string result = ""; map<string,string>::const_iterator i = msg.begin(); while(i!=msg.end()){ result += i->first + "=" + i->second; i++; if(i!=msg.end()){ result += "&"; } } return result; }
void TestGet(const map<string,string>& msg){ SOCKET sock_client = Connect(SERVER_IP.c_str());
string url = "/http_packet_demo/demo.php"; url += "?" + MsgToString(msg);
cout<<"url : "<<url<<endl;
string packet = "";
packet += "GET " + url + " HTTP/1.1\r\n";
packet += "Host: www.baidu.com\r\n";
packet += "\n";
send(sock_client, packet.c_str(), packet.size(),0);
PrintRecvData(sock_client); Disconnect(sock_client); }
void TestPost(const map<string,string>& msg){ string data = MsgToString(msg);
stringstream ss; ss<<data.length();
SOCKET sock_client = Connect(SERVER_IP.c_str());
string url = "/http_packet_demo/demo.php";
string packet = "";
packet += "POST " + url + " HTTP/1.1\r\n";
packet += "Host: www.islinjw.cn\r\n"; packet += "Content-Type:application/x-www-form-urlencoded\r\n"; packet += "Content-Length: " + ss.str() + "\r\n";
packet += "\n";
packet += data;
send(sock_client, packet.c_str(), packet.size(),0);
PrintRecvData(sock_client); Disconnect(sock_client); }
int main(int argc, char* argv[]) { map<string,string> msg; msg["abc"] = "123"; msg["def"] = "456";
TestRequest(); cout<<"\n\n\n"<<endl; TestGet(msg); cout<<"\n\n\n"<<endl; TestPost(msg);
return 0; }
|