用Delphi实现Socket5代理编程系列讲座(陈经韬)


一:前言

     经常在论坛上面看到很多人问如何实现Socket5编程,下面就自己对于Socket5的一些肤浅认识写上几句.文章分几个系列,包括Socket5客户端和服务端的编写.文章参考了一些SOCKET5的RFC文档资料和代码,在这里不再一一列出.2002.12.6,谨以此文献给好朋友王甲春和熊恒.

二:Socket5客户端基于Tcp协议的实现

    该程序的原理如下:你的客户端程序(发送数据)------>Socket5代理服务器(中转数据)----->远程目的主机(显示数据).所以你必须拥有一个Socket5代理服务器软件,强烈推荐朱尧坤先生写的CCproxy,下载地址 http://www.youngzsoft.com/.
    新建一个工程,放上四个Label,五个Edit,一个ServerSock控件,一个按钮和一个Memo控件.在uses里添加Winsock.窗口初始化的时候初始化各个控件.

procedure TForm1.FormCreate(Sender: TObject);
var
TempWSAData:TWSAData;
begin
Label1.Caption:='Socks5代理服务器地址';
Edit1.Text:='127.0.0.1';
Label2.Caption:='Socks5代理服务器端口';
Edit2.Text:='1080';
Label3.Caption:='远程服务器地址';
Edit3.Text:='127.0.0.1';
Label4.Caption:='远程服务器端口';
Edit4.Text:='9999';
Edit5.Text:='输入要发送的字符';
Button1.Caption:='测试';
ServerSocket1.Port:=9999;
ServerSocket1.Active:=True;
Memo1.Lines.Clear;
//初始化Winsock
if (WSAStartup(MAKEWORD(2,0),TempWSAData)<>0) then
begin
Application.MessageBox('程序初始化失败!',Pchar(Application.Title),MB_ICONINFORMATION);
Application.Terminate;
end
else
Memo1.Lines.Add('程序初始化成功!');
end;

    点Button1的时候通过代理服务器发送数据

procedure TForm1.Button1Click(Sender: TObject);
var
MyClientSock:TSocket;
Socket5Proxy:TSockAddr;
TargetSock:TSockAddr;
MySocketBuf:array[0..256]of byte;
SendStrBuf:array[0..1024*16] of char;
PcharSocketAddr:PChar;
Re,i:integer;
begin
Memo1.Lines.Add('----------------------------');
//1:创建Socket
MyClientSock:=socket(AF_INET,SOCK_STREAM,0);
if(MyClientSock=INVALID_SOCKET) then
begin
Memo1.Lines.Add('创建Socket失败!');
Exit;
end
else
Memo1.Lines.Add('成功创建socket.');
//2:连接Socket5代理服务器
ZeroMemory(@Socket5Proxy,sizeof(Socket5Proxy));
Socket5Proxy.sin_family := AF_INET;
GetMem(PcharSocketAddr,Length(Edit1.Text)+1);
ZeroMemory(PcharSocketAddr,Length(Edit1.Text)+1);
StrPCopy(PcharSocketAddr,Edit1.Text);
Socket5Proxy.sin_addr.S_addr :=inet_addr(PcharSocketAddr);
FreeMem(PcharSocketAddr);
Socket5Proxy.sin_port := htons(StrToInt(Edit2.Text));
Re:=connect(MyClientSock,Socket5Proxy,sizeof(Socket5Proxy));
if Re = SOCKET_ERROR then
begin
Memo1.Lines.Add('连接代理服务器错误.错误代码:'+IntToStr(WSAGetLastError()));
closesocket(MyClientSock);
Exit;
end
else
Memo1.Lines.Add('连接代理服务器成功!');
//3:Socket5协议验证与协商
MySocketBuf[0] := $05; MySocketBuf[1] := $01;MySocketBuf[2] := $00;
re := send(MyClientSock, MySocketBuf, 3, 0);//发送格式化消息
if re=-1 then
begin
Memo1.Lines.Add('该服务器不支持Socket5代理!');
closesocket(MyClientSock);
Exit;
end;
re:=recv(MyClientSock,MySocketBuf,257,0); //接收返回结果
if re<2 then
begin
Memo1.Lines.Add('该服务器不支持Socket5代理!');
closesocket(MyClientSock);
Exit;
end;
if MySocketBuf[1]<>$00 then
begin
Memo1.Lines.Add('该服务器需要身份验证!');
closesocket(MyClientSock);
Exit;
end;
Memo1.Lines.Add('与Socket5代理服务器协商成功!');
//4:发送远程主机信息并连接
ZeroMemory(@TargetSock,Sizeof(TargetSock));
Getmem(PcharSocketAddr,length(edit3.text)+1);
ZeroMemory(PcharSocketAddr,length(edit3.text)+1);
StrPcopy(PcharSocketAddr,edit3.text);
TargetSock.sin_addr.s_addr := inet_addr(PcharSocketAddr);
TargetSock.sin_port := htons(strtoint(edit4.text));
TargetSock.sin_family := AF_INET;
MySocketBuf[0] := $05;MySocketBuf[1] := $01; MySocketBuf[2] :=$00; MySocketBuf[3] := $01;
CopyMemory(@MySocketBuf[4],@TargetSock.sin_addr,4);
CopyMemory(@MySocketBuf[8],@TargetSock.sin_port,2);
re:=send(MyClientSock,MySocketBuf,10,0);
if re=-1 then
begin
Memo1.Lines.Add('发送远程主机信息失败!');
closesocket(MyClientSock);
Exit;
end;
re :=recv(MyClientSock,MySocketBuf,1024,0);
if re=-1 then
begin
Memo1.Lines.Add('接收返回信息失败!');
closesocket(MyClientSock);
Exit;
end;
if MySocketBuf[1]<>$00 then
begin
Memo1.Lines.Add('连接远程主机失败!');
closesocket(MyClientSock);
Exit;
end;
Memo1.Lines.Add('连接远程主机成功!');
//5:发送数据
for i:=0 to Length(Edit5.Text)-1 do SendStrBuf[i]:=Edit5.Text[i+1];
re:=send(MyClientSock,SendStrBuf,Strlen(SendStrBuf),0);
if re=-1 then
begin
Memo1.Lines.Add('发送数据到远程主机失败!');
closesocket(MyClientSock);
Exit;
end
else
Memo1.Lines.Add('发送数据到远程主机成功!');
//6:关闭Socket
Memo1.Lines.Add('关闭Socket!');
Memo1.Lines.Add('----------------------------');
closesocket(MyClientSock);
end;

  放上一个ServerSocket控件是用来接收信息并显示出来的.

procedure TForm1.ServerSocket1ClientRead(Sender: TObject;
Socket: TCustomWinSocket);
var
TempStr:String;
begin
TempStr:=Socket.ReceiveText;
Application.MessageBox(Pchar(TempStr),'接收信息',0);
end;

  程序退出的时候要做一些工作.


procedure TForm1.FormDestroy(Sender: TObject);
begin
WSACleanUP();//Winsocket释构
end;
  效果图如下,运行CCproxy来显示连接信息:




   也就是说,只要我们和代理服务器握手和协商成功后,就可以直接把代码服务器看成透明不存在的了,把代理服务器看作是远程主机即可.我们上面的程序只是针对没有用户验证的情况.如果代理服务器需要验证的话只要修改一下握手协商过程即可.想更加深入了解的朋友点这里下载一个CSocksifiedSocket类(VC代码).

三:预告

   下一节我们将先以一个简单的数据转发例子说明代理服务器的工作原理(如图):




  然后编写一个简单的Socket5代理服务器.还是先请大家下载一个FlashGet吧:)

 
               
                            用Delphi实现Socket5代理编程系列讲座(二)
                                               ---谨以此文献给好友王甲春和熊恒.

                                                                    陈经韬

四:代理服务器原理-----一个简单的QQ数据转发程序

    上一节我们讲述了Socket5代理编程的一些流程,本节将以一个简单的数据转发例子说明代理服务器的工作原理,为下一节的Socket5代理服务器编程做好准备。

    什么是代理服务器呢?简单的说,就是数据转发程序,它的工作流程如下:需要代理的程序A--->代理服务器--->目的服务器B。代理服务器接收A发送的数据并发送给B,同样,接收B发送的数据发送给A。A与B之间互相通信的时候都是直接跟代理服务器打交道而已。

    网上曾经有一个IP电话的代理程序,就是用来转发UDP数据的。比如说你属于局域网接入INTENET,也就是说你是没有动态IP的,这种情况下别人如何将数据发送到你的电脑呢?一个方法是用Socket Tcp编程,你先连接对方,不过这种连接要求对方有动态IP地址。另一种方法就是利用代理了。当然,还有其它方法,如果有时间我们会在后面提一提。这个例子是这样实现的:比如说你在局域网内的IP地址为192.168.0.77,你连接入INTENET的主机动态IP地址为202.98.26.74,那么你先在主机运行代理程序,并在Edit1填上你的IP地址192.168.0.77,对方将数据发送到主机202.98.26.74端口6660,主机再将数据转发到你的电脑。该程序代码如下:

const MaxPackets=160;
var
PacketLen,PlayPackets:integer;
ok:integer=0;
mPackets:integer=1;
sPackets:integer=1;
MBuffer:array[1..160,1..2000] of char; //8 Seconds

procedure TForm1.FormCreate(Sender: TObject);
begin
NMUDP1.ReportLevel := Status_Basic;
NMUDP1.LocalPort := 6660;//本地监听端口
NMUDP1.RemotePort := 6661;//远程接收数据端口
PlayPackets:=0;
end;

procedure TForm1.Edit1Change(Sender: TObject);
begin
NMUDP1.Remotehost:=edit1.text;//远程主机的IP地址
end;


procedure TForm1.NMUDP1DataReceived(Sender: TComponent;NumberBytes: Integer; FromIP: String; Port:Integer);
var
mdata:array[1..2000] of char;
k:integer;
begin
PacketLen:=NumberBytes;
NMUDP1.ReadBuffer(mdata,NumberBytes);//接收数据
for k:=1 to NumberBytes do MBuffer[mPackets mod MaxPackets+1][k]:=mdata[k];
label2.caption:='Packets:'+inttostr(mPackets);
inc(mPackets);
NMUDP1.SendBuffer(mdata,NumberBytes);//将数据发送到目的主机
end; 

    上面的程序简单的实现了一对一连接数据单向发送的转发.修改一下即可做成双向数据转发.下面我们就以一个简单QQ数据转发程序为例说明一下.该例子分为服务端和客户端两个程序,要求运行服务端的电脑必须有动态IP地址.

    服务端用到一个TServerSocket和TNMUDP。ServerSocket1接收到客户端发送的数据通过NMUDP1发送到滕讯服务器,NMUDP1接收到滕讯服务器的数据再通过ServerSocket1发送到客户端。代码如下:

procedure TForm1.FormCreate(Sender: TObject);
begin
NMUDP1.LocalPort:=4000;//本地端口:接收滕讯服务器发回来的信息
NMUDP1.RemoteHost:='202.104.129.251';//远程主机:滕讯服务器
NMUDP1.RemotePort:=8000;//远程端口
ServerSocket1.Port:=9000;
ServerSocket1.Active:=True;
end;

procedure TForm1.ServerSocket1ClientConnect(Sender: TObject;
Socket: TCustomWinSocket);
begin
StatusBar1.SimpleText:='远程主机'+Socket.RemoteAddress+'成功建立连接!';
end;

procedure TForm1.ServerSocket1ClientError(Sender: TObject;
Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
var ErrorCode: Integer);
begin
StatusBar1.SimpleText:='Socket错误!';
ErrorCode:=0;
end;

procedure TForm1.ServerSocket1ClientDisconnect(Sender: TObject;
Socket: TCustomWinSocket);
begin
StatusBar1.SimpleText:='远程主机'+Socket.RemoteAddress+'断开连接!';
end;

procedure TForm1.ServerSocket1ClientRead(Sender: TObject;
Socket: TCustomWinSocket);
var
Len:integer;
rec_bytes: integer;
rec_Buffer: array[0..8191] of char;
begin
try
Len:=Socket.ReceiveLength;
rec_bytes:=socket.ReceiveBuf(rec_buffer,Len);
NMUDP1.SendBuffer(rec_buffer,rec_bytes);
except
end;
end;

procedure TForm1.NMUDP1DataReceived(Sender: TComponent;
NumberBytes: Integer; FromIP: String; Port: Integer);
var
C:array[1..8192] of Char;
I:Integer;
begin
NMUDP1.ReadBuffer(C,I); //收到的字符定义给c
if i=0 then Exit;
if ServerSocket1.Socket.ActiveConnections>0 then ServerSocket1.Socket.Connections[0].SendBuf(c,i);
end;

    客户端用到一个TClientSocket和TNMUDP。NMUDP1接收到QQ的数据,通过ClientSocket1发送给服务端,ClientSocket1接收到服务端的数据再通过NMUDP1转发给QQ。代码如下:

procedure TForm1.FormCreate(Sender: TObject);
begin
NMUDP1.LocalPort:=8000;//打开端口给本地QQ连接.可以随便改变
NMUDP1.RemoteHost:='127.0.0.1';//发送信息给本地的QQ时候用,不能改变
NMUDP1.RemotePort:=4000;//发送信息给本地的QQ端口.不能改变
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
ClientSocket1.Address:=Edit1.Text;
ClientSocket1.Port:=9000;
ClientSocket1.Active:=True;
end;

procedure TForm1.ClientSocket1Connect(Sender: TObject;
Socket: TCustomWinSocket);
begin
StatusBar1.SimpleText:='成功连接';
end;

procedure TForm1.ClientSocket1Disconnect(Sender: TObject;
Socket: TCustomWinSocket);
begin
StatusBar1.SimpleText:='断开连接';
end;

procedure TForm1.ClientSocket1Error(Sender: TObject;
Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
var ErrorCode: Integer);
begin
StatusBar1.SimpleText:='Socket错误';
ErrorCode:=0;
end;

procedure TForm1.NMUDP1DataReceived(Sender: TComponent;
NumberBytes: Integer; FromIP: String; Port: Integer);
var
C:array[1..8192] of Char;
I:Integer;
begin
NMUDP1.ReadBuffer(C,I);
try
if ClientSocket1.Active then ClientSocket1.Socket.SendBuf(c,i);
except
end;
end;

procedure TForm1.ClientSocket1Read(Sender: TObject;
Socket: TCustomWinSocket);
var
Len:integer;
rec_bytes: integer;
rec_Buffer: array[0..8191] of char;
begin
try
Len:=Socket.ReceiveLength;
rec_bytes:=socket.ReceiveBuf(rec_buffer,Len);
NMUDP1.SendBuffer(rec_buffer,rec_bytes);
except
end;
end;

     使用说明和演示程序在 http://tty.yyun.net/lovejingtao/ocx/QQProxyClient.htm。前面提到局域网与局域网之间不用代理服务器通信。也就是
     局域网电脑A---》拨号主机C--》INTENET
     局域网电脑B---》拨号主机D--》INTENET
     如何不用代理服务器实现A与B通信呢?利用映射原理即可,由于路由的缘故,C和D会自动转发数据到A和B的,我的一个朋友很早就试验成功了,可惜的是该原理只能用于UDP协议。

★作者:

陈经韬
Home: http://Lovejingtao.126.com
E-Mail: Lovejingtao@21.cn.com                   

 
 
                                &copy;CopyRight 2000-2003 
 
参与评论 您还未登录,请先 登录 后发表或查看评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值