用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: [email protected]                   

 
 
                                &copy;CopyRight 2000-2003 
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章