2013年3月4日星期一

【转】delphi端口扫描源程序

作者:啊D 

  ------------------------------------------------ 
     本程序由“啊D”制作! 
     QQ:9269563 
     E-Mail:paf@163.net 
     主页:Http://coold.cn99.com 
     制作于:2002.8.27 
  **你可以随意修改代码! 但在转载时请保留此声名!** 
  ------------------------------------------------ 

unit main; 
interface 
uses 
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, 
  Buttons, StdCtrls, ComCtrls,winsock,SearchPortThread, Spin; 
type 
  TForm1 = class(TForm) 
    M1: TMemo; 
    Port1: TEdit; 
    Port2: TEdit; 
    Label1: TLabel; 
    Label2: TLabel; 
    Label3: TLabel; 
    Search: TSpeedButton; 
    SB1: TStatusBar; 
    MaxThread: TSpinEdit; 
    Label4: TLabel; 
    IP: TComboBox; 
    AboutButton: TSpeedButton; 
    procedure SearchClick(Sender: TObject); 
    procedure FormCreate(Sender: TObject); 
    procedure FormDestroy(Sender: TObject); 
    procedure FormKeyPress(Sender: TObject; var Key: Char); 
    procedure AboutButtonClick(Sender: TObject); 
    procedure FormShow(Sender: TObject); 
  private 
    procedure Search_ST; 
    procedure ThreadDone(Sender: TObject); 
    procedure Search_Exit; 
    function HostNameToIP(S: string): DWord; 
    procedure AddComboBox(CB: TcomboBox); 
    procedure SearchOK; 
      { Private declarations } 
  public 
    ThreadList: array of TSearchPortThread;   //线程数组 
    Portlist:Tlist; //线程要用到的Strings 
    PortIndex:integer; //正在处理的端口号 
    MaxPort,MinPort:integer; //保存最大和最小的端口号的变量 
    addr:Tsockaddr; // 
    about:string; //关于 
    StopThread:boolean; //停止线程标记 
    { Public declarations } 
  end; 
var 
  Form1: TForm1; 
implementation 
{$R *.DFM} 

============================= 
=          自定义函数       = 
============================= 

//==== 开始扫描 ==== 
procedure TForm1.Search_ST(); 
var 
i,j:integer; 
Address: DWord; 
begin 
try 
//-- 检测部分 -- 
Address := HostNameToIP(PChar(IP.text));// 检测IP是否有效! 
if (Address = INADDR_NONE) then begin 
    showmessage('输入的IP、域名无效或网络不通!'); 
    exit; 
end; 
if MaxThread.Value < 2 then begin //检测线程数 
    showMessage('线程数请选择 2-300 之间的值!'); 
    exit; 
end; 
Maxport:=strtoint(port2.text); //最大端口号 
Minport:=strtoint(port1.text); //最小端口号 
PortIndex:=Minport; 
if Minport > Maxport then begin 
    showMessage('[开始端口]不能大于[结束端口]的值!'); 
    exit; 
end; 
//端口范围检测 
if (minport < 1) or (minport >65535) or (Maxport < 1) or (Maxport >65535) then begin 
    showMessage('输入数值超过了指定的范围!'#13#10'数值范围:1-65535'); 
    exit; 
end; 
//------扫描部分------- 
search.Caption := '停止扫描'; 
search.tag :=1; 
AddComboBox(IP); //把IP或域名加入列表! 
m1.lines.Clear; 
m1.Lines.Add('正在扫描: ' + ip.text + #13#10'[端口] | [说明 
]'#13#10'-------------------------------------'); 
m1.Update; 
  SetLength(ThreadList,MaxThread.Value);  //创建 MaxThread.Value 个线程数组 
  if (length(threadlist) >1) then j:=length(threadlist)-1; 
  if (maxport-minport)<j then j:=maxport-minport; 
  for i:=0 to j do 
  begin 
    addr.sin_family:=AF_INET; // 
    addr.sin_addr.S_addr:=Address;  //地址 
    addr.sin_port:=htons(PortIndex); //端口 
    threadlist[i]:=TSearchPortThread.Create(m1,addr);  //创建线程1 
    threadlist[i].OnTerminate :=ThreadDone; //线程处理完后要处理的事 
    Portlist.Add(threadlist[i]);  //把线程加入 Portlist 
    sb1.Panels[0].Text :=format('已扫描端口: %s',[inttostr(Portindex)]); 
    PortIndex:=PortIndex+1; 
end; 
//出错处理代码 
except 
showMessage('出错了!'); 
end; 
end; 
//===== END ===== 

//==== 线程退出代码 ====== 
procedure TForm1.ThreadDone(Sender: TObject); 
var 
Index:integer; 
begin 
try 
if (PortIndex >= maxport) and (StopThread=false) then begin 
    SearchOK(); 
    StopThread:=true; 
    exit; 
end; 
if (PortIndex > maxport) or (StopThread = true) then exit; 
Index := PortList.IndexOf(Sender); //在Filst 中查找刚才的线程对象 
PortList.Delete(Index); 
addr.sin_port:=htons(portindex); //端口 
threadlist[Index]:=TSearchPortThread.Create(m1,addr);  //创建线程1 
threadlist[Index].OnTerminate :=ThreadDone; //线程处理完后要处理的事 
Portlist.Add(threadlist[Index]);  //把线程加入 Portlist 
sb1.Panels[0].Text :='已扫描端口: ' + inttostr(Portindex); 
PortIndex:=PortIndex+1; 
except //错误处理 
//    sb1.Panels[0].Text :='程序出错[线程退出]!'; 
end; 
end; 
//===== END ===== 

//==== 停止扫描 ===== 
Procedure Tform1.Search_Exit; 
var 
i:integer; 
temp:TSearchPortThread; 
begin 
try 
 m1.Lines.Add('[ 用户中断 ]'); 
 sb1.Panels[0].Text :='正在关闭线程.....'; 
for i:=0 to portlist.Count  - 1 do 
begin 
    temp:=portlist.Items[i]; 
    temp.Terminate;  //结束线程 
end; 
      SearchOK(); //显示开始扫描 
      StopThread:=true; //StopThread为真,为停止扫描 
except //错误处理 
//   sb1.Panels[0].Text :='程序出错[停止扫描]!'; 
end; 
end; 
//===== END ===== 
// ===== 扫描完成 ========= 
procedure Tform1.SearchOK(); 
begin 
      search.Caption :='开始扫描'; 
      search.Enabled :=true; 
      search.tag:=0; 
      sb1.Panels[0].Text :='扫描完成'; 
      m1.Lines.Add('-------------------------------------'#13#10'扫描完成!'); 
      portlist.Free;//释放PortList的内存空间 
end; 
//==== END ==== 
//==== 域名、IP自动转为IP ==== 
function TForm1.HostNameToIP(S:string):DWord; 
const 
  INADDR_NONE = $FFFFFFFF; 
var 
Host:pHostent; 
Address:DWord; 
begin 
Address := inet_addr(PChar(S)); 
if (Address = INADDR_NONE) then begin 
   Host:=GetHostByName(Pchar(S)); 
   if Host = nil then begin 
        HostNameToIP:=INADDR_NONE; 
        exit; 
   end 
   else begin 
        hostNametoip:=longint(pointer(Host^.h_addr_list^)^); 
        exit; 
   end; 
end 
else begin 
  HostNameToIP:=Address; 
  exit; 
end; 
end; 
//==== END ==== 
//==== 自动把ComboBox控件的内容里没有的内容加入列表中 =====! 
procedure Tform1.AddComboBox(CB:TcomboBox); 
var 
a :integer; 
begin 
    a:=CB.Items.IndexOf(CB.Text); 
    if a=-1 then CB.Items.Add(CB.Text); 
end; 
//== END === 
// ◎◎◎ 自定义函数 END ◎◎◎ 
  


============================= 
          控件部分 
============================== 

//==== Search按钮 ===== 
procedure TForm1.SearchClick(Sender: TObject); 
begin 
if search.tag =0 then begin 
    portlist:=Tlist.Create; //创建Tstring 
    Search_ST(); //开始 
    StopThread:=false; //停止为假时,为扫描 
end 
else begin 
    Search_Exit(); //停止 
end; 
end; 
//===== END ===== 

//===== Form 创建 ===== 
procedure TForm1.FormCreate(Sender: TObject); 
var 
Wsadata:Twsadata; 
begin 
WSAStartup(Makeword(2,0),Wsadata);// 
about:=m1.Text; //把简介内容保存到About变量中 
end; 
//===== END ===== 

//===== Form 退出 ====== 
procedure TForm1.FormDestroy(Sender: TObject); 
begin 
//PortList.free; //释放PortList的内存空间 
WSAcleanUP;// 
end; 
//===== END ====== 

//===== 当按‘回车’、‘Esc’键时的动作 ====== 
procedure TForm1.FormKeyPress(Sender: TObject; var Key: Char); 
begin 
if (key =#13) and (search.Tag=0) then  SearchClick(Sender); //开始扫描 
if (key =#27) and (search.Tag=1) then  SearchClick(Sender); //停止扫描 
end; 
//===== END ====== 
  
//==== 关于 ===== 
procedure TForm1.AboutButtonClick(Sender: TObject); 
begin 
showMessage(About + 
'地址:深圳市龙岗区坪山镇石井太阳村'#13#10#13#10 + 
'注:'#13#10 + 
'本软件为免费软件,对使用本软件产生'#13#10 + 
'的后果,本人不承担任何的责任!'#13#10#13#10 + 
'本程序用Delphi编写,如要源码,请和作者联系!'#13#10#13#10 + 
'制作时间: 2002年8月27日' 
); 
//m1.Text:=About; 
end; 
//==== END ===== 
//====== 窗口居中 ====== 
procedure TForm1.FormShow(Sender: TObject); 
begin 
     Left := (Screen.Width - Width) div 2; 
     Top := (Screen.Height - Height) div 2; 
end; 
//===== END ==== 
// ◎◎◎ 控件部分 END ◎◎◎ 
end. 
 __________________________________________________________ 
如果你写扫描程序可千万不要直接去连接,应该使用别的链接方法,否则你的踪迹会被别人 
发现.例如使用sys扫描或者fin扫描: 
我给你异步socket的api代码: 

unit Unit1; 
interface 
uses 
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, 
  StdCtrls,WInSock, ExtCtrls; 
const WM_SOCKET=WM_USER+1;   //socket消息 
type 
  TForm1 = class(TForm) 
    Button1: TButton; 
    Edit1: TEdit; 
    Panel1: TPanel; 
    Memo1: TMemo; 
    procedure FormCreate(Sender: TObject); 
    procedure FormDestroy(Sender: TObject); 
    procedure Button1Click(Sender: TObject); 
    procedure Button2Click(Sender: TObject); 
    procedure Button3Click(Sender: TObject); 
  private 
    Sockhd : integer;   //socket句柄 
    Serv_Addr : Tsockaddr;//目标地址 
  procedure SockEvent(var msg: Tmessage);message WM_SOCKET;   //处理cocket消息 
  procedure DspMsg(msg : string);   //显示信息 
    { Private declarations } 
  public 
    { Public declarations } 
  end; 
  Form1: TForm1; 
implementation 
{$R *.DFM} 
function lookup_hostname(const hostname:string):longint;  //把域名转化成IP地址 
var 
  RemoteHost : PHostEnt;  (* no, don't free it! *) 
  ip_address: longint; 
begin 
  ip_address:=-1; 
  try 
    if hostname='' then 
      begin  (* no host given! *) 
        lookup_hostname:=ip_address; 
        EXIT; 
       end 
    else 
      begin 
        ip_address:=Winsock.Inet_Addr(PChar(hostname));  (* try a xxx.xxx.xxx.xx first *) 
        if ip_address=SOCKET_ERROR then begin 
        RemoteHost:=Winsock.GetHostByName(PChar(hostname)); 
        if (RemoteHost=NIL) or (RemoteHost^.h_length<=0) then 
          begin 
            lookup_hostname:=ip_address; 
            EXIT;  (* host not found *) 
          end 
        else 
          ip_address:=longint(pointer(RemoteHost^.h_addr_list^)^); 
      end; 
      end; 
   except 
    ip_address:=-1; 
  end; 
  lookup_hostname:=ip_address; 
end; 
procedure TFOrm1.DspMsg(msg: string); 
begin 
 memo1.Lines.Add(msg+'...'); 
 if Memo1.Lines.Count>200 then Memo1.Lines.Delete(0); 
end; 
procedure TForm1.SockEvent(var msg : tmessage);  //处理socket消息 
begin 
 case msg.LParam of 
  FD_READ: begin    //标识可以读数据,当然肯定已经链接上了 
    dspmsg('可以读取数据'); 
    //do what you want do 
  end; 
  FD_WRITE: begin 
    dspmsg('可以发送数据'); 
    //do what you want do 
  end; 
  FD_ERROR: begin 
    dspmsg('发生错误'); 
     //如果你是客户端,则应该是连接不上,即端口没有开 
  end; 
  FD_CLOSE: Begin 
    dspmsg('服务器断开连接'); 
    //对方关闭连接 
  end; 
  FD_CONNECT: begin 
    dspmsg('连结上服务器'); 
    //表示对方端口开放 
  end; 
  FD_ACCEPT: begin 
    dspmsg('接收一个请求'); 
   //这个消息只有服务端可能出现 
  end; 
 end; 
end; 
procedure TForm1.FormCreate(Sender: TObject); 
 var  wsaData:TwsaData; 
begin                 //启动winsock动态链接库 
  if WSAStartup (makeword(2,2), wsaData)<>0 then begin 
    messagebox(application.handle,'无法启动winsock动态连接库!','警告',MB_OK or MB_APPLMODAL or 
MB_ICONWARNING); 
    Application.Terminate; 
  end;   
end; 
procedure TForm1.FormDestroy(Sender: TObject); 
begin    //关闭dll 
 WSACleanup; 
end; 
procedure TForm1.Button1Click(Sender: TObject); 
begin 
 Sockhd := socket(AF_INET,SOCK_STREAM,0);  //创建socket句柄 
 if Sockhd<0 then begin 
   messagebox(application.handle,'无法创建句柄!','警告',MB_OK or MB_APPLMODAL or 
MB_ICONWARNING); 
   exit; 
 end; 
  Serv_addr.sin_addr.s_addr:= lookup_hostname(edit1.Text);  //主机名 
  Serv_addr.sin_family := PF_INET; 
  Serv_addr.sin_port := htons(23);      //any port you want to connect 
  if WSAAsyncSelect(Sockhd,Form1.handle,WM_SOCKET,FD_ACCEPT or FD_CONNECT or FD_CLOSE or   
FD_READ or FD_WRITE)=SOCKET_ERROR 
  then begin 
   messagebox(application.handle,'无法创建句柄!','警告',MB_OK or MB_APPLMODAL or 
MB_ICONWARNING); 
   exit; 
  end;   //异步socket 
  connect(sockhd,serv_addr,sizeof(serv_addr));  //连接,结果会在前面的处理函数处理 
end; 
end. 

【转】SIP通过NAT的实例解析

下面对SIP协议产生NAT穿透问题,作一些解释;及提出解决的办法。 
1、大致有4种类型的NAT。
a) Full Cone 完全圆锥体
b) restricted cone 受限制的圆锥体
c) port restricted 端口受限制的圆锥体
d) symmetric 对称的
其中a,b,c 也称作非对称的NAT。

2、SIP终端在NAT后面,其工作有可能出现问题。原因是SIP信令走的路径,和媒体流走的路径不一样。

3、Full Cone 完全圆锥体NAT
因特网上的任何PC,可以发送数据包到IP:port对;而NAT将这个IP:port对(公网上的)映射到内网的IP:port对(私有网络的)。

4、restricted cone 受限制的圆锥体NAT
NAT外面的PC,只有那些内网中已有PC与之联系过的PC,才能通过这个映射进来。例如,我通过内网的一台机器,IP 地址是10.1.1.1:123,与PC(a)联系,则PC(a)也可以通过这个NAT的映射,联系到我。而PC(b)则不行。
10.1.1.1:123 ---NAT ---> 202.70.65.78:10000 ------pc(a)
如果pc(b)也发送数据到202.70.65.78:10000,则不会有数据送到10.1.1.1:123

补充说明:
如果我也联系过pc(b),则pc(b)也可以进来了。
10.1.1.1:123 ---NAT ---> 202.70.65.78:10000 ------pc(b)
如果pc(b)也发送数据到202.70.65.78:10000,则数据送到10.1.1.1:123

5、port restricted 端口受限制的圆锥体NAT
除了4的条件外,即不但要检测pc(a)的源IP地址,还要检测其端口是否与前面也一样。
10.1.1.1:123 --->NAT---->202.70.65.78:10000 -----> pc(a)[213.123.324.34:8000]

这个NAT只会接收从IP地址 213.123.324.34 且端口为 8000来的数据,让进入到10.1.1.1:123

6、对称的NAT 这是关系描述最简单的一个
10.1.1.1:1000 ----NAT -----> 200.123.123.34:1234 ----pc(a)
10.1.1.1:1000 ----NAT -----> 200.123.123.34:2222----pc(b)
这种NAT的IP:port对,对每个外部的程序,都是不同的。因而每一个外部的程序,都有自己的映射(NAT分配的IP:port对)。而前面的三种,多个外部程序,可能共用一个NAT的IP:port对。

7、RTP的问题
在RTP的消息正文内,有UA能够成功通信所需要的一些信息。这种消息正文,就叫做SDP消息。

问题在于,SIP终端(UA)可能对NAT一无所知。因而在SDP中包含的IP地址,通常使用内部的IP地址,也就是SIP终端知道的IP。这样,当通信的对方想与SIP终端通信时,就查看SDP消息中的IP地址,但是什么也没有得到,因为这里使用的是内部IP地址。

下面举个例子说明:

INVITE sip:040600@192.168.20.2:5060 SIP/2.0.
Record-Route: <sip:143.248.130.35;ftag=3a7ceb24a6ac50c4;lr=on>.
Via: SIP/2.0/UDP 143.248.130.35;branch=z9hG4bK758e.976609c7.0.
Via: SIP/2.0/UDP
192.168.20.3;rport=1024;received=223.178.140.109;branch=z9hG4bK34efcab2403aa20d.
From: "Iqbal" <sip:040618@sip.dom.com>;tag=3a7ceb24a6ac50c4.
To: <sip:040600@sip.dom.com>.
Contact: <sip:040618@223.178.140.109:1024>.
Supported: replaces.
Call-ID: 7f2c327896a5b0e1@192.168.20.3.
CSeq: 8717 INVITE.
User-Agent: Grandstream HT487 1.0.5.18.
Max-Forwards: 16.
Allow: INVITE,ACK,CANCEL,BYE,NOTIFY,REFER,OPTIONS,INFO,SUBSCRIBE.
Content-Type: application/sdp.
Content-Length: 343.
.
v=0.
o=040618 8000 1 IN IP4 192.168.20.3.
s=SIP Call.
c=IN IP4 192.168.20.3.
t=0 0.
m=audio 38660 RTP/AVP 0 8 4 18 2 15 99.
a=sendrecv.
a=rtpmap:0 PCMU/8000/3.
a=rtpmap:8 PCMA/8000/3.

SIP消息的标题头,类似于邮件的标题头。从后往前看,从From行开始,看到第一个Via行,这是SIP终端自己认为的IP地址,例如192.168.20.3。但是SIP代理服务器是聪明的,它知道这个消息是从哪里发过来的,它添加了rport和接收到的标志:
Via: SIP/2.0/UDP
192.168.20.3;rport=1024;received=223.178.140.109;branch=z9hG4bK34efcab2403aa20d.

也就是说,SIP代理服务器,知道发消息的SIP终端的公网地址是223.178.140.109:1024
这样,SIP代理服务器是可以与SIP终端通信的,因为它知道SIP终端的公网地址。

但是,可怜的老式的RTP被阻塞了,因为它的标题头如下:
v=0.
o=040618 8000 1 IN IP4 192.168.20.3.
s=SIP Call.
c=IN IP4 192.168.20.3.
t=0 0.
m=audio 38660 RTP/AVP 0 8 4 18 2 15 99.
a=sendrecv.
a=rtpmap:0 PCMU/8000/3.
a=rtpmap:8 PCMA/8000/3.

SIP终端期望从端口 port m=38660 且IP地址IP c= 192.168.20.3来接收RTP数据,而这个192.168.20.3:38660,就是通信的对方试图发RTP数据的目的地址。

这就是SIP电话振铃总是能够听到,而接起来却没有声音的原因。

8 解决办法 告诉SIP终端,不要如此傻地工作,而要想办法知道NAT分配给自己的端口映射。

并将公网的IP地址:端口放到SDP消息中。这样,SIP终端就问NAT....。或者是问公网的某个服务器,NAT分配给自己的映射是什么。

9 问NAT。 这种办法就是使用UPnP协议,另外去参见UPnP的资料。

10 问公网上的某个服务器。 如STUN服务器。
SIP终端发送一个探测数据包,到公网上的服务器。公网上的服务器会发回数据包,会包含有关NAT的详细信息。有了这些信息,SIP终端就会知道它是否在NAT后面。这种探测方法,可以用于上面4种NAT,都有效。例如SIP终端从10.1.1.1:1000发送一个数据包,则SDP消息中包含的是m=1000 and c=10.1.1.1。但是,如果SIP终端先进行NAT探测,则会知道NAT会分配公网的IP:端口是212.134.123.23:12345。则 SIP终端直接在SDP消息写m=12345,c=212.134.123.23。

产生的问题 因为NAT的端口分配是动态的,因而有可能会改变。这样,在发送了NAT探测消息后,要很快地发送出SIP消息。而且,SIP终端发送数据的端口和接收数据的端口要是一样的。

注意受限制的圆锥体(包括端口受限制的圆锥体)NAT,它不让回来的消息进来,除非SIP终端先发了一个数据包给它。因而,SIP终端需要先发一个数据包给对方。这样以后对方来的数据,就可以进入NAT内部了(不过,不需要为这个操作担心,有办法的)。

上面的办法在对称的NAT后面,不能够工作。因为,对称的NAT,它在分配给SIP终端外部的IP:port时,每次都变化(不同的对方不一样)。也就是SIP终端在探测NAT时,得到的IP:port,与它后来发SIP消息时,分配的IP:port不一样。这样,对方来的语音数据就进不来,因为对方得不到正确的IP:port。

11、上面描述的过程,其实就是采用STUN协议时,解决问题的过程。就是SIP终端向STUN服务器发探测NAT的数据包。

12、对称的NAT 解决办法 就是在公网上放一个中转语音流的服务器。这个中转语音流的服务器有时就是一个Out Bound proxy。注意,这个中转语音流的服务器,可能会成为瓶颈。由于语音要经过中转语音流的服务器,所以路径增长了,音质会变差。所以,对称的NAT,要SIP能够工作,总之是个麻烦。

不过,目前大多数的NAT,都可以做“虚拟服务器的端口转发”,即将SIP工作需要的端口转发到SIP终端上去。如果在NAT设备上,做了“虚拟服务器的端口转发”,则NAT会保留SIP工作需要的端口,专用于SIP终端,这样SIP终端就相当于是在一个Full Cone完全圆锥体的NAT后面,SIP用STUN工作没有问题。SIP终端需要的端口数是这样确定的,一个端口用于SIP信令,比如5060。RTP端口的数量,取决于通话的路数。一路通话需要2个RTP端口。每增加一路通话,则需要多2个端口。
只支持一路通话的SIP话机,需要NAT映射3个端口。
前面三种的NAT,可统称为非对称的NAT。非对称的NAT,都可以用STUN协议穿过NAT。

将对称的NAT,通过端口转发的方式,变为完全圆锥体的NAT,是解决NAT问题的最好办法。首先应当用这种办法来解决。没有办法映射端口,再想其它的办法。当然,建议大家不要买那种不能够映射端口的宽带路由器,那样的路由器太差了。

摘自:http://blog.163.com/hlz_2599/blog/static/142378474201157115119157/

【转】STUN在SIP中的工作原理及过程

STUN是RFC3489规定的一种NAT穿透方式,它采用辅助的方法探测NAT的IP和端口。毫无疑问的,它对穿越早期的NAT起了巨大的作用,并且还将继续在ANT穿透中占有一席之地。

       STUN的探测过程需要有一个公网IP的STUN server,在NAT后面的UAC必须和此server配合,互相之间发送若干个UDP数据包。UDP包中包含有UAC需要了解的信息,比如NAT外网 IP,PORT等等。UAC通过是否得到这个UDP包和包中的数据判断自己的NAT类型。

       假设有如下UAC(B),NAT(A),SERVER(C),UAC的IP为IPB,NAT的IP为 IPA ,SERVER的 IP为IPC1 、IPC2。请注意,服务器C有两个IP,后面你会理解为什么需要两个IP。 

       (1)NAT的探测过程:


        STEP1:B向C的IP1的pot1端口发送一个UDP 包。C收到这个包后,会把它收到包的源IP和port写到UDP包中,然后把此包通过IP1和port1发还给B。这个IP和port也就是NAT的外网 IP和port(如果你不理解,那么请你去看我的BLOG里面的NAT的原理和分类),也就是说你在STEP1中就得到了NAT的外网IP。

        熟悉NAT工作原理的朋友可以知道,C返回给B的这个UDP包B一定收到(如果你不知道,去读下我的其它文章)。如果在你的应用中,向一个STUN服务器发送数据包后,你没有收到STUN的任何回应包,那只有两种可能:1、STUN服务器不存在,或者你弄错了port。2、你的NAT拒绝一切UDP包从外部向内部通过(我们公司的NAT就是)。

       当B收到此UDP后,把此UDP中的IP和自己的IP做比较,如果是一样的,就说明自己是在公网,下步NAT将去探测防火墙类型,我不想多说。如果不一样,说明有NAT的存在,系统进行STEP2的操作。

       STEP2:B向C的IP1发送一个UDP包,请求C通过另外一个IP2和PORT(不同与SETP1的IP1)向B返回一个UDP数据包(现在知道为什么C要有两个IP了吧,虽然还不理解为什么,呵呵)。

       我们来分析一下,如果B收到了这个数据包,那说明什么?说明NAT来着不拒,不对数据包进行任何过滤,这也就是STUN标准中的full cone NAT。遗憾的是,full cone nat太少了,这也意味着你能收到这个数据包的可能性不大。如果没收到,那么系统进行STEP3的操作。

       STEP3:B向C的IP2的port2发送一个数据包,C收到数据包后,把它收到包的源IP和port写到UDP包中,然后通过自己的IP2和port2把此包发还给B。

       和step1一样,B肯定能收到这个回应UDP包。此包中的port是我们最关心的数据,下面我们来分析:

        如果这个port和step1中的port一样,那么可以肯定这个NAT是个CONE NAT,否则是对称NAT。道理很简单:根据对称NAT的规则,当目的地址的IP和port有任何一个改变,那么NAT都会重新分配一个port使用,而在step3中,和step1对应,我们改变了IP和port。因此,如果是对称NAT,那这两个port肯定是不同的。

       如果在你的应用中,到此步的时候PORT是不同的,恭喜你,你的STUN已经死了。如果不同,那么只剩下了restrict cone 和port restrict cone。系统用step4探测是是那一种。

       STEP4:B向C的IP2的一个端口PD发送一个数据请求包,要求C用IP2和不同于PD的port返回一个数据包给B。

      我们来分析结果:如果B收到了,那也就意味着只要IP相同,即使port不同,NAT也允许UDP包通过。显然这是restrict cone NAT。如果没收到,没别的好说,port restrict  NAT.

      (2)SIP怎么使用STUN

       个人认为这是个很不值得提的问题,不过有许多人问我,还是简要提一下。其实这是个很简单的问题,SIP通过STUN得到NAT的外网IP和SIP的信令监听端口的外网port,替换SIP注册包中的contact头中的IP和port,然后注册。这样就可以确保当有人呼叫你的的时候注册服务器能找到你。需要提醒你的是,NAT发现一个连接超过一段时间后没有活动,它就会关闭这个影射,因此你必须间隔一端时间发送一个数据包出去以keep alive。

        另外,当你要和别人建立RTP通讯的时候,不要忘记把你的SDP中的IP和PORT改成公网IP和PORT。

        也许文章到这里就可以完了,不过很遗憾,显然我们还有工作没有完成,那就是对称NAT。在我后面的文章中,我会对对称NAT对一些探讨,希望你关注我的BLOG,谢谢

摘自:http://blog.163.com/hlz_2599/blog/static/14237847420115384442348/

【转】PHP删除目录及目录下所有文件

PHP删除目录及目录下所有文件
  1. <?php  
  2. //循环删除目录和文件函数  
  3. function delDirAndFile( $dirName )  
  4. {  
  5. if ( $handle = opendir( "$dirName" ) ) {  
  6.    while ( false !== ( $item = readdir( $handle ) ) ) {  
  7.    if ( $item != "." && $item != ".." ) {  
  8.    if ( is_dir"$dirName/$item" ) ) {  
  9.    delDirAndFile( "$dirName/$item" );  
  10.    } else {  
  11.    if( unlink( "$dirName/$item" ) )echo "成功删除文件: $dirName/$item<br />\n";  
  12.    }  
  13.    }  
  14.    }  
  15.    closedir$handle );  
  16.    ifrmdir$dirName ) )echo "成功删除目录: $dirName<br />\n";  
  17. }  
  18. }  
  19. ?>  

函数代码二:仅删除指定目录下的文件,不删除目录文件夹。

  1. class shanchu {  
  2. //循环目录下的所有文件  
  3. function delFileUnderDir( $dirName="../Smarty/templates/templates_c" )  
  4. {  
  5. if ( $handle = opendir( "$dirName" ) ) {  
  6.    while ( false !== ( $item = readdir( $handle ) ) ) {  
  7.    if ( $item != "." && $item != ".." ) {  
  8.    if ( is_dir"$dirName/$item" ) ) {  
  9.          delFileUnderDir( "$dirName/$item" );  
  10.    } else {  
  11.    if( unlink( "$dirName/$item" ) )echo "成功删除文件: $dirName/$item<br />\n";  
  12.    }  
  13.    }  
  14.    }  
  15.    closedir$handle );  
  16. }  
  17. }  
  18. }  
  19. ?>  
  20. <?php  
  21. $user = new shanchu();  
  22. $user->delFileUnderDir();  
  23.   
  24. ?>  

使用方法示例:

假设需要删除一个名叫"upload"的同级目录即此目录下的所有文件,你可以通过以下代码完成:

  1. <?php  
  2. delDirAndFile( 'upload');  
  3. ?>  
  4.    

假设需要删除一个名叫"upload"目录下的所有文件(但无需删除目录文件夹),你可以通过以下代码完成:

  1. <?php  
  2. delFileUnderDir( 'upload');  
  3. ?>  
  4.    

2013年2月28日星期四

【转】SIP keep-alive方法

摘自:http://blog.csdn.net/xuyunzhang/article/details/8439006 

                                  SIP keep-alive方法

在SIP族协议中,只有RFC4028明确讨论了对话keep-alive问题。实际上这在工程应用、生产环境部署中,是个非常重 要的话题,尤 其是SIP基于UDP协议时,网络原因丢包是很常见的,另外还有软终端任意退出对话等情况。缺乏keep-alive保护的SIP服务器毫无疑问将会严重 消耗资源,最终导致整个server被迫退出服务。

RFC4028协议考虑到有状态Proxy、无状态Proxy等情况,提出扩展 Session-Expires以及Min-SE两个新的 Header,并且通过reINVITE消息来keep-alive dialog。基本上,这个协议本身没有太多问题,按照它的思路,的确是可以解决keep-alive的问题,但是在实际部署中,该协议未必可取,有很大 的缺陷:

(1)SIP运营商可能会拒绝reINVITE消息。实际上很多SIP运营商会拒绝reINVITE消息,这是出于SIP运营 商媒体处理方面的考 虑。最初应用reINVITE就是为了修改媒体流,而这毫无疑问会导致SIP运营商媒体服务器重新分配资源等情况出现。RFC4028中只是定义了新增 Header和callFlow,不幸的是,却没有区分出这个reINVITE与更改媒体流的reINVITE,因此实际部署时是否有效,我们需要打一个 很大的问号。

(2)采用reINVITE流程太过重型了。正如前面所说,keep-alive的reINVITE消息是没有与更改媒体 流的reINVITE进行 区分,因此可以肯定绝大部分SIP服务器或者终端,在收到这个reINVITE消息时,会启动媒体更改流程。对话内重改媒体毫无疑问是个很重型的流程,一 旦对话内本身就可能有媒体类业务,例如Music On Hold等,就很有可能导致冲突。

如果不采用reINVITE消息,在4028协议中也同时建议可以采用UPDATE消息。在UPDATE消息中可以不携带SDP更改媒体。这种方式可能是比较可行,但是未必所有的SIP设备会支持UPDATE消息并用于keep-alive过程。

方案之二应该采用SIP最基础协议RFC3261中定义的OPTIONS消息。理由如下:

(1)该消息定义在RFC3261协议中,这个协议是整个SIP协议族的基础。基本上不可能有SIP设备(包括服务器、Proxy、终端等)会不支持这个消息。

(2)我们注意到,这个消息可以在对话内,也可以在对话外使用。在RFC3261中很明确地定义了:

An OPTIONS request received within a dialog generates a 200 (OK) response that is identical to one constructed outside a dialog and does not have any impact on the dialog.

(3)对话内使用OPTIONS,最显著的优点就是“does not have any impact on the dialog”,对现有对话没有任何影响,更不可能去更改媒体。

(4)对端设备如果由于某种原因已经退出,当然就不能产生200OK响应消息,此时可以理所当然地拆除当前对话,从而保护服务器自身资源,达到keep-alive的目的。

对 于设备层级的keep-alive,采用OPTIONS没有任何问题。但是对于dialog层级的keep-alive,则会有问题。原因在 于:dialog内的OPTIONS消息有可能被对端作为对话外的OPTIONS来处理。也就是说,如果设备是alive的,但是dialog的BYE消 息丢失了,无论dialog内还是dialog外,设备对OPTIONS都可能会返回200OK。

Requests that do not change in any way the state of a dialog may be received within a dialog (for example, an OPTIONS request).  They are  processed as if they had been received outside the dialog.

方案三是采用RFC5626协议,对于UDP-SIP的情况,该协议建议采用STUN keep-alive方式。缺陷在于:定义有些复杂,而且很多SIP设备未必会支持。

呼叫服务器和媒体服务器合一的情况下,当然也可以由媒体服务器检测RTP/RTCP来keep-alive,不过这是另外一个topic了,这种方式可能更合适于SIP终端设备。


本文转载自: http://www.cnblogs.com/xiaOt119/archive/2012/07/13/2590105.html

【转】用OPTIONS方法实现Keep Alive

noiile@hotmail.com

在SIP应用中,假如用户在用SIP UA享受某种计时收费的业务,比如在线观看收费电视,突然死机,这时服务器不能得知任何关于用户设备死机的信息,计费服务仍在继续。

这显然是种不合理的情况,服务器必须实时的知道用户的状态,这种情况presence服务器也不一定能帮助解决问题。因为用户的SIP UA只有在改变状态的情况下才发PUBLISH消息到Presence Server,也就是说要等这个PUBLISH expired,服务器才能知道用户已经不在线了。但是用户无论发PUBLISH还是REGISTER,它的过期时间的设置都取决于用户的SIP UA,很多时候,用户会把这个时间设为10分钟,或更长。这就不能满足某些业务实时性高的要求。

 

很容易得出结论,对于某些实时性要求高的业务,比如IPTV Server,无论是REGISTER还是PUBLISH都不能解决问题。解决方案需要考虑以下几点:

1.         利用现有的SIP方法,并且不取决于用户SIP UA

2.         用户的SIP UA不必对此做任何特殊的处理。

3.         且时间控制取决于服务方,即Application Server或其他Server,因为不同的服务可能对时间精度要求不同。

 

很明显,如果Server周期的发一个SIP请求到终端,这个周期时间取决于该Server,如果用户的SIP UA有回response,则表示用户还是online,如果用户没有回response,那它就是offline的,Server可以终止该用户的服务。

但是,用什么SIP方法最合适呢?OPTIONS。因为RFC3261定义,所有的UA都必须支持OPTIONS方法,所以OPTIONS方法满足了上面提及的2点要求。而且OPTIONS方法简单,只是请求用户SIP UA的能力,不需要人机交互,与Dialog无关。所以,用OPTIONS方法来获知用户是否online再合适不过了。

 

其流程如下图:

摘自:http://blog.csdn.net/noiile/article/details/448152