HTTP - TCP实现HTTP GET请求 (2)
2016-08-22 17:13
211 查看
我们尝试用TCP来实现HTTP GET
基本TCP通信
首先写一段TCP通信的例子。这是一些测试代码,不要看代码质量,我们目的只是验证。
服务器:
先写个服务器,这里用了select。
客户端:
先运行服务端,在运行客户端,就会得到一下结果:
我们可以看到客户端收到了服务端的数据,其实就是客户端自己发给服务端的数据hello world.abc
这就是一个很简单的TCP通信例子。
如果配合前面介绍的,我们可以知道,当客户端发送数据"hello world.abc"的时候,send函数内部会加上TCP头,然后网络层会加上IP头,然后数据链路层会加上以太网头。
最终在物理网路上传播的数据类似:以太网头 + IP头 + TCP头 + 用户层数据(hello world.abc)。当然如果用户层数据多的话,一帧放不下,还会分片传播。
当服务器从网卡上收到数据的时候,就是反过来,从数据链路层=》网络层=》传输层=》应用层,一步一步把头给去掉,最终得到数据hello world.abc
应用层协议
到底什么是应用层协议呢?
我们上面的例子是没有什么意义的。假如我们的场景是用户登录,那么客户端就需要发送用户名和密码。怎么发呢?
简单点,我们约定好,客户端发上来的就是格式: 用户名;密码。也就是中间用分号隔开。
那么当服务器收到的时候就知道用分号来分割字符串,得到用户名和密码。
这就是一个自定义协议。
所谓协议,其实就是通信双方事先约定好的一些规则。
当然上面的协议只是一个例子,很傻的。
实际应用不太会用这么啥的办法,协议总是要尽可能定义的完善,通用。比如HTTP, FTP等,都是常见的协议。
HTTP协议
有关HTTP协议,可以查看RFC文档,里面有具体的定义,这里只是用TCP自己模拟一下HTTP访问。
以百度为例,先抓包看看浏览器访问百度的时候到底发送了什么数据包。我这里使用soapui来看:
可以很清楚的看到左边的HTTP GET 请求。
那么我们可以把刚才的代码改一下:
然后发送的数据是:
运行一下:
得到了服务器的返回。当然这里只是傻傻的收固定的字节,实际上是应该根据HTTP协议把所有的数据收完的。这里只是例子,无所谓。
所以说,HTTP是应用层协议,它是基于TCP协议实现(听说也有基于UDP的,但是少见)。
HTTP一般是无状态的,也就是收到服务器的响应就断开连接了,实际上这也只是HTTP协议的一种规定而已。
实际上,我们不太可能自己用TCP去实现HTTP,像这种通用的协议,别人早就实现好了,拿来用就行,我们所要知道的是HTTP到底是怎么回事,怎么来的。
至于HTTP的具体细节,查看RFC文档就行了,像什么GET, POST, PUT, DELETE, KEEP-ALIVE, CHUNKED等等。
附
服务器代码:
客户端代码:
基本TCP通信
首先写一段TCP通信的例子。这是一些测试代码,不要看代码质量,我们目的只是验证。
服务器:
先写个服务器,这里用了select。
// SelectServer.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include <winsock2.h> #include "boost/timer.hpp" #pragma comment(lib,"ws2_32.lib") #define PORT 1688 bool InitAndListen(SOCKET &sListen) { WSADATA wsaData; sockaddr_in local; WORD version=MAKEWORD(2,0); int ret=WSAStartup(version,&wsaData); if(ret != 0) { printf("WASStarup failed/n"); return 0; } local.sin_family=AF_INET; local.sin_addr.s_addr=INADDR_ANY; local.sin_port=htons((u_short)PORT); //Initial socket sListen=socket(AF_INET,SOCK_STREAM,0); if(sListen == INVALID_SOCKET) { printf("Initial socket failed/n"); return 0; } //Bind socket if(bind(sListen,(sockaddr*)&local,sizeof(local))!=0) { printf("Bind socket failed/n"); return 0; } //Listen socket if(listen(sListen,10)!=0) { printf("Listen socket failed"); return 0; } return 1; } void RunService() { SOCKET sListen; if(InitAndListen(sListen) == 0) { return; } printf("Server wait for client connect...\n"); fd_set fdSocket; FD_ZERO(&fdSocket); //Add the listen socket to FD_set : fdSocket FD_SET(sListen,&fdSocket); while (true) { //assign the fdSocket to fdRead to select fd_set fdRead = fdSocket; timeval t; t.tv_sec = 2; t.tv_usec = 1; boost::timer elapsed; int nRet = select(NULL,&fdRead,NULL,NULL,NULL);//设置timeout为NULL,那么select会一直等下去,最多支持64个 printf("select elapsed: %f s\n", elapsed.elapsed()); if (nRet <= 0) { printf("select failed/n"); break; } for(int i=0;i<(int)fdSocket.fd_count;i++) { //check whether the socket is set if(FD_ISSET(fdSocket.fd_array[i],&fdRead)) { //New connect come if(fdSocket.fd_array[i] == sListen) { sockaddr_in addrRemote; int nAddrLen=sizeof(addrRemote); SOCKET sNew=::accept(sListen,(sockaddr*)&addrRemote,&nAddrLen); FD_SET(sNew,&fdSocket);//Put it to fdSocket sets printf("Client %s connected\n",inet_ntoa(addrRemote.sin_addr)); } else { char buffer[1024]; memset(buffer,0,1024); int nRecev = recv(fdSocket.fd_array[i],buffer, 1024,0); if (nRecev > 0) { printf("Received Client Msg:%s\n",buffer); //echo back ::send(fdSocket.fd_array[i],buffer, strlen(buffer),0); } else { printf("client disconnected\n"); //Close the socket and clear from the sets closesocket(fdSocket.fd_array[i]); FD_CLR(fdSocket.fd_array[i],&fdSocket); } } } } } } int _tmain(int argc, _TCHAR* argv[]) { RunService(); return 0; }这个服务器很简单,就是收到客户连接,如果客户有数据发过来,就再发回去。
客户端:
int Test1() { WSADATA wsaData; int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); if (iResult != NO_ERROR) { wprintf(L"WSAStartup function failed with error: %d\n", iResult); return 1; } //---------------------- // Create a SOCKET for connecting to server SOCKET ConnectSocket; ConnectSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (ConnectSocket == INVALID_SOCKET) { wprintf(L"socket function failed with error: %ld\n", WSAGetLastError()); WSACleanup(); return 1; } //---------------------- // The sockaddr_in structure specifies the address family, // IP address, and port of the server to be connected to. sockaddr_in clientService; clientService.sin_family = AF_INET; clientService.sin_addr.s_addr = inet_addr("127.0.0.1"); clientService.sin_port = htons(1688); //---------------------- // Connect to server. iResult = connect(ConnectSocket, (SOCKADDR *) & clientService, sizeof (clientService)); if (iResult == SOCKET_ERROR) { wprintf(L"connect function failed with error: %ld\n", WSAGetLastError()); iResult = closesocket(ConnectSocket); if (iResult == SOCKET_ERROR) wprintf(L"closesocket function failed with error: %ld\n", WSAGetLastError()); WSACleanup(); return 1; } wprintf(L"Connected to server.\n"); char buf[] = "hello world.abc"; send(ConnectSocket, buf, sizeof(buf), 0); char received[100] = {0}; int r = recv(ConnectSocket, received, 100, 0); iResult = closesocket(ConnectSocket); if (iResult == SOCKET_ERROR) { wprintf(L"closesocket function failed with error: %ld\n", WSAGetLastError()); WSACleanup(); return 1; } WSACleanup(); }很简单的客户端代码,往服务器发一些数据,再接收服务器的数据。
先运行服务端,在运行客户端,就会得到一下结果:
我们可以看到客户端收到了服务端的数据,其实就是客户端自己发给服务端的数据hello world.abc
这就是一个很简单的TCP通信例子。
如果配合前面介绍的,我们可以知道,当客户端发送数据"hello world.abc"的时候,send函数内部会加上TCP头,然后网络层会加上IP头,然后数据链路层会加上以太网头。
最终在物理网路上传播的数据类似:以太网头 + IP头 + TCP头 + 用户层数据(hello world.abc)。当然如果用户层数据多的话,一帧放不下,还会分片传播。
当服务器从网卡上收到数据的时候,就是反过来,从数据链路层=》网络层=》传输层=》应用层,一步一步把头给去掉,最终得到数据hello world.abc
应用层协议
到底什么是应用层协议呢?
我们上面的例子是没有什么意义的。假如我们的场景是用户登录,那么客户端就需要发送用户名和密码。怎么发呢?
简单点,我们约定好,客户端发上来的就是格式: 用户名;密码。也就是中间用分号隔开。
那么当服务器收到的时候就知道用分号来分割字符串,得到用户名和密码。
这就是一个自定义协议。
所谓协议,其实就是通信双方事先约定好的一些规则。
当然上面的协议只是一个例子,很傻的。
实际应用不太会用这么啥的办法,协议总是要尽可能定义的完善,通用。比如HTTP, FTP等,都是常见的协议。
HTTP协议
有关HTTP协议,可以查看RFC文档,里面有具体的定义,这里只是用TCP自己模拟一下HTTP访问。
以百度为例,先抓包看看浏览器访问百度的时候到底发送了什么数据包。我这里使用soapui来看:
可以很清楚的看到左边的HTTP GET 请求。
那么我们可以把刚才的代码改一下:
int Test2() { WSADATA wsaData; int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); if (iResult != NO_ERROR) { wprintf(L"WSAStartup function failed with error: %d\n", iResult); return 1; } //---------------------- // Create a SOCKET for connecting to server SOCKET ConnectSocket; ConnectSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (ConnectSocket == INVALID_SOCKET) { wprintf(L"socket function failed with error: %ld\n", WSAGetLastError()); WSACleanup(); return 1; } //---------------------- // The sockaddr_in structure specifies the address family, // IP address, and port of the server to be connected to. sockaddr_in clientService; clientService.sin_family = AF_INET; clientService.sin_addr.s_addr = inet_addr("115.239.211.112"); clientService.sin_port = htons(80); //---------------------- // Connect to server. iResult = connect(ConnectSocket, (SOCKADDR *) & clientService, sizeof (clientService)); if (iResult == SOCKET_ERROR) { wprintf(L"connect function failed with error: %ld\n", WSAGetLastError()); iResult = closesocket(ConnectSocket); if (iResult == SOCKET_ERROR) wprintf(L"closesocket function failed with error: %ld\n", WSAGetLastError()); WSACleanup(); return 1; } wprintf(L"Connected to server.\n"); char buf[] = "GET / HTTP/1.1\r\n" "Accept-Encoding: gzip,deflate\r\n" "Host: www.baidu.com\r\n" "Connection: Keep-Alive\r\n" "User-Agent: Apache-HttpClient/4.1.1 (java 1.5)\r\n\r\n" ; send(ConnectSocket, buf, sizeof(buf), 0); char received[1000] = {0}; int r = recv(ConnectSocket, received, 1000, 0); iResult = closesocket(ConnectSocket); if (iResult == SOCKET_ERROR) { wprintf(L"closesocket function failed with error: %ld\n", WSAGetLastError()); WSACleanup(); return 1; } WSACleanup(); }这里没有写DNS解析,所以直接用百度的ip(ping一下就知道)。
115.239.211.112上面这个就是ping得到的百度ip,那么大家都知道HTTP服务的监听端口是80,所以改成80.
然后发送的数据是:
GET http://www.baidu.com/ HTTP/1.1 Accept-Encoding: gzip,deflate Host: www.baidu.com Connection: Keep-Alive User-Agent: Apache-HttpClient/4.1.1 (java 1.5)而这个就是HTTP协议的header。这个就是实现约定好的规则,服务器和客户端都遵守。
运行一下:
得到了服务器的返回。当然这里只是傻傻的收固定的字节,实际上是应该根据HTTP协议把所有的数据收完的。这里只是例子,无所谓。
所以说,HTTP是应用层协议,它是基于TCP协议实现(听说也有基于UDP的,但是少见)。
HTTP一般是无状态的,也就是收到服务器的响应就断开连接了,实际上这也只是HTTP协议的一种规定而已。
实际上,我们不太可能自己用TCP去实现HTTP,像这种通用的协议,别人早就实现好了,拿来用就行,我们所要知道的是HTTP到底是怎么回事,怎么来的。
至于HTTP的具体细节,查看RFC文档就行了,像什么GET, POST, PUT, DELETE, KEEP-ALIVE, CHUNKED等等。
附
服务器代码:
// SelectServer.cpp : Defines the entry point for the console application. // #include "stdafx.h" #include <winsock2.h> #include "boost/timer.hpp" #pragma comment(lib,"ws2_32.lib") #define PORT 1688 bool InitAndListen(SOCKET &sListen) { WSADATA wsaData; sockaddr_in local; WORD version=MAKEWORD(2,0); int ret=WSAStartup(version,&wsaData); if(ret != 0) { printf("WASStarup failed/n"); return 0; } local.sin_family=AF_INET; local.sin_addr.s_addr=INADDR_ANY; local.sin_port=htons((u_short)PORT); //Initial socket sListen=socket(AF_INET,SOCK_STREAM,0); if(sListen == INVALID_SOCKET) { printf("Initial socket failed/n"); return 0; } //Bind socket if(bind(sListen,(sockaddr*)&local,sizeof(local))!=0) { printf("Bind socket failed/n"); return 0; } //Listen socket if(listen(sListen,10)!=0) { printf("Listen socket failed"); return 0; } return 1; } void RunService() { SOCKET sListen; if(InitAndListen(sListen) == 0) { return; } printf("Server wait for client connect...\n"); fd_set fdSocket; FD_ZERO(&fdSocket); //Add the listen socket to FD_set : fdSocket FD_SET(sListen,&fdSocket); while (true) { //assign the fdSocket to fdRead to select fd_set fdRead = fdSocket; timeval t; t.tv_sec = 2; t.tv_usec = 1; boost::timer elapsed; int nRet = select(NULL,&fdRead,NULL,NULL,NULL);//设置timeout为NULL,那么select会一直等下去,最多支持64个 printf("select elapsed: %f s\n", elapsed.elapsed()); if (nRet <= 0) { printf("select failed/n"); break; } for(int i=0;i<(int)fdSocket.fd_count;i++) { //check whether the socket is set if(FD_ISSET(fdSocket.fd_array[i],&fdRead)) { //New connect come if(fdSocket.fd_array[i] == sListen) { sockaddr_in addrRemote; int nAddrLen=sizeof(addrRemote); SOCKET sNew=::accept(sListen,(sockaddr*)&addrRemote,&nAddrLen); FD_SET(sNew,&fdSocket);//Put it to fdSocket sets printf("Client %s connected\n",inet_ntoa(addrRemote.sin_addr)); } else { char buffer[1024]; memset(buffer,0,1024); int nRecev = recv(fdSocket.fd_array[i],buffer, 1024,0); if (nRecev > 0) { printf("Received Client Msg:%s\n",buffer); //echo back ::send(fdSocket.fd_array[i],buffer, strlen(buffer),0); } else { printf("client disconnected\n"); //Close the socket and clear from the sets closesocket(fdSocket.fd_array[i]); FD_CLR(fdSocket.fd_array[i],&fdSocket); } } } } } } int _tmain(int argc, _TCHAR* argv[]) { RunService(); return 0; }
客户端代码:
// TestSocket.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <string>
#pragma comment(lib, "ws2_32.lib")
using namespace std;
int Test1() { WSADATA wsaData; int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); if (iResult != NO_ERROR) { wprintf(L"WSAStartup function failed with error: %d\n", iResult); return 1; } //---------------------- // Create a SOCKET for connecting to server SOCKET ConnectSocket; ConnectSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (ConnectSocket == INVALID_SOCKET) { wprintf(L"socket function failed with error: %ld\n", WSAGetLastError()); WSACleanup(); return 1; } //---------------------- // The sockaddr_in structure specifies the address family, // IP address, and port of the server to be connected to. sockaddr_in clientService; clientService.sin_family = AF_INET; clientService.sin_addr.s_addr = inet_addr("127.0.0.1"); clientService.sin_port = htons(1688); //---------------------- // Connect to server. iResult = connect(ConnectSocket, (SOCKADDR *) & clientService, sizeof (clientService)); if (iResult == SOCKET_ERROR) { wprintf(L"connect function failed with error: %ld\n", WSAGetLastError()); iResult = closesocket(ConnectSocket); if (iResult == SOCKET_ERROR) wprintf(L"closesocket function failed with error: %ld\n", WSAGetLastError()); WSACleanup(); return 1; } wprintf(L"Connected to server.\n"); char buf[] = "hello world.abc"; send(ConnectSocket, buf, sizeof(buf), 0); char received[100] = {0}; int r = recv(ConnectSocket, received, 100, 0); iResult = closesocket(ConnectSocket); if (iResult == SOCKET_ERROR) { wprintf(L"closesocket function failed with error: %ld\n", WSAGetLastError()); WSACleanup(); return 1; } WSACleanup(); }
int Test2()
{
WSADATA wsaData;
int iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != NO_ERROR) {
wprintf(L"WSAStartup function failed with error: %d\n", iResult);
return 1;
}
//----------------------
// Create a SOCKET for connecting to server
SOCKET ConnectSocket;
ConnectSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (ConnectSocket == INVALID_SOCKET) {
wprintf(L"socket function failed with error: %ld\n", WSAGetLastError());
WSACleanup();
return 1;
}
//----------------------
// The sockaddr_in structure specifies the address family,
// IP address, and port of the server to be connected to.
sockaddr_in clientService;
clientService.sin_family = AF_INET;
clientService.sin_addr.s_addr = inet_addr("115.239.211.112");
clientService.sin_port = htons(80);
//----------------------
// Connect to server.
iResult = connect(ConnectSocket, (SOCKADDR *) & clientService, sizeof (clientService));
if (iResult == SOCKET_ERROR) {
wprintf(L"connect function failed with error: %ld\n", WSAGetLastError());
iResult = closesocket(ConnectSocket);
if (iResult == SOCKET_ERROR)
wprintf(L"closesocket function failed with error: %ld\n", WSAGetLastError());
WSACleanup();
return 1;
}
wprintf(L"Connected to server.\n");
char buf[] = "GET / HTTP/1.1\r\n"
"Accept-Encoding: gzip,deflate\r\n"
"Host: www.baidu.com\r\n"
"Connection: Keep-Alive\r\n"
"User-Agent: Apache-HttpClient/4.1.1 (java 1.5)\r\n\r\n"
;
send(ConnectSocket, buf, sizeof(buf), 0);
char received[2000] = {0};
int r = recv(ConnectSocket, received, 2000, 0);
iResult = closesocket(ConnectSocket);
if (iResult == SOCKET_ERROR) {
wprintf(L"closesocket function failed with error: %ld\n", WSAGetLastError());
WSACleanup();
return 1;
}
WSACleanup();
}
int main(int argc, char **argv)
{
Test2();
return 0;
}
相关文章推荐
- C#实现http协议支持上传下载文件的GET、POST请求
- C# 实现HTTP GET POST请求
- httpcomponents-client-4.3.5实现http的post请求和get请求方法
- ASIHTTPRequest实现对PUT,DELETE,POST,GET请求方式的处理
- socket编程实现http GET请求
- java HTTP get post请求,获取二进制文件实现
- JAVA Socket 实现HTTP与HTTPS客户端发送POST与GET方式请求
- C#实现http协议GET、POST请求 2010-10-11 16
- Android HTTP实例 使用GET方法和POST方法发送请求(通过Apache接口实现)
- Java实现http get post请求
- 【转】C# 实现http协议GET、POST请求
- JAVA Socket 实现HTTP与HTTPS客户端发送POST与GET方式请求
- C#实现http协议GET、POST请求
- socket编程---实现get post 向http发送请求
- C#实现http协议支持上传下载文件的GET、POST请求
- C#实现请求服务器,类似于asp下的getHTTPPage(url)功能
- C++ 简单实现HTTP GET/POST 请求
- JAVA Socket 实现HTTP与HTTPS客户端发送POST与GET方式请求
- linux C 实现HTTP get 及post 请求
- C#实现http协议支持上传下载文件的GET、POST请求