使用 ACE 库的简单 HTTP 代理






4.73/5 (8投票s)
关于如何构建简单 HTTP 代理服务器的文章

引言
我是网络编程的新手。所以,我尝试把学习目标定为制作一个非常简单的代理服务器。这是一个非常简单的,使用 lib ACE 的 HTTP 代理服务器。
背景
你需要了解 socket 和多线程编程,以及 lib ACE。我使用 lib ACE 来处理 socket 和 logger。实际上,我一开始尝试的是异步代理服务器(单线程,非阻塞 socket),但是最终得到的是一个非常慢的代理服务器。这就是我使用这个库的原因。所以,最好先下载 lib ACE,然后构建 debug 和 release 两个版本。Lib ACE 是一个非常好的网络编程库。我在这里将 lib ACE 用作动态库。
基本理论
好,这里是一些理论
代理服务器是一个服务器(一个计算机系统或应用程序),它通过将请求转发到其他服务器来为客户端的请求提供服务。客户端连接到代理服务器,请求一些服务,例如文件、连接、网页或其他资源,这些资源可以从不同的服务器获得。代理服务器通过连接到指定的服务器并代表客户端请求服务来提供资源 (引自 Wikipedia.org)。
那么,我们如何来实现它呢?这是计划。
创建一个到客户端的 TCP socket,也就是你的 Web 浏览器。绑定到端口 5060,并开始监听来自客户端的传入请求。
- 客户端的连接被接受后,代理会将其 socket 数据存储到缓冲区变量中。这个过程在一个无限循环中进行。
- 一个名为 母线程 的线程将始终运行,检查缓冲区。如果缓冲区不为空,它将创建另一个线程,称为 子线程。
- 子线程将根据其存储的 socket 数据接收来自客户端的请求。
- 代理将处理此请求并确定目标 IP 地址和端口号(通常 HTTP 为 80)。
- 然后,请求被发送到目标 Web 服务器。
- 很快,Web 服务器将回复,发送回请求页面的内容。
- 代理将不断接收这些数据,然后将其发送回客户端,直到其中一方(客户端或服务器)关闭连接,或者直到所有页面内容都已发送完毕。
代码
一切都从这里开始。有一个无限循环,它将接受任何传入的连接,并将其 socket 信息放入缓冲区列表。与此同时,母线程正在运行,监视该缓冲区列表。
int CPROXY::Run(int nPort)
{
ACE_DEBUG((LM_DEBUG, ACE_TEXT("%t: Running!\n")));
DWORD thid;
HANDLE hMotherThread = CreateThread(NULL, 0, MotherThread, this, 0, &thid);
if (!hMotherThread)
return -1;
ACE_SOCK_Stream client_stream;
ACE_INET_Addr addr;
addr.set(nPort, addr.get_ip_address());
int e = client_acceptor.open(addr);
if (e == INVALID_SOCKET)
return -1;
while(true)
{
int e = client_acceptor.accept(client_stream);
if (e == INVALID_SOCKET)
continue;
//Store in a buffer.
EnterCriticalSection(&guard);
queue.push_back(client_stream);
LeaveCriticalSection(&guard);
}
return 0;
}
除了母线程,我们还有子线程。真正的事情发生在子线程中。母线程只负责检查请求缓冲区。
int CPROXY::MotherThreadWorker()
{
isMotherThreadRunning = true;
while (isMotherThreadRunning)
{
EnterCriticalSection(&guard);
bool isEmpty = queue.empty();
LeaveCriticalSection(&guard);
if (!isEmpty){
DWORD thid;
HANDLE hDaughterThread = CreateThread(NULL, 0,
DaughterThread, this, 0, &thid);
if (!hDaughterThread)
continue;
WaitForSingleObject(wait, INFINITE);
}
}
return 0;
}
int CPROXY::DaughterThreadWorker()
{
char buf1[BUFSIZE];
char cServerAddress[256];
int nServerPort;
EnterCriticalSection(&guard);
ACE_SOCK_Stream client_stream = queue.front();
queue.pop_front();
LeaveCriticalSection(&guard);
SetEvent(wait);
const ACE_Time_Value t(2,0);
size_t nLen = client_stream.recv(buf1, sizeof(buf1), &t);
if(sizeof(buf1) > nLen)
buf1[nLen+1] = '\0';
if (nLen <= 0){
ACE_DEBUG((LM_DEBUG, ACE_TEXT("%t: Connection closed by "
"browser client while receiving requests.\n")));
return -1;
}
//Parse the received packet from client.
GetAddressAndPort(buf1, cServerAddress, &nServerPort);
//Attempt to connect to remote server
ACE_INET_Addr addr;
addr.set(nServerPort, cServerAddress);
ACE_SOCK_Stream server_stream;
if (server_connector.connect(server_stream, addr) == INVALID_SOCKET){
ACE_DEBUG((LM_DEBUG, ACE_TEXT("%t: Connection closed "
"by remote server while connecting.\n")));
return -1;
}
//Send request to server.
if (server_stream.send(buf1, nLen) == INVALID_SOCKET){
ACE_DEBUG((LM_DEBUG, ACE_TEXT("%t: Connection closed by remote"
" server while sending requests.\n")));
return -1;
}
//Retrieve responses from server and send to client.
char buf2[BUFSIZE];
while(1){
nLen =server_stream.recv(buf2, sizeof(buf2), &t);
if (nLen <= 0){
ACE_DEBUG((LM_DEBUG, ACE_TEXT("%t: Connection closed by remote "
"server while receiving responses.\n")));
break;
}
if (client_stream.send((buf2), nLen) == SOCKET_ERROR){
ACE_DEBUG((LM_DEBUG, ACE_TEXT("%t: Connection closed by browser "
"client while sending responses.\n")));
break;
}
}
server_stream.close();
client_stream.close();
return 0;
}
好了,从源代码中我们可以看到,代理只会将请求从客户端(Internet 浏览器)传递到 Web 服务器,然后将 Web 服务器的响应发送回客户端。
关注点
嗯,实际上这很简单但非常好。对于像我这样的新手来说,学习非常好。期待评论...
历史
- 2008 年 11 月 2 日:初始发布
- 2008 年 11 月 3 日:添加了更详细的描述,使其更容易理解