简易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代理的实现的主要内容,如果未能解决你的问题,请参考以下文章