C++ Winsock P2P

10

场景

有没有人可以提供使用Winsock的C++对等网络(P2P)的好例子?这是我为客户所必须使用的技术要求(天知道为什么)。我需要确定这是否可行。

任何帮助都将不胜感激。

编辑

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

2个回答

45

由于我不知道您正在寻找哪些信息,所以我将尝试描述如何设置套接字程序以及我遇到的问题。

首先,*阅读MSDN上的Winsock教程。 这是一个基本的程序,用于连接、发送消息和断开连接。它非常适合了解套接字编程。

有了这个,让我们开始:

考虑:

  • 阻塞或非阻塞

    首先,您需要决定您想要阻塞或非阻塞程序。 如果您有一个GUI,您需要使用非阻塞或线程以避免冻结程序。 我的做法是使用阻塞调用,但总是在调用阻塞函数之前调用select(稍后会更多地介绍select)。 这样,我避免了线程、互斥锁等等,但仍然使用基本的acceptsendreceive调用。

  • 不能依赖数据包以您发送它们的方式到达!

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

    考虑以下情况:

    您发送两条消息

  "Hello"
  "World!"
当您使用send函数发送这两个消息时,您的recv函数可能无法像这样收到它们。 它可能看起来像这样:
  "Hel"
  "loWorld!"

或许

  "HelloWorld!"

无论底层网络如何。

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

注意套接字错误。Winsock函数返回大量信息。了解您的WSAGetLastError()函数。我不会在下面的示例中保留它,但请注意它们 tend 返还很多信息。每次收到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:对等方重置连接

    1
    这里有一些错误。不建议在阻塞模式下使用select()的通用技术,因为它可能会导致竞争条件:而且仅使用select()进行可写性保证套接字发送缓冲区中有一些空间,而不是有足够的空间来发送所有要发送的数据,因为它不知道这些数据的情况,所以无法保证后续的send()不会被阻塞。没有必要调用shutdown()。关闭套接字具有完全相同的效果。当对等方关闭或关闭连接时,recv()返回零。 - user207421

    2

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接