首页 > 代码库 > 简易HTTP代理的实现

简易HTTP代理的实现

     编写一个简易的HTTP代理服务器,步骤其实很简单:

     1.设置一个监听套接字gListen_Socket;

     2.每当接受到客户端的请求之后,我们构建一个新的线程来继续监听客户端的请求,然后原线程处理用户请求;

     3.先从用户发送的HTTP请求中解析出服务器端的主机地址,然后通过另外一个线程连接到服务器;

     4.本程序充当中介,不断转发来自两端的消息;

     5.通信结束后,关闭套接字即可.

      这个程序当中,有一些函数可能不太熟悉windows编程的同学不太懂,因此,我建议你先去看一看《VC++深入详解》这本书,就看后面的一些网络和多线程的章节!看懂了之后再回过头来看这个程序理解起来会好很多!

      我们先看程序吧!

      事先说一句:我这个程序是在vs中编写的,vc6可能无法运行,因为vs对vc6做了一些改进,但其实在vc6下你只要改动几个程序就可以了,如sscanf_s改为sscanf,strcpy_s改为strcpy,当然参数可能也要做一些变化,不过很容易更正,我这里就不再一一叙述了!(XXX_s是对于原来的一些不安全的函数XXX的一个安全版本。)

#include <iostream>
#include <WinSock2.h>
#include <windows.h>
#include <string.h>
using namespace std;
#pragma  comment(lib, "ws2_32.lib")	/*链接ws2_32.lib动态链接库*/

const int BUFSIZE = 10240;
const int USERPORT = 8888; //客户端连接的端口号
const char userAddr[16] = "127.0.0.1"; //客户端连接的ip地址
#define HTTP "http://"

struct SocketPair{
	SOCKET user_proxy; //socket : 本地机器到PROXY 服务机
	SOCKET proxy_server; //socket : PROXY 服务机到远程主机
	BOOL IsUser_ProxyClosed; // 客户端到PROXY 服务机状态
	BOOL IsProxy_ServerClosed; // PROXY 服务机到服务器状态
};


struct ProxyParam{
	char Address[256]; // 服务端地址
	HANDLE User_SvrOK; // 这个句柄主要用来实现同步
	SocketPair *pPair; 
	CRITICAL_SECTION *g_cs; //用来实现线程同步的关键段
	int Port; // 连接服务端使用的端口号
}; 

SOCKET gListen_Socket; //用来侦听的SOCKET

DWORD WINAPI ProxyToServer(LPVOID pParam);
DWORD WINAPI UserToProxyThread(LPVOID pParm);


int CloseServer()
{
	closesocket(gListen_Socket);
	WSACleanup();
	return 1;
}

//分析接收到的字符,得到远程主机地址
BOOL GetAddressAndPort(char * str, char *address, int * port)
{
	char buf[BUFSIZE], command[512], proto[128], *p;
	sscanf_s(str, "%s%s%s", command, 512, buf, BUFSIZE, proto, 128);//从str里面提取出数据
	p = strstr(buf, HTTP);//buff里面含有域名信息
	//HTTP
	if (p)
	{
		//以 http://www.njust.edu.cn/ 这个为例,现在p指向 http://www.njust.edu.cn/ 这个字符串
		p += strlen(HTTP); // 现在p指向 www.njust.edu.cn/
		int i;
		for (i = 0; i < (int)strlen(p); i++)
		{
			if (*(p + i) == '/' || *(p + i) == ':') break;
		}

		if (*(p + i) == '/')
		{
			*(p + i) = 0;// www.njust.edu.cn/ 将最后一位置为NULL ==> www.njust.edu.cn
			*port = 80; //缺省的 http 端口
			strcpy_s(address, 256, p); //成功提取出域名信息
			p = strstr(str, HTTP);
			for (int j = 0; j < i + (int)strlen(HTTP); j++)
				*(p + j) = ' '; //去掉远程主机名: GET http://www.njust.edu.cn/ HTTP1.1 == > GET / HTTP1.1
		}
		else //这种情况主要是为了应对http://211.67.208.118:10000这种情况
		{
			*port = atoi(p + i + 1);
			*(p + i) = 0;
			strcpy_s(address, 256, p); //成功提取出域名信息
			p = strstr(str, HTTP);
			for (int j = 0;; j++)
			{
				if (j > 7 && *(p + j) == '/')
				{
					*(p + j) = ' ';
					break;
				}
				*(p + j) = ' '; //去掉远程主机名: GET http://211.67.208.118:10000/ HTTP1.1 == > GET / HTTP1.1
			}
				
		}
		
	}
	else
	{   
		//cout << "不支持的协议类型" << endl;
		//return FALSE;
		return FALSE;
	}
	return TRUE;
}

void StartServer()
{
	WORD wVersionRequested;
	WSADATA wsaData;
	int err;

	wVersionRequested = MAKEWORD(2, 1);
	err = WSAStartup(wVersionRequested, &wsaData);

	//创建用于监听的套接字
	SOCKET sockBlind = socket(AF_INET, SOCK_STREAM, 0);

	SOCKADDR_IN local;
	local.sin_addr.S_un.S_addr = inet_addr(userAddr);
	local.sin_family = AF_INET;
	local.sin_port = htons(USERPORT);

	//绑定套接字
	bind(sockBlind, (SOCKADDR*)&local, sizeof(SOCKADDR));
	//将套接字设为监听模式
	listen(sockBlind, 7);
	gListen_Socket = sockBlind;
	CreateThread(NULL, 0, UserToProxyThread, NULL, 0, NULL); //启动侦听
}

DWORD WINAPI UserToProxyThread(LPVOID pParm)
{
	char Buffer[BUFSIZE];
	int Len;
	sockaddr_in from;
	int fromlen = sizeof(from);

	SOCKET msg_socket;
	//cout << "连接客户端进程---准备接收来自客户端的请求…………" << endl;
	msg_socket = accept(gListen_Socket, (sockaddr *)&from, &fromlen); //监听来自本地主机的信息
	cout << "连接客户端进程---成功接收到了客户端的请求…………" << endl;

	HANDLE useToProxy = CreateThread(NULL, 0, UserToProxyThread, NULL, 0, NULL); //启动另一侦听

	SocketPair Spair;
	ProxyParam ProxyP; //用于维护远程服务器状态的数据结构

	Spair.IsUser_ProxyClosed = FALSE; //表示已经连接到了客户端
	Spair.IsProxy_ServerClosed = TRUE; //表示还没有连接到远程服务器
	Spair.user_proxy = msg_socket; //将对应的连接到客户端的套接字存储起来

	int retval = recv(Spair.user_proxy, Buffer, sizeof(Buffer), 0); //接收本地应用程序发送过来的信息
	
	if (retval == SOCKET_ERROR)
	{
		cout << "连接客户端进程---接收出现了错误…………" << endl;
		if (Spair.IsUser_ProxyClosed == FALSE)
		{
			closesocket(Spair.user_proxy);	//关闭客户端连接
			Spair.IsUser_ProxyClosed = TRUE; 
			return 0; //返回的话,意味着这个线程的结束
		}
	}
	if (retval == 0)
	{
		cout << "连接客户端进程---客户端关闭了连接…………" << endl;
		if (Spair.IsUser_ProxyClosed == FALSE)
		{
			closesocket(Spair.user_proxy);	//关闭客户端连接
			Spair.IsUser_ProxyClosed = TRUE; 
			return 0;
		}
	}
	cout << "连接客户端进程---成功接收到用户数据…………" << endl;

	Len = retval;

	
	Spair.IsUser_ProxyClosed = FALSE;  //已经连接到了本地机器
	Spair.IsProxy_ServerClosed = TRUE; //还没有连接到远程服务器
	Spair.user_proxy = msg_socket;    //对应的连接到本地机的套接字
	
	ProxyP.pPair = &Spair;

	//创建人工重置事件的内核对象
	ProxyP.User_SvrOK = CreateEvent(NULL,	/*使用默认的安全属性*/
		TRUE,   /*人工重置*/
		FALSE,/*初始无信号状态*/
		NULL);/*匿名的事件对象*/

	if (FALSE == GetAddressAndPort(Buffer, ProxyP.Address, &ProxyP.Port)) //获得服务器端的地址信息
	{
		cout << "地址解析失败!" << endl;
		return 0;
	}
	CRITICAL_SECTION critical;
	CRITICAL_SECTION *g_cs = &critical;
	ProxyP.g_cs = g_cs;
	InitializeCriticalSection(g_cs);
	/*开启一个线程用于连接至服务器端*/
	HANDLE pChildThread = CreateThread(NULL, 0, ProxyToServer, (LPVOID)&ProxyP, 0, NULL);

	//由于ProxyP.User_SvrOK初始无信号状态,这个线程会一直等待
	//SetEvent函数在连接服务端进程中调用,也就是说,只有当服务器连接成功了之后,或者等待超时之后,才能继续运行
	WaitForSingleObject(ProxyP.User_SvrOK, 60000); //申请事件对象,起到了一个同步的作用
	CloseHandle(ProxyP.User_SvrOK); //让计数器减1

	//注意到,这里是一个循环
	while (Spair.IsProxy_ServerClosed == FALSE && Spair.IsUser_ProxyClosed == FALSE)//服务器和客户端都已经连接成功
	{
		//cout << "连接客户端进程---向服务器发送数据…………" << endl;
		retval = send(Spair.proxy_server, Buffer, Len, 0); //向服务器端发送从本地接收到的数据
		
		if (retval <= 0) break; //出错就退出
		cout << "连接客户端进程---向服务器发送数据成功…………" << endl;

		retval = recv(Spair.user_proxy, Buffer, sizeof(Buffer), 0); //从本地机器接收数据
		if (retval <= 0)  break;//出错就退出
		
		Len = retval;
		cout << "连接客户端进程---从客户端接收数据成功…………" << endl;
	} //End While
	//下面的代码用于关闭连接
	if (Spair.IsUser_ProxyClosed == FALSE)
	{
		EnterCriticalSection(g_cs);
		closesocket(Spair.proxy_server);
		Spair.IsProxy_ServerClosed = TRUE;
		LeaveCriticalSection(g_cs);
	}
	if (Spair.IsUser_ProxyClosed == FALSE)
	{
		LeaveCriticalSection(g_cs);
		closesocket(Spair.user_proxy);
		Spair.IsUser_ProxyClosed = TRUE;
		LeaveCriticalSection(g_cs);
	}
	WaitForSingleObject(pChildThread, 20000); //这个函数主要是等待连接服务器的线程退出
	DeleteCriticalSection(g_cs);

	cout << endl;
	return 1;
}

DWORD WINAPI ProxyToServer(LPVOID pParam)
{
	ProxyParam * pPar = (ProxyParam*)pParam;
	CRITICAL_SECTION *g_cs = pPar->g_cs;
	char Buffer[BUFSIZE];
	char *server_name = "localhost";
	unsigned short port;  //端口

	int retval, Len;
	unsigned int addr;
	int socket_type;

	struct sockaddr_in server;
	struct hostent *hp;
	SOCKET conn_socket;

	socket_type = SOCK_STREAM;
	server_name = pPar->Address; //服务器的域名或者IP地址
	port = pPar->Port; //服务器的端口

	if (isalpha(server_name[0]))  /*名称为域名的话*/
	{
		hp = gethostbyname(server_name);
	}
	else /*否则的话就是ip地址了*/
	{ 
		addr = inet_addr(server_name);
		hp = gethostbyaddr((char *)&addr, 4, AF_INET);
	}
	if (hp == NULL) 
	{
		cout << "连接服务端进程---无法完成地址转化" << '[' << server_name << ']:' << WSAGetLastError() << endl;
		SetEvent(pPar->User_SvrOK);
		return 0;
	}

	//构建要连接到的服务器的信息
	memset(&server, 0, sizeof(server));
	memcpy(&(server.sin_addr), hp->h_addr, hp->h_length);
	server.sin_family = hp->h_addrtype;
	server.sin_port = htons(port);

	conn_socket = socket(AF_INET, socket_type, 0); //创建一个 socket 
	if (conn_socket < 0)
	{
		cout << "连接服务端进程---创建socket的时候发生错误:" << WSAGetLastError() << endl;
		pPar->pPair->IsProxy_ServerClosed = TRUE;
		SetEvent(pPar->User_SvrOK); //这里将pPar->User_SvrOK设置为有信号状态
		return 0;
	}

	if (connect(conn_socket, (struct sockaddr*)&server, sizeof(server)) == SOCKET_ERROR)/*连接到远程服务器*/
	{
		cout << "连接服务端进程---连接服务器失败:" << WSAGetLastError() << endl;
		pPar->pPair->IsProxy_ServerClosed = TRUE;
		SetEvent(pPar->User_SvrOK);//这里将pPar->User_SvrOK设置为有信号状态
		return 0;
	}
	cout << "连接服务端进程---成功连接到了服务器…………" << endl;
	pPar->pPair->proxy_server = conn_socket; //将已经连接到服务器的套接字存储起来
	pPar->pPair->IsProxy_ServerClosed = FALSE; //表示已经连接到了服务器
	SetEvent(pPar->User_SvrOK); //连接成功,使pPar->User_SvrOK变为有信号状态,以便于向服务器发送数据

	while (!pPar->pPair->IsProxy_ServerClosed &&!pPar->pPair->IsUser_ProxyClosed)//已经连接到了服务器与客户端
	{
		//cout << "连接服务端进程---正准备从服务器接收数据…………" << endl;
		retval = recv(conn_socket, Buffer, sizeof (Buffer), 0);//从服务器接收数据
		if (retval <= 0) break;
		
		Len = retval;
		cout << "连接服务端进程---接收服务器数据成功…………" << endl;

		retval = send(pPar->pPair->user_proxy, Buffer, Len, 0); //向客户端发送数据

		if (retval <= 0) break;
		
		cout << "连接服务端进程---向客户端发送数据成功…………" << endl;
	}
	//下面的代码用于关闭连接
	if (pPar->pPair->IsUser_ProxyClosed == FALSE)
	{
		EnterCriticalSection(g_cs); //关键代码段,用于线程间的同步
		closesocket(pPar->pPair->proxy_server);
		pPar->pPair->IsProxy_ServerClosed = TRUE;
		LeaveCriticalSection(g_cs);
	}
	if (pPar->pPair->IsUser_ProxyClosed == FALSE)
	{
		LeaveCriticalSection(g_cs);
		closesocket(pPar->pPair->user_proxy);
		pPar->pPair->IsUser_ProxyClosed = TRUE;
		LeaveCriticalSection(g_cs);
	}
	cout << endl;
	return 1;
}

int main()
{
	StartServer();
	while (1)
	{
		if (getchar() == 'q')
			break;
	}
	CloseServer();
	return 0;
}

        好吧!我们如何使用这个程序呢?

      当我们开启这个程序之后,要在IE上进行一些设置才行。

      Internet选项-->连接


       然后点击设置。

       

            在代理服务器框中输入如图所示的端口与地址,当然这是本程序指定的端口与地址,如果你更改了的话,你要按照自己设定的端口与地址来设定。

       当然如果嫌弃IE浏览器设置过于复杂的话,我们可以用chrome的插件来实现设定。

       我们先到chrome应用商店下载一个 SwitchySharp插件,然后参考下面的设定:


       按照顺序设置即可!然后在浏览器中切换即可!

    

     比IE要方便多了!

     这个程序参考了网上的一些源代码,但是作者已经找不到了,在此一并感谢!