C++ Winsock P2P

Posted

技术标签:

【中文标题】C++ Winsock P2P【英文标题】: 【发布时间】:2011-02-20 01:25:21 【问题描述】:

场景

有没有人有任何使用 Winsock 在 C++ 中进行点对点 (p2p) 网络的好例子? 这是我对特别需要使用这项技术的客户的要求(天知道为什么)。 我需要确定这是否可行。

任何帮助将不胜感激。

编辑

并且我想避免使用库,以便我可以理解底层源代码并进一步了解我的知识。

【问题讨论】:

【参考方案1】:

由于我不知道您在寻找什么信息,我将尝试描述如何设置套接字程序以及我遇到了哪些陷阱。

首先,*阅读 MSDN 上的Winsock tutorial。这是连接、发送消息和断开连接的基本程序。它非常适合感受套接字编程。

这样,让我们​​开始吧:

注意事项:

阻塞或非阻塞

首先,您需要决定是否需要阻塞或非阻塞程序。如果您有 GUI,则需要使用非阻塞或线程来不冻结程序。我这样做的方式是使用阻塞调用,但总是在调用阻塞函数之前调用select(稍后会详细介绍选择)。这样我就避免了线程和互斥锁之类的,但仍然使用基本的acceptsendreceive 调用。

您不能相信您的数据包会按照您发送它们的方式到达!

您也无法控制这一点。这是我遇到的最大问题,基本上是因为网卡可以决定发送什么信息以及何时发送。我解决它的方法是创建一个networkPackageStruct,其中包含sizedata,其中size 是该数据包中的数据总量。请注意,您发送的一条消息可以拆分为 2 个或更多数据包,也可以与您发送的另一条消息合并。

考虑以下几点:

你发送了两条消息

  "Hello"
  "World!"

当您使用send 函数发送这两条消息时,您的recv 函数可能不会像这样收到它们。它可能看起来像这样:

  "Hel"
  "loWorld!"

或许

  "HelloWorld!"

无论底层网络感觉如何。

记录(几乎)所有内容!

调试网络程序很困难,因为您无法完全控制它(因为它在两台计算机上)。如果您遇到阻塞操作,您也看不到它。这也可以称为“了解您的阻止代码”。当一侧发送一些东西时,您不知道它是否会到达另一侧,因此请跟踪发送的内容和接收的内容。

注意socket错误

Winsock 函数返回大量信息。了解您的WSAGetLastError() 功能。我不会在下面的示例中保留它,但请注意它们往往会返回大量信息。每次收到SOCKET_ERRORINVALID_SOCKET 时,请查看Winsock Error Messages 进行查找。

设置连接:

由于您不需要服务器,所有客户端都需要一个侦听套接字来接受新连接。最简单的是:

  SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
  sockaddr_in localAddress;
  localAddress.sinfamily = AF_INET;
  localAddress.sin_port = htons(10000);  // or whatever port you'd like to listen to
  localAddress.sin_addr.s_addr = INADDR_ANY;

INADDR_ANY 很棒 - 它让您的套接字侦听您的所有 IP 地址,而不仅仅是一个 IP 地址。

  bind(s, (SOCKADDR*)&localAddress, sizeof(localAddress));
  listen(s, SOMAXCONN);

有趣的部分来了。 bindlisten 不会阻止,但 accept 会。诀窍是使用select 检查是否有传入连接。所以上面的代码只是为了设置套接字。在您的程序循环中,您检查套接字中的新数据。

交换数据

我解决它的方法是大量使用select。基本上,您会查看是否需要在任何套接字上响应任何内容。这是通过FD_xxx 函数完成的。

  // receiving data
  fd_set mySet;
  FD_ZERO(&mySet);
  FD_SET(s, &mySet);
  // loop all your sockets and add to the mySet like the call above
  timeval zero =  0, 0 ;
  int sel = select(0, &mySet, NULL, NULL, &zero);
  if (FD_ISSET(s, &mySet))
      // you have a new caller
      sockaddr_in remote;
      SOCKET newSocket = accept(s, (SOCKADDR*)&remote, sizeof(remote));
  
  // loop through your sockets and check if they have the FD_ISSET() set

newSocket 中,您现在有了一个新的同伴。那是为了接收数据。 但请注意!send 也在阻止!我遇到的一个令人头疼的错误是send 阻止了我。不过,select 也解决了这个问题。

 // sending data
 // in: SOCKET sender
 fd_set mySet;
 FD_ZERO(&mySet);
 FD_SET(sender, &mySet);
 timeval zero =  0, 0 ;
 int sel = select(0, NULL, mySet, NULL, &zero);
 if (FD_ISSET(sender, &mySet))
      // ok to send data
 

关机

最后,有两种关机方式。您要么通过关闭程序来断开连接,要么调用shutdown 函数。

调用shutdown 将使您的对等select 触发。但是recv 将不会收到任何数据,而是返回 0。我没有注意到recv 返回 0 的任何其他情况,因此可以(在某种程度上)可以肯定地说这可以被视为关闭代码。打电话给shutdown 是最好的选择。 关闭连接而不调用shutdown 只是冷酷无情,但当然可以。即使您使用shutdown,您仍然需要处理错误,因为关闭连接的可能不是您的程序。最好记住的错误代码是 10054,即 WSAECONNRESET: Connection reset by peer

【讨论】:

这里有一些错误。不推荐在阻塞模式下使用select() 的一般技术,因为它会导致竞争条件:select() 可写性仅保证套接字发送缓冲区中有 一些 空间,而不是对于您要发送的所有数据,有足够,它不知道,所以它不能保证下面的send()不会阻塞..没有必要调用@987654363 @。关闭套接字具有完全相同的效果。 recv() 在对等方关闭或关闭连接端时返回零。【参考方案2】:

如果你只是想在微软 Windows 上实现一个 P2P 应用,你可以试试Windows Peer-to-Peer Networking

如果你想自己实现一个新的P2P协议,你可以研究一下eMule协议,eMule source code。如果您查看Shareaza source code,您可以做进一步的事情,它可以做 eMule/Guntella/Gnutella/BitTorrent。

【讨论】:

以上是关于C++ Winsock P2P的主要内容,如果未能解决你的问题,请参考以下文章

使用 winsock 进行文件传输 (c++)

套接字程序 Python vs C++ (Winsock)

C++ Winsock 结构发送/接收

bind(...) 总是使用 WinSock2 返回 -1 -- C++

C++ winsock 错误

winsock 缺少数据 c++ win32