导航

用udp实现p2p

Posted on 2009-04-27 13:35  loskiller  阅读(1617)  评论(0编辑  收藏  举报
   p2p也就是点对点通讯技术,如果熟悉网络协议的话,p2p的原理是很简单的。内网的电脑要访问外网
必须要经过路由器,路由器会为该电脑分配一个端口,有了这个端口之后,就可以访问外网的电脑。
外网的电脑也是通过这个端口访问这台电脑的,但相对于内网访问外网,外网访问内网是比较困难的,
因为路由器只会让经过认证的ip通过这个端口,p2p的作用就是就在于此。
    我们可以在外网放一台服务器,服务器上记录了来自不通网段的ip和端口。当内网的A电脑要与B
一台电脑通讯,我们可以向服务器发送请求,让服务器通知B电脑,A电脑要与你通讯,请把他的ip

放到路由器上去,这样就可以了。

 

服务器:
void InitWinSock()
{
   vector<User*> users;
   WSADATA wsaData;
   WSAStartup(MAKEWORD(2, 2), &wsaData);
   SOCKET PrimaryUDP=socket(AF_INET, SOCK_DGRAM, 0);
   sockaddr_in local;
   local.sin_addr.s_addr=htonl(INADDR_ANY);
   local.sin_family=AF_INET;
   local.sin_port=htons(3650);
   int nResult=bind(PrimaryUDP,(const sockaddr*)&local,sizeof(sockaddr));

   sockaddr_in sender;
   User *recvbuf=new User();
   for(;;)
   {
      int dwSender = sizeof(sender);
      int ret = recvfrom(PrimaryUDP, (char *)recvbuf, sizeof(User), 0, (sockaddr *)&sender, &dwSender);
      if(recvbuf->cmd==0)
      {
          User *userip=new User();
          userip->ip=sender.sin_addr.s_addr;
          userip->port=sender.sin_port;
          users.push_back(userip);
      }
      else if(recvbuf->cmd==1)
      {
          for(vector<User*>::iterator it=users.begin();it!=users.end();it++)
          {
              if((*it)->port==sender.sin_port)
                  continue;
              (*it)->cmd=1;
              sendto(PrimaryUDP,(const char*)(*it),sizeof(User),0,(const sockaddr*)(&sender),sizeof(sender));
              break;
          }
      }
      else if(recvbuf->cmd==2)
      {
           //解出接收方的ip信息
           sockaddr_in remote;
           remote.sin_family=AF_INET;
           remote.sin_port= recvbuf->port;//直接使用网络字节
           remote.sin_addr.S_un.S_addr = recvbuf->ip;
           //创建发送者的ip信息
           User thisuser;
           memset(&thisuser,0,sizeof(User));
           thisuser.ip=sender.sin_addr.S_un.S_addr;//直接使用网络字节
           thisuser.port=sender.sin_port;
           thisuser.cmd=2;
           //Hole,关键代码
           sendto(PrimaryUDP,(const char*)&thisuser, sizeof(User), 0, (const sockaddr *)&remote, sizeof(remote));
      }
   }
   delete recvbuf;

}

 

int main()
{
   InitWinSock();
   return 0;

}


客户端:
TUser = packed record
  ip:uint;
  port:uint;
  cmd:byte;//0:注册ip 1:取得对方ip 2: 打洞 3:p2p通讯 4:什么也不做
end;

puser=^TUser;

procedure TForm1.Button1Click(Sender: TObject);//打洞
var
  user:PUser;
begin
  GetMem(user,sizeof(Tuser));
  user.ip:=ip;
  user.port:=port;
  user.cmd:=2;
  sendto(PrimaryUDP,user^,sizeof(TUser),0,remote,sizeof(remote));
  FreeMem(user);

end;

procedure TForm1.FormCreate(Sender: TObject);
var
  wsaData:TWSADATA;
begin
  WSAStartup(MakeWord(2,2), wsaData);
  PrimaryUDP:=socket(AF_INET, SOCK_DGRAM, 0);
  remote.sin_addr.S_addr:=inet_addr('192.168.0.6');
  remote.sin_port:=htons(3650);
  remote.sin_family:=AF_INET;
  TRecvThread.Create(false);

end;

procedure TForm1.Button2Click(Sender: TObject);//注册ip
var
   user:PUser;
begin
  GetMem(user,sizeof(Tuser));
  user.ip:=0;
  user.port:=0;
  user.cmd:=0;
  sendto(PrimaryUDP,user^,sizeof(TUser),0,remote,sizeof(remote));
  FreeMem(user);

end;

procedure TForm1.Button4Click(Sender: TObject);//取得对方ip
var
   user:PUser;
begin
  GetMem(user,sizeof(Tuser));
  user.ip:=0;
  user.port:=0;
  user.cmd:=1;
  sendto(PrimaryUDP,user^,sizeof(TUser),0,remote,sizeof(remote));
  FreeMem(user);

end;

procedure TForm1.Button3Click(Sender: TObject);//p2p通讯
var
  user:PUser;
begin
  GetMem(user,sizeof(Tuser));
  user.ip:=ip;
  user.port:=port;
  user.cmd:=3;
  sendto(PrimaryUDP,user^,sizeof(TUser),0,remote,sizeof(remote));//注意,经过打洞后,remote的地址和端口已经发生变化,不再是指向服务器了,而是指向另一端的电脑。
  FreeMem(user);
end;

{ TRecvThread }
procedure TRecvThread.DoShowMessage();
begin
   ShowMessage('hi!I`m duck!');

end;

procedure TRecvThread.Execute;//接收线程
var
  user:PUser;
  len:integer;
  remoteClient:sockaddr_in;
  FDSet:TFDSet;
begin
  FD_ZERO(FDSet);
  FD_SET(PrimaryUDP, FDSet);
  GetMem(user,sizeof(Tuser));
  len:=sizeof(remote);
  while true do
  begin
    if select(0,@FDSet, nil, nil, nil)>0 then
    begin
      recvfrom(PrimaryUDP,user^,sizeof(TUser),0,remote,len);
      if user.cmd=1 then
      begin
        ip:=User.ip;
        port:=User.port;
      end
      else if user.cmd=2 then//开始向路由器打洞
      begin
        remoteClient.sin_family:=AF_INET;//remoteClient是另一台电脑在外网的ip
        remoteClient.sin_port:=User.port;
        remoteClient.sin_addr.S_addr:=User.ip;       
        User.cmd:=4;
        sendto(PrimaryUDP,user^,sizeof(TUser),0,remoteClient,sizeof(remoteClient));
      end
      else if user.cmd=3 then
      begin
        Synchronize(DoShowMessage);
      end;
    end;
  end;
  FreeMem(user);

end;