随着Internet的高速发展,代理服务器日益成为企业及家庭用户连接内部网络与Internet的重要工具。代理服务器在本地浏览器和远端web服务器之间转发命令(请求与应答命令)和数据(信息流),在内外部网络间起桥接作用,用一个公用IP地址提供给多个客户端用户,以同时访问Internet,从而大大减少上网费用,并可延伸出网络管理、网络监控等多种用途。本文介绍用C#实现Http协议代理服务器的方法并提供经验证的Http协议代理服务器实用程序。
、代理服务器实现原理
Http代理服务器在客户端与远端Web服务器间充当中间人角色,它接收和解释客户端的请求,连接远端服务器;连接成功后,则将服务器的响应信息通过代理服务器传回至客户端,Http代理服务器的实现模型如下图所示。
谙求Http代理服务器[服务
程刷程Http代理服务器[客户谙求
客户端web服务器端
响应响应
Http代理服务器的具体工作流程为以下步骤:启动Http代理服务器的服务程序,开始监听客户端IE的连接。当客户端的连接请求到达代理服务器后,代理服务器分析连接请求的第一行内容(其内容为请求的目标URL),得到对应的远端web服务器主机名称及相应的端口号,接着代理服务器的客户端程序向该远端web服务器发出远程连接请求,当请求连接成功后,则建立了一条介于客户端(通过连接)、代理服务器及web服务器间的数据通路,代理程序则在IE客户端与远端web服务器间转发双方的命令及数据,直到结束该代理服务。
、实现方法
首先创建一个Http代理服务器通讯组件,在VS.net中新建个以“类库"为模板的Visual C#项目,将该项目命名为Http-
Proxy,图小如下.
灬一
心一
、0芗0
建項目
顶自类驢
o, 鼠℃苤顶目
0 v引c++项目
0安装和部署项目
o其他项目
0 v引u解决方案Windows应用犭Windows控件程序
ASP.N飞猪IP Web ASP.N飞猪IPWeP Webff*库应用程序服务
;一緔亻旁訌,‰0
Proxy
饧浏河
在躐。×y旰《ppr。汾处创建顼目,
罗更多
到《孬狲聽脚0望斗'淼鲨方就谔瞓薊醺0《经00牖胡帮荨謂隰为河然0 融《攣下、准
然后在该代理服务器组件项目HttpProxy中新建下述几个类:Listener类,HttpListener类,Client类,HttpClient类,下以创建Listener类为例:
添加項..w服安裝程序类
一0宀;“就到的0明卡是
趾蜘Pro
闩0本地顼目项
o用户界面
“0 web
《。0实用工具
o资涯
《名称(t:Listener.cs
以下详述各个类的功能及实现方法。
由于下述的几个类中都需调用.Net中的类库进行socket通讯,首先我们在每个类文件的头部都加人如下命名空间:
using System.Net
using System.Net.Sockets
1.Listener类
为了将该代理服务器扩充,以支持除Http协议以外的如Ftp,Socks等其他网络协议,使用面向对象编程的方法,封装了一个用于实现代理服务器服务程序,侦听客户端连接请求的基类—Listener类
2004,4
电脑编翟技巧与雉护
private int m_Port;private IPAddress m_Address;private Socket m_ListenSocket;m_ListenSocket Socket
Listensocket,:protected Socket ListenSocket
get
return mJistenSocket;
set
if(value==null)throw new ArgumentNullException();m_ListenSocket=value;
Listener
///<summary>
///</summary>public bool Start()
try
ListenSocket=new Socket(Address.AddressFamily,SocketType.Stream,
ProtocoiType.TCP);
ListenSocket.Bind(new IPEndPoint(Address,Port));
ListenSocket.Listen(50);
ListenSocket.BeginAccept(new AsyncCa}lback
(this.OnAccept),ListenSocket);
catch
ListenSocket null;throw new SocketException();
return true;
///<summary>
</summary>public abstract void OnAccept(IAsyncResult ar);///<summary></summary>public void Stop()
if(ListenSocket—null)
ListenSocket.Close();
Listener Starn Stop OnAccept
Start()TCP Socket ListenSocket,IP
ü,OnAccept
Listener
OnAccept
Listener HttpListener
Stop()ListenSocket,
2.HttpListener
HttpListener Listener,
Http
OnAccept(),
///<sumrnarp
</summary>public override void OnAccept(IAsyncResult ar)
try
Socket NewSocket=ListenSocket.EndAccept(ar);if(NewSocket!=null)
HttpClient NewCEient=new HttpClient(NewSocket,new DestroyDelegate(this.RemoveClient));
NewClient.StartHandshake();
catch
Console.WriteLine("Connect accept error.
try
ListenSocket.BeginAccept(new AsyncCallback
(this.OnAccept),ListenSocket);
catch
return;
Dispose();
OnAccept方法在客户端进行Http请求连接时被调用,在该方法中,首先根据接收的连接请求套接字,创建一个代理服务器客户端NewClient,然后通过该代理客户端NewClient的StartHandshake()方法,与客户端进行握手操作,并开始接收客户端的Http请求。最后在侦听套接字Listensocket上,调用ginAccept()方法等待接收新的客户端请求连接。
3.Client类
Ghent类定义了实现代理服务器客户程序的基类,它主要用于在客户端及远端web服务器间进行数据转发操作,该类的设计中包括与客户端、远端web服务器进行通讯所需的属性、方法。
该类中包括客户端连接套接字,远程目标主机连接套接字、客户端数据缓冲区、远端主机数据缓冲区等主要属性。其中前两个为可读写属性,后两个为只读属性。
///<summary>
///用于取得和设置介于代理服务器和客户端间的连接套接字
///</summary>internal Socket ClientSocket
get return m ClientSocket;set
if(m_CIientSocket!=null)m_ClientSocket.Close 0;m_ClientSocket=value,
///<summary>
///用于取得和设置介于代理服务器和远端主机间的连接套接字
///</summary>internal Socket DestinationSocket g et
return m DestinationSocket;set
if(m DestinationSocket!null)m DestinationSocket.Close 0;m DestinationSocket=value,
///<summary>取得客户端数据缓冲区,用于存放
从客户端的接收数据</summary>protected byte[]Buffer
get return m Buffer;
///<summary>取得远端主机缓冲区,于存放从远端主机接收的数据</summary>protected byte[]RemoteBuffer
get return m RemoteBuffer;
下面我们将完成该类中的一些方法设置。StartRelay()方法是该类中的核心方法它用于在客户端及远端主机间进行数据转发,其程序实现如下:
///<summary>在客户端及远端主机间进行数据转发操作</summary>public void StartRelay()
ClientSocket.BeginReceive(Buffer,0,Buffer.Length,SocketFlags.None,new
AsyncCaNback(this.OnClientReceive),ClientSocket),
DestinationSocket.BeginReceive
(RemoteBuffer,0,RemoteBuffer.Length,SocketFlags None,new AsyncCallback(this.OnRemoteReceive),DestinationSocket),catc h
Dispose(),
StartRelay方法中分别通过客户端连接套接字ChentSocket和远程主机连接套接字DestmationSocket调用BeginReceive方法在客户端及远端主机间循环地接收数据,在收完数据后调用各自的回调函数OnClientReceive,OnRemoteReceive转发接收到的数据以实现在客户端及远端主机间的数据转发操作,完整的回调函数实现过程请参见所附程序。
4.HttpClient类
HttpChent类是实现Http代理服务器客户端功能的核心部份,该类首先从客户端接收请求数据,然后将该Http请求进行解析,得到相应的目标主机地址及端口号,再根据目析主机地址及端口号进行远端连接,如果连接成功则调用基类Client中的
TECHNOLOC
StartRe1ay(//Http http://
Client if(RequestedPath.Length>—7&&RequestedPath.Substring(0,7).ToLower().Equals("http://"))
///<summary>Ret=RequestedPath.IndexOf('/7);
</summary>if(Ret
public override void StartHandshake()
try
ClientSocket.BeginReceive(Buffer,0,Buffer.Length,SocketFlags.None,new AsyncCallback(this.OnReceiveQuery),ClientSocket);
catch
Dispose();
Http iü*G;,
OnReceiveQuery,
%fikJ Http ProcessQuery,
///HI-rp
StartHandshake
.</summary>
RequestedPath=else
RequestedPath=RequestedPath.Substring(Ret);
for(Cnt=1;Cnt<Lines.Length;Cnt++)
Ret=Lines[Cnt].IndexOf("if(Ret>0&&Ret<Lines[Cnt].Length
retdict.Add(Lines[Cnt].Substring(0,Ret),
Lines[Cnt].Substring(Ret+1).Trim());
return retdict;
ParseQuery
,
private StringDictionary ParseQuery(string Query)
StringDictionary retdict=new StringDictionary();string[]Lines=Query.r\n",Http:
*G飞猪IP
.Split('\n');int Cnt,Ret;.nOce.ept-•Encodinr;:gr.ip-def Late
99 Jul
•If—None—hatch:4
etpn
if(Lines.Length>0)
//Http
Ret=Lines[0].IndexOf('if(Ret>0)
HttpRequestType=Lines[0].Substring(0,Ret);Hozi11Ä/4.g,(compatible;rte{E
-231
Keep--fliioe?
Request:;NT
Lines[O]=Lines[O).Substring(Ret).Trim(J;
//Http
Ret=Lines[0].LastlndexOf(if(Ret>0)
HttpVersion=Lines[0].Substring(Ret).Trim(Url Http(iT*fi)
RequestedPath=Lines[0].Substring(0,Ret);HttpüfäAfiéä+ä,ParseQuery
else fin F:
Http Http
RequestedPath Lines[0];StringDictionary,Http
F Http:
Host
.'Wfr'
2004.4
N飞猪IPWORK*IECHNOLOGY
///<summary>
///HttP web
///</summary>private void ProcessQuery(string Query)
HeaderFields=ParseQuery(Query);if(HeaderFields=null)
SendBadRequest();return;
int Port;string Host;int Ret;
Ret=((string)HeaderFields["Host"]).IndexOf(if(Ret>0)
Host=((string)HeaderFields[•Host"]).Substring(O,Ret);
Port=int.Parse(((string)HeaderFietds(Host"I)
Substring(Ret+I));
else
Host=(string)HeaderFields["Host"];port=80;
if(HttpRequestType.ToUpper().Equals("POST"))
int index=Query.IndexOf(m_HttpPost=Query.Substring(index+4);
try
tPEndPoint DestinationEndPoint=new
IPEndPoint(Dns.Resolve(Host).AddressList[0],Port);DestinationSocket=new Socket(DestinationEndPoint.AddressFamily,SocketType.Stream,ProtocolType.TCP)if(HeaderFields.ContainsKey("Proxy—Connection")&&HeaderFields["Proxy—Connection"].ToLower().Equals("keep—)
DestinationSocket.SetSocketOption(SocketOptionLevel.
Socket,SocketOptionName.KeepAIive,I);
DestinationSocket.BeginConnect(DestinationEndPoint,new
AsyncCallback(this.OnConnected),DestinationSocket);
catch
SendBadRequest();return;
ProcessQuery,ParseQuery,$ÅJSiifi{J Http Http,
Host e,Destinationsocket,
Web
fikJ Http Visual Http
1.,Hup-
Proxy o
3.[Visual]
4.[Z T]ProxyMain
5.ProxyMain
6.HttpProxy
7•Classl.cs ProxyMam cs,Classl ProxyMaino
8.ProxyMain.
using HttpProxy o
9.ProxyMain.cs class,
//E X—4 HttpListener private static HttpListener httpListener;
private static bool StartProxy(int port)
bool bStartFlag=false,if(null=httpListener)
httpListener=new HttpListener(port);
bStartFlag=httpListener.Start();
嫲、
Console.WriteLine(httpListener);return bStartFlag
//实现停止代理服务器的接口函数。private static bool StopPr0><Y 0
if(null:httpListener)return true,httpListener.Stop 0;return true
TECHNOLOGY
00、论、友ode Ma№嫲№它腼
V贮0》
10.在ProxyMam.cs的Main函数中添加下列代码,用于实现在Dos命令行方式下通过键人"start"、"exit"命令启动、停止
代理服务器。1.选择菜单[工具]|[Internet选项]后,打开Internet选项static void Main(string、[]args)、对话框。
lit:tp PI、0又y不t 1,t:cce过with the铝,0
bool bStartFIag=false;String command;
Console.WriteLine(“\r\n Http Proxy\r\n
Console.Write("\r\n>");command=Console.ReadLine 0.TOLower():while 0 command.Equals("exit"))switch(command〉
case Start
//启动代理服务器bStartFIag:StartProxy(8080);if(bStartFlag)
Console.WriteLine("The Http Proxy start success with the port 8080凵!\r\n"),break;default:
Console.WriteLine("Command not understood.
break;
Console.Write("\r\n>");command=Console.ReadLine(),TOLower();
//停止代理服务器
StopProxy 0;
Console.WriteLine("Goodbye.
至此完成了代理应用程序的编写。按下Ctrl+F5运行该程序,然后在命令行方式下键人"start"命令后将会得到如下图所示的运行结果,提示用户代理服务程序已成功运行。
接下来我们只要在客户端的IE中按照如下步骤进行设置,设置完成后,该客户端就可以打开IE,通过该代理进行上网操作了。