简易HTTP代理的实现

Posted lishuhuakai

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了简易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的一个安全版本。)

      下面的程序需要放在win32工程中才行:


#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 = 2048;
const int USERPORT = 9999; //客户端连接的端口号
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;
	//cout << str << endl;
	sscanf_s(str, "%s%s%s", command, 512, buf, BUFSIZE, proto, 128);//从str里面提取出数据
	p = strstr(buf, HTTP);//buff里面含有域名信息
	//HTTP
	if ((0 == strcmp(command, "GET") || 0 == strcmp(command, "POST"))
		&& p) //GET命令
	
		//以 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 if (strcmp(command, "CONNECT") == 0)
	   /*http代理中经常出现如CONNECT 123.13.12.123:8076 之类的东西 */
		int i, j;
		char portBuff[10];
		for(i = 0; i < BUFSIZE; i++)//取出前面的主机名或主机地址
		
			if (buf[i] == ':')
			
				break;
			
			address[i] = buf[i];
		
		i++;
		
		for(j = 0; j < 10; j++, i++)
		
			if (buf[i] >= '0' && buf[i] <= '9')
			
				portBuff[j] = buf[i];
			
			else
			
				break;
			
			
		
		portBuff[j] = '\\0';	
		if (j == 0) /*j == 0说明':'后面接的不是端口号,这样的话,无法解析*/
		return FALSE;
		*port = atoi(portBuff);

	
	else
	
	  cout << "不支持的协议类型" << endl;
	  cout << str << endl;
	  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, 8);
	gListen_Socket = sockBlind;
	CreateThread(NULL, 0, UserToProxyThread, NULL, 0, NULL); //启动侦听
	cout << "代理正在运行~~~~~" << endl;


DWORD WINAPI UserToProxyThread(LPVOID pParm)

	char Buffer[BUFSIZE];  /*接收的缓冲区*/
	int Len;
	sockaddr_in from;
	int fromlen = sizeof(from);

	SOCKET msg_socket;
	
	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, 30000); //申请事件对象,起到了一个同步的作用
	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.user_proxy); /*关闭客户端连接*/
		Spair.IsUser_ProxyClosed = TRUE;
		LeaveCriticalSection(g_cs);
	
	if (Spair.IsProxy_ServerClosed == FALSE) /*如果服务器端没有关闭连接*/
	
		LeaveCriticalSection(g_cs);
		closesocket(Spair.proxy_server); /*关闭服务器端连接*/
		Spair.IsProxy_ServerClosed = TRUE;
		LeaveCriticalSection(g_cs);
	
	WaitForSingleObject(pChildThread, 10000); //这个函数主要是等待连接服务器的线程退出
	DeleteCriticalSection(g_cs);
	return 0;


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;

	sockaddr_in server;
	
	SOCKET conn_socket;

	hostent *hp;
	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, SOCK_STREAM, 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, (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->IsProxy_ServerClosed == 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);
	
	return 0;


int main()

	cout << "lishuhuakai代理,欢迎使用!" << endl;
	cout << "端口号  : " << USERPORT << endl;
	cout << "代理地址: " << userAddr << endl;
	cout << "ps:输入q命令退出!" << endl;
	StartServer();
	while (1)
	
		if (getchar() == 'q')
			break;
	
	CloseServer();
	return 0;


          之前的代码有一些细小的错误,导致程序经常堆栈溢出,是因为我写错了一些语句,不过这一版本已经更正,我自己在VS2005下测试完美通过!我对于之前造成的错误表示抱歉!(百度云可以使用这个代理!)

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

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

      Internet选项-->连接

       

       然后点击设置。

        

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

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

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

        

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

         

        比IE要方便多了!

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

以上是关于简易HTTP代理的实现的主要内容,如果未能解决你的问题,请参考以下文章

实现一个简单的Http代理服务器

nodejs简易代理服务器

简易反向代理

JDK动态代理深入理解分析并手写简易JDK动态代理(上)

nginx简易教程

Web知识简易介绍及HTTP知识总结