HTTP - TCP实现HTTP GET请求 (2)

我们尝试用TCP来实现HTTP GET


基本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;
}








猜你喜欢

转载自blog.csdn.net/zj510/article/details/52278144