实现TCP的P2P打洞C语言源代码_第1页
实现TCP的P2P打洞C语言源代码_第2页
实现TCP的P2P打洞C语言源代码_第3页
已阅读5页,还剩7页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

1、哪位朋友搞过P2P的能说说怎么实现 ClientA要求Server发送信息给ClientB向ClientA打洞?原理大体知道点:想实现ClientA 与B通信,必须让Server通知ClientB向ClientA 打洞,因为ClientA 直接向B发送信息,出于安全考虑会被禁止的,所以在A向B通信之前,必须让 B发送信息给A但是我不明白ClientB发送信息给A不一样会被A给禁止么?你好,实现过程如下:服务器端Server启动两个网络侦听。客户端CilentA和客户端CilentB分别与服务器端Server保持联系。当CilentA需要和CilentB建立直接的TCP连接时,首先连接服务器端

2、Server的打洞端口,并发送协助连 接申请。同时在该端口号上启动侦听。SERVE连接收到CilentA的申请后通知CilentB,并将CilentA经过公网IP地址和端口等信息告诉 CilentBCilentB收到服务器端Server的连接通知后首先与Server的打洞端口连接,随便发送一些数据后立即断开,这样做的目的是让服务器Server能知道CilentB的公网IP和端口号。CilentB尝试与CilentA 的公网IP地址和端口进行 connect客户端B打洞的同时在相同的端口上启动侦听。CilentB在一切准备就绪以后通过与服务器Server在收到以后将CilentB 的公网IP和端

3、口号告诉给 CilentACilentA收到服务器Server回复的CilentB的公网IP和端口号等信息以后,开始连接到CilentB 公网 IP和端口号,从而直接的 TCP连接建立起来了/ 服务器SERVERS址和端口号定义#define SRV_TCP_MAIN_PORT接的端口号#define SRV_TCP_HOLE_PORT客户端打洞申请的端口号4000/服务器主连8000/服务器响应这两个端口是固定的,服务器Server启动时就开始侦听这两个端口了/将新客户端登录信息发送给所有已登录的客户端,但不发送给自己/BOOL SendNewUserLoginNotifyToAII ( L

4、PCTSTR lpszClientIP, UINT nClientPort,DWORDdwID )0&& pSockClient->m_dwlDASSERT ( lpszClientIP && nClientPort > 0);g_CSFor_PtrAry_SockClient.Lock();for ( int i=0; im_bMainConn && pSockClient->m_dwID >!= dwID )if ( !pSockClient->SendNewUserLoginNotify ( IpszClien

5、tIP,nClientPort,dwID )g_CSFor_PtrAry_SockClient.Unlock();E;return FALSg_CSFor_PtrAry_SockClient.Unlock ();return TRUE;IP地址、端口号)发当有新的客户端连接到服务器时,服务器负责将该客户端的信息(送给其他客户端。/ 执行者:客户端A/ 有新客户端B登录了,我(客户端 A)连接服务器端口SRV_TCP_HOLE_POR丁申请与客户端B建立直接的TCP连接/BOOL Handle_NewUserLogin(CSocket & MainSock, t_NewUserLogin

6、Pkt*pNewUserLoginPkt )ewUserLoginPkt->szClientlP,printf("New user (%s:%u:%u ) login servern",pNnPkt->dwlD );号 SRV_TCP_HOLE_PORTpNewUserLoginPkt->nClientPort,pNewUserLogiBOOL bRet = FALSE;DWORDdwThreadID =0;t_ReqConnClientPkt ReqConnClientPkt;CSocket Sock;CString csSocketAddress;ch

7、ar szRecvBufferNET_BUFFER_SIZE =0;int nRecvBytes =0;/创建打洞Socket,连接服务器协助打洞的端口tryif ( !Sock.Socket ()printf ( "Create socketfailed%sn", hwFormatMessage(GetLastError());goto finished;UINTifnOptValue!Sock.SetSockOpt ( SO_REUSEADDR &1;nOptValue ,et failed :iled : %snRV_TCP_HOLE.:%d failed);

8、sizeof(UINT) )printf("SetSockOpt sock%sn", hwFormatMessage(GetLastError()hwFormatMessage(GetLastError()PORT )%sn", g_pServerAddess,ifif););gotofinished;!Sock.Bind (printfgoto!Sock.Connectprintf("Bind socketfinished;( g_pServerAddess,("Connect tofa%sSRV_TCP_HOLE_PORThwFormatM

9、essage(GetLastError();or );t );/创建一个线程来侦听端口dwThreadID = 0;g_nHolePort 的连接请求goto finished;catch ( CException e )char szError255= 0;e.GetErrorMessage(szError,sizeof(szErrorprintf ( "Exception occur, %sn", szErr goto finished;g_pSock_MakeHole = & Sock;ASSERT ( g_nHolePort = 0 );VERIFY ( S

10、ock.GetSockName ( csSocketAddress, g_nHolePor:CreateThread(NULL, 0, :ThreadPg_hThread_Listenroc_Listen, LPVOID(NULL), 0, &dwThreadID );if (!HANDLE_IS_VALID(g_hThread_Listen) ) return FALSE; Sleep (3000);/ 我(客户端A)向服务器协助打洞的端口号 SRV_TCP_HOLE_POR发送申请,希望与新登录的客户端B建立连接/服务器会将我的打洞用的外部IP和端口号告诉客户端 BASSERT (

11、 g_WelcomePkt.dwlD >0);ReqConnClientPkt.dwInviterlD = g_WelcomePkt.dwlD; ReqConnClientPkt.dwInvitedID = pNewUserLoginPkt->dwlD;if ( Sock.Send ( & ReqConnClientPkt,sizeof(t_ReqConnClientPkt) )!= sizeof(t_ReqConnClientPkt) )goto finished;/等待服务器回应,将客户端B的外部IP地址和端口号告诉我(客户端A)nRecvBytes = Sock.Re

12、ceive ( szRecvBuffer, sizeof(szRecvBuffer) );if ( nRecvBytes >0 )ASSERT ( nRecvBytes = sizeof(t_SrvReqDi rectConnectPkt) );PACKET_TYPE*pePacketType = (PACKET_TYPE* )szRecvBuffer;ASSERT ( pePacketType && *pePacketType=PACKET_TYPE_TCP_DIRECT_CONNECTSleep (1000);Handle_SrvReqDirectConnect (

13、(t_SrvReqDirectConnectPkt*)szRecvBuffer);printf("Handle_SrvReqDirectConnectn");/对方断开连接了elsegotofinished;bRet =TRUE;finished:g_pSock_MakeHole =NULL;returnbRet;这里假设客户端A先启动,当客户端B启动后客户端A将收到服务器S的新客户端登录的通知,并得到客户端 B的公网IP和端口,客户端 A启动线程连接S的【协助打洞】端口 (本地端口号可 以用GetSocketName()函数取得,假设为 M),请求S协助TCP打洞,然后启

14、动线程侦听该本地端口上的连 接请求,然后等待服务器的回应。/ 客户端A请求我(服务器)协助连接客户端B,这个包应该在打洞 Socket中收到/BOOL CSockClient:Handle_ReqConnClientPkt(t_ReqConnClientPkt*pReqConnClientPkt)ASSERT ( !m_bMainConn );CSockClient *pSockClient_B= FindSocketClient( pReqConnClientPkt->dwlnvitedlD);if ( !pSockClient_B) returnFALSE;printf( "

15、;%s:%u:%u invite%s:%u:%u connection'"',m_csPeerAddress, m_nPeerPort,m_dwID,pSockClient_B->m_csPeerAddress,pSockClientB->m_nPeerPort, pSockClient_B->m_dwlD/ 客户端A想要和客户端B建立直接的TCP连接,服务器负责将 A的外部IP和端口号告诉给Bt_SrvReqMakeHolePkt SrvReqMakeHolePkt;SrvReqMakeHolePkt.dwInviterID = pReqConnC

16、lientPkt->dwlnviterID;SrvReqMakeHolePkt.dwInviterHoleID= m_dwID;SrvReqMakeHolePkt.dwInvitedID= pReqConnClientPkt->dwlnvitedID;STRNCPY_CS( SrvReqMakeHolePkt.szClientHolelP,m_csPeerAddress );SrvReqMakeHolePkt.nClientHolePort= m_nPeerPort;if ( pSockClient_B->SendChunk ( & SrvReqMakeHolePkt

17、,sizeof(t_SrvReqMakeHolePkt),0 )!= sizeof(t_SrvReqMakeHolePkt) )return FALSE;/等待客户端B打洞完成,完成以后通知客户端 A直接连接客户端外部IP和端口号if(!HANDLE_IS_VA LID(m_hEvtWaitClientBHole)returnFALSE;if(WaitForSingleObject( m_hEvtWaitClientBHole,6000*1000)= WAIT_OBJECT_0 )/dif ( SendChunk ( &m_SrvReqDirectConnectPkt, sizeof(

18、t_SrvReqDirectConnectPkt),sizeof(t_SrvReqDirectConnectPkt)return TRUE;return FALSE;服务器S收到客户端A的协助打洞请求后通知客户端B,要求客户端B向客户端A打洞,即让客户端B尝试与客户端 A的公网IP和端口进行connect。/ 执行者:客户端B/ 处理服务器要我(客户端B)向另外一个客户端(A)打洞,打洞操作在线程中进行。II 先连接服务器协助打洞的端口号SRV_TCP_HOLE_PORT通过服务器告诉客户端 A我(客户端B)的外部IP地址和端口号,然后启动线程进行打洞,/ 客户端A在收到这些信息以后会发起对我

19、(客户端B)的外部IP地址和端口号的连接(这个连接在客户端B打洞完成以后进行,所以/ 客户端B的NAT不会丢弃这个SYN包,从而连接能建立)/BOOL Handle_SrvReqMakeHole ( CSocket & MainSock, t_SrvReqMakeHolePkt *pSrvReqMakeHolePkt )ASSERT ( pSrvReqMakeHolePkt ); / 创建Socket,连接服务器协助打洞的端口 号 SRV_TCP_HOLE_PQR连接建立以后发送一个断开连接的请求给服务器,然后连接断开/ 这里连接的目的是让服务器知道我(客户端B)的外部IP地址和端口号

20、,以通知客户端 AfailedCSocketSock;tryif(!Sock.Create ()printf("Create socket%sn", hwFormatMessage(GetLastError();returnFALSE;if(!Sock.Connectg_pServerAddess, SRV_TCP_HOLE_PORT )printf("Connect to %s:%d failed%sn", g_pServerAddess,SRV_TCP_HOLE_PORThwFormatMessage(GetLastError();return FA

21、LSE;catch (CException e )char szError255= 0;e.GetErrorMessage(szError,sizeof(szError);printf ( "Exception occur, %sn", szErr or );return FALSE;CString csSocketAddress;ASSERT ( g_nHolePort = 0 );VERIFY ( Sock.GetSockName ( csSocketAddress, g_nHolePor t );/连接服务器协助打洞的端口号SRV_TCP_HOLE_PO发送一个断开连

22、接的请求,然后将连接断开,服务器在收到这个包的时候也会将/连接断开t_ReqSrvDisconnectPkt ReqSrvDisconnectPkt;ReqSrvDisconnectPkt.dwInviterlD = pSrvReqMakeHolePkt->dwln vitedID;ReqSrvDisconnectPkt.dwInviterHoleID= pSrvReqMakeHolePkt->dwInviterHoleID;ReqSrvDisconnectPkt.dwInvitedID = pSrvReqMakeHolePkt->dwln vitedID;ASSERT (

23、ReqSrvDisconnectPkt.dwInvitedID = g_WelcomePkt .dwID );if ( Sock.Send ( & ReqSrvDisconnectPkt,sizeof(t_ReqSrvDisconnectPkt) )!= sizeof(t_ReqSrvDisconnectPkt)return FALSE;Sleep (100);Sock.Close ();/创建一个线程来向客户端A的外部IP地址、端口号打洞t_SrvReqMakeHolePkt *pSrvReqMakeHolePkt_New = new t_SrvR eqMakeHolePkt;if

24、( !pSrvReqMakeHolePkt_New ) return FALSE;memcpy ( pSrvReqMakeHolePkt_New, pSrvReqMakeHolePkt, siz eof(t_SrvReqMakeHolePkt) );DWORDdwThreadID =0;g_hThread_MakeHole = :CreateThread ( NULL, 0, :Threa dProc_MakeHole, LPVOID(pSrvReqMakeHolePkt_New), 0,& dwThreadID );if (!HANDLE_IS_VALID(g_hThread_Mak

25、eHole) ) return FALSE;/创建一个线程来侦听端口g_nHolePort的连接请求dwThreadID = 0;g_hThread_Listen = :CreateThread ( NULL, 0, :ThreadP roc_Listen, LPVOID(NULL), 0, &dwThreadlD );if (!HANDLE_IS_VALID(g_hThread_Listen) ) return FALSE;/ 等待打洞和侦听完成HANDLE hEvtAry g_hEvt_ListenFinished,g_hEvt_MakeHoleFinished ;if (:Wai

26、tForMultipleObjects(LENGTH(hEvtAry), hEvtAry,TRUE, 30*1000)WAIT_TIMEOUT )return FALSE;t_HoleListenReadyPkt HoleListenReadyPkt;HoleListenReadyPkt.dwInvitedIDpSrvReqMakeHolePkt->dwlnvitedID;HoleListenReadyPkt.dwInviterHolelDpSrvReqMakeHolePkt->dwInviterHoleID;HoleListenReadyPkt.dwInvitedIDpSrvRe

27、qMakeHolePkt->dwInvitedID;if ( MainSock.Send(& HoleListenReadyPkt,sizeof(t_HoleListenReadyPkt)!=sizeof(t_HoleListenReadyPkt):%ufailed%sn",printfg_WelcomePkt.szClientlP, g_WelcomePkt.nClientPort,("Send HoleListenReadyPkt to %shwFormatMessage(GetLastError);()returnTRUE;returnFALSE;客户端

28、B收到服务器S的打洞通知后,先连接 S的【协助打洞】端口号(本地端口号可以用GetSocketName()函数取得,假设为 X,启动线程尝试连接客户端 A的公网IP和端口号,根据路由 器不同,连接情况各异,如果运气好直接连接就成功了,即使连接失败,但打洞便完成了。同时还要启动 线程在相同的端口(即与 S的【协助打洞】端口号建立连接的本地端口号X)上侦听到来的连接,等待客户端A直接连接该端口号。/执行者:客户端A/ 服务器要求主动端(客户端A)直接连接被动端(客户端B)的外部IP和端口号/BOOL Handle_SrvReqDirectConnect ( t_SrvReqDirectConnec

29、tPkt *pSrvReqDirectConnectPkt )ASSERT ( pSrvReqDirectConnectPkt );PORT:printf ( "You can connect direct to ( IP:%s%d ID:%u )n", pSrvReqDirectConnectPkt->szlnvitedlP,pSrvReqDirectConnectPkt->nlnvitedPort,pSrvReqDirectConnectPkt->dwlnvitedlD );/ 直接与客户端B建立TCP连接,如果连接成功说明 TCP打洞已经成功了。CS

30、ocketSock;tryif ( !Sock.Socket ()failed%sn", hwFormatMessage(GetLastError();UINT nOptValueifprintfreturn1;!Sock.SetSockOptnOptValuesizeof(UINT) )printfet failed%sn", hwFormatMessage(GetLastError();returnif!Sock.Bind (printfiled%sn", hwFormatMessage(GetLastError();returnfor("Create socketFALSE;(SO_REUSEADDR&("SetSockOpt sockFALSE;g_nHolePort("BindFALSE;int ii=0; iiszlnv

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论