已阅读5页,还剩4页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
哪位朋友搞过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连接时,首先连接服务器端Server的打洞端口,并发送协助连接申请。同时在该端口号上启动侦听。SERVER连接收到CilentA的申请后通知CilentB,并将CilentA经过公网IP地址和端口等信息告诉CilentB。CilentB收到服务器端Server的连接通知后首先与Server的打洞端口连接,随便发送一些数据后立即断开,这样做的目的是让服务器Server能知道CilentB的公网IP和端口号。CilentB尝试与CilentA的公网IP地址和端口进行connect客户端B打洞的同时在相同的端口上启动侦听。CilentB在一切准备就绪以后通过与服务器Server在收到以后将CilentB的公网IP和端口号告诉给CilentA。CilentA收到服务器Server回复的CilentB的公网IP和端口号等信息以后,开始连接到CilentB公网IP和端口号,从而直接的TCP连接建立起来了。/服务器SERVER地址和端口号定义#defineSRV_TCP_MAIN_PORT4000/服务器主连接的端口号#defineSRV_TCP_HOLE_PORT8000/服务器响应客户端打洞申请的端口号这两个端口是固定的,服务器Server启动时就开始侦听这两个端口了。/将新客户端登录信息发送给所有已登录的客户端,但不发送给自己/BOOLSendNewUserLoginNotifyToAll(LPCTSTRlpszClientIP,UINTnClientPort,DWORDdwID)ASSERT(lpszClientIP&nClientPort0);g_CSFor_PtrAry_SockClient.Lock();for(inti=0;im_bMainConn&pSockClient-m_dwID0&pSockClient-m_dwID!=dwID)if(!pSockClient-SendNewUserLoginNotify(lpszClientIP,nClientPort,dwID)g_CSFor_PtrAry_SockClient.Unlock();returnFALSE;g_CSFor_PtrAry_SockClient.Unlock();returnTRUE;当有新的客户端连接到服务器时,服务器负责将该客户端的信息(IP地址、端口号)发送给其他客户端。/执行者:客户端A/有新客户端B登录了,我(客户端A)连接服务器端口SRV_TCP_HOLE_PORT,申请与客户端B建立直接的TCP连接/BOOLHandle_NewUserLogin(CSocket&MainSock,t_NewUserLoginPkt*pNewUserLoginPkt)printf(Newuser(%s:%u:%u)loginservern,pNewUserLoginPkt-szClientIP,pNewUserLoginPkt-nClientPort,pNewUserLoginPkt-dwID);BOOLbRet=FALSE;DWORDdwThreadID=0;t_ReqConnClientPktReqConnClientPkt;CSocketSock;CStringcsSocketAddress;charszRecvBufferNET_BUFFER_SIZE=0;intnRecvBytes=0;/创建打洞Socket,连接服务器协助打洞的端口号SRV_TCP_HOLE_PORTtryif(!Sock.Socket()printf(Createsocketfailed:%sn,hwFormatMessage(GetLastError();gotofinished;UINTnOptValue=1;if(!Sock.SetSockOpt(SO_REUSEADDR,&nOptValue,sizeof(UINT)printf(SetSockOptsocketfailed:%sn,hwFormatMessage(GetLastError();gotofinished;if(!Sock.Bind(0)printf(Bindsocketfailed:%sn,hwFormatMessage(GetLastError();gotofinished;if(!Sock.Connect(g_pServerAddess,SRV_TCP_HOLE_PORT)printf(Connectto%s:%dfailed:%sn,g_pServerAddess,SRV_TCP_HOLE_PORT,hwFormatMessage(GetLastError();gotofinished;catch(CExceptione)charszError255=0;e.GetErrorMessage(szError,sizeof(szError);printf(Exceptionoccur,%sn,szError);gotofinished;g_pSock_MakeHole=&Sock;ASSERT(g_nHolePort=0);VERIFY(Sock.GetSockName(csSocketAddress,g_nHolePort);/创建一个线程来侦听端口g_nHolePort的连接请求dwThreadID=0;g_hThread_Listen=:CreateThread(NULL,0,:ThreadProc_Listen,LPVOID(NULL),0,&dwThreadID);if(!HANDLE_IS_VALID(g_hThread_Listen)returnFALSE;Sleep(3000);/我(客户端A)向服务器协助打洞的端口号SRV_TCP_HOLE_PORT发送申请,希望与新登录的客户端B建立连接/服务器会将我的打洞用的外部IP和端口号告诉客户端BASSERT(g_WelcomePkt.dwID0);ReqConnClientPkt.dwInviterID=g_WelcomePkt.dwID;ReqConnClientPkt.dwInvitedID=pNewUserLoginPkt-dwID;if(Sock.Send(&ReqConnClientPkt,sizeof(t_ReqConnClientPkt)!=sizeof(t_ReqConnClientPkt)gotofinished;/等待服务器回应,将客户端B的外部IP地址和端口号告诉我(客户端A)nRecvBytes=Sock.Receive(szRecvBuffer,sizeof(szRecvBuffer);if(nRecvBytes0)ASSERT(nRecvBytes=sizeof(t_SrvReqDirectConnectPkt);PACKET_TYPE*pePacketType=(PACKET_TYPE*)szRecvBuffer;ASSERT(pePacketType&*pePacketType=PACKET_TYPE_TCP_DIRECT_CONNECT);Sleep(1000);Handle_SrvReqDirectConnect(t_SrvReqDirectConnectPkt*)szRecvBuffer);printf(Handle_SrvReqDirectConnectendn);/对方断开连接了elsegotofinished;bRet=TRUE;finished:g_pSock_MakeHole=NULL;returnbRet;这里假设客户端A先启动,当客户端B启动后客户端A将收到服务器S的新客户端登录的通知,并得到客户端B的公网IP和端口,客户端A启动线程连接S的【协助打洞】端口(本地端口号可以用GetSocketName()函数取得,假设为M),请求S协助TCP打洞,然后启动线程侦听该本地端口上的连接请求,然后等待服务器的回应。/客户端A请求我(服务器)协助连接客户端B,这个包应该在打洞Socket中收到/BOOLCSockClient:Handle_ReqConnClientPkt(t_ReqConnClientPkt*pReqConnClientPkt)ASSERT(!m_bMainConn);CSockClient*pSockClient_B=FindSocketClient(pReqConnClientPkt-dwInvitedID);if(!pSockClient_B)returnFALSE;printf(%s:%u:%uinvite%s:%u:%uconnectionn,m_csPeerAddress,m_nPeerPort,m_dwID,pSockClient_B-m_csPeerAddress,pSockClient_B-m_nPeerPort,pSockClient_B-m_dwID);/客户端A想要和客户端B建立直接的TCP连接,服务器负责将A的外部IP和端口号告诉给Bt_SrvReqMakeHolePktSrvReqMakeHolePkt;SrvReqMakeHolePkt.dwInviterID=pReqConnClientPkt-dwInviterID;SrvReqMakeHolePkt.dwInviterHoleID=m_dwID;SrvReqMakeHolePkt.dwInvitedID=pReqConnClientPkt-dwInvitedID;STRNCPY_CS(SrvReqMakeHolePkt.szClientHoleIP,m_csPeerAddress);SrvReqMakeHolePkt.nClientHolePort=m_nPeerPort;if(pSockClient_B-SendChunk(&SrvReqMakeHolePkt,sizeof(t_SrvReqMakeHolePkt),0)!=sizeof(t_SrvReqMakeHolePkt)returnFALSE;/等待客户端B打洞完成,完成以后通知客户端A直接连接客户端外部IP和端口号if(!HANDLE_IS_VALID(m_hEvtWaitClientBHole)returnFALSE;if(WaitForSingleObject(m_hEvtWaitClientBHole,6000*1000)=WAIT_OBJECT_0)/dif(SendChunk(&m_SrvReqDirectConnectPkt,sizeof(t_SrvReqDirectConnectPkt),0)=sizeof(t_SrvReqDirectConnectPkt)returnTRUE;returnFALSE;服务器S收到客户端A的协助打洞请求后通知客户端B,要求客户端B向客户端A打洞,即让客户端B尝试与客户端A的公网IP和端口进行connect。/执行者:客户端B/处理服务器要我(客户端B)向另外一个客户端(A)打洞,打洞操作在线程中进行。/先连接服务器协助打洞的端口号SRV_TCP_HOLE_PORT,通过服务器告诉客户端A我(客户端B)的外部IP地址和端口号,然后启动线程进行打洞,/客户端A在收到这些信息以后会发起对我(客户端B)的外部IP地址和端口号的连接(这个连接在客户端B打洞完成以后进行,所以/客户端B的NAT不会丢弃这个SYN包,从而连接能建立)/BOOLHandle_SrvReqMakeHole(CSocket&MainSock,t_SrvReqMakeHolePkt*pSrvReqMakeHolePkt)ASSERT(pSrvReqMakeHolePkt);/创建Socket,连接服务器协助打洞的端口号SRV_TCP_HOLE_PORT,连接建立以后发送一个断开连接的请求给服务器,然后连接断开/这里连接的目的是让服务器知道我(客户端B)的外部IP地址和端口号,以通知客户端ACSocketSock;tryif(!Sock.Create()printf(Createsocketfailed:%sn,hwFormatMessage(GetLastError();returnFALSE;if(!Sock.Connect(g_pServerAddess,SRV_TCP_HOLE_PORT)printf(Connectto%s:%dfailed:%sn,g_pServerAddess,SRV_TCP_HOLE_PORT,hwFormatMessage(GetLastError();returnFALSE;catch(CExceptione)charszError255=0;e.GetErrorMessage(szError,sizeof(szError);printf(Exceptionoccur,%sn,szError);returnFALSE;CStringcsSocketAddress;ASSERT(g_nHolePort=0);VERIFY(Sock.GetSockName(csSocketAddress,g_nHolePort);/连接服务器协助打洞的端口号SRV_TCP_HOLE_PORT,发送一个断开连接的请求,然后将连接断开,服务器在收到这个包的时候也会将/连接断开t_ReqSrvDisconnectPktReqSrvDisconnectPkt;ReqSrvDisconnectPkt.dwInviterID=pSrvReqMakeHolePkt-dwInvitedID;ReqSrvDisconnectPkt.dwInviterHoleID=pSrvReqMakeHolePkt-dwInviterHoleID;ReqSrvDisconnectPkt.dwInvitedID=pSrvReqMakeHolePkt-dwInvitedID;ASSERT(ReqSrvDisconnectPkt.dwInvitedID=g_WelcomePkt.dwID);if(Sock.Send(&ReqSrvDisconnectPkt,sizeof(t_ReqSrvDisconnectPkt)!=sizeof(t_ReqSrvDisconnectPkt)returnFALSE;Sleep(100);Sock.Close();/创建一个线程来向客户端A的外部IP地址、端口号打洞t_SrvReqMakeHolePkt*pSrvReqMakeHolePkt_New=newt_SrvReqMakeHolePkt;if(!pSrvReqMakeHolePkt_New)returnFALSE;memcpy(pSrvReqMakeHolePkt_New,pSrvReqMakeHolePkt,sizeof(t_SrvReqMakeHolePkt);DWORDdwThreadID=0;g_hThread_MakeHole=:CreateThread(NULL,0,:ThreadProc_MakeHole,LPVOID(pSrvReqMakeHolePkt_New),0,&dwThreadID);if(!HANDLE_IS_VALID(g_hThread_MakeHole)returnFALSE;/创建一个线程来侦听端口g_nHolePort的连接请求dwThreadID=0;g_hThread_Listen=:CreateThread(NULL,0,:ThreadProc_Listen,LPVOID(NULL),0,&dwThreadID);if(!HANDLE_IS_VALID(g_hThread_Listen)returnFALSE;/等待打洞和侦听完成HANDLEhEvtAry=g_hEvt_ListenFinished,g_hEvt_MakeHoleFinished;if(:WaitForMultipleObjects(LENGTH(hEvtAry),hEvtAry,TRUE,30*1000)=WAIT_TIMEOUT)returnFALSE;t_HoleListenReadyPktHoleListenReadyPkt;HoleListenReadyPkt.dwInvitedID=pSrvReqMakeHolePkt-dwInvitedID;HoleListenReadyPkt.dwInviterHoleID=pSrvReqMakeHolePkt-dwInviterHoleID;HoleListenReadyPkt.dwInvitedID=pSrvReqMakeHolePkt-dwInvitedID;if(MainSock.Send(&HoleListenReadyPkt,sizeof(t_HoleListenReadyPkt)!=sizeof(t_HoleListenReadyPkt)printf(SendHoleListenReadyPktto%s:%ufailed:%sn,g_WelcomePkt.szClientIP,g_WelcomePkt.nClientPort,hwFormatMessage(GetLastError();returnFALSE;returnTRUE;客户端B收到服务器S的打洞通知后,先连接S的【协助打洞】端口号(本地端口号可以用GetSocketName()函数取得,假设为X),启动线程尝试连接客户端A的公网IP和端口号,根据路由器不同,连接情况各异,如果运气好直接连接就成功了,即使连接失败,但打洞便完成了。同时还要启动线程在相同的端口(即与S的【协助打洞】端口号建立连接的本地端口号X)上侦听到来的连接,等待客户端A直接连接该端口号。/执行者:客户端A/服务器要求主动端(客户端A)直接连接被动端(客户端B)的外部IP和端口号/BOOLHandle_SrvReqDirectConnect(t_SrvReqDirectConnectPkt*pSrvReqDirectConnectPkt)ASSERT(pSrvReqDirectConnectPkt);printf(Youcanconnectdirectto(IP:%sPORT:%dID:%u)n,pSrvReqDirectConnectPkt-szInvitedIP,pSrvReqDirectConnectPkt-nInvitedPort,pSrvReqDirectConnectPkt-dwInvitedID);/直接与客户端B建立TCP连接,如果连接成功说明TCP打洞已经成功了。CSocketSock;tryif(!Sock.Socket()printf(Createsocketfailed:%sn,hwFormatMessage(GetLastError();returnFALSE;UINTnOptValue=1;if(!Sock.SetSockOpt(SO_REUSEADDR,&nOptValue,sizeof(UINT)printf(SetSockOptsocketfailed:%sn,hwFormatMessage(GetLastError();returnFALSE;if(!Sock.Bind(g_nHolePort)printf(Bindsocketfailed:%sn,hwFormatMessage(GetLastError();returnFALSE;for(intii=0;iiszInvitedI
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2023年萍乡辅警协警招聘考试备考题库完整参考答案详解
- 2023年金昌辅警招聘考试真题附答案详解(轻巧夺冠)
- 2023年阜新辅警协警招聘考试备考题库含答案详解(基础题)
- 2024年密云县辅警招聘考试真题附答案详解(满分必刷)
- 2024年东莞辅警招聘考试题库有完整答案详解
- 2023年铜仁辅警招聘考试真题含答案详解(综合卷)
- 2023年黄石辅警协警招聘考试备考题库含答案详解(培优b卷)
- 2024年孝感辅警协警招聘考试备考题库含答案详解(b卷)
- 湖北省鄂东南示范高中教改联盟2025年生物高一第一学期期末调研试题含解析
- 2024年临汾辅警招聘考试真题及答案详解(有一套)
- 超高频超声波治疗的研究进展
- 宝山区2024-2025学年六年级上学期期中考试数学试卷及答案(上海新教材)
- 外科院内感染管理制度
- 2025年中国苯乙烯类热塑性弹性体行业市场前景预测及投资价值评估报告
- 《电商平台店铺日常运营管理》课件
- GA/T 2184-2024法庭科学现场截干树木材积测定规程
- 面向边缘计算的安全解决方案-全面剖析
- 2025年陕西艺术职业学院高职单招语文2019-2024历年真题考点试卷含答案解析
- 氟气基础知识
- 中小学校园中匹克球推广策略与实践研究
- 助贷电销知识培训课件
评论
0/150
提交评论