首页 > 代码库 > 简易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要方便多了!
这个程序参考了网上的一些源代码,但是作者已经找不到了,在此一并感谢!