C++ Winsock P2P
Posted
技术标签:
【中文标题】C++ Winsock P2P【英文标题】: 【发布时间】:2011-02-20 01:25:21 【问题描述】:场景
有没有人有任何使用 Winsock 在 C++ 中进行点对点 (p2p) 网络的好例子? 这是我对特别需要使用这项技术的客户的要求(天知道为什么)。 我需要确定这是否可行。
任何帮助将不胜感激。
编辑
并且我想避免使用库,以便我可以理解底层源代码并进一步了解我的知识。
【问题讨论】:
【参考方案1】:由于我不知道您在寻找什么信息,我将尝试描述如何设置套接字程序以及我遇到了哪些陷阱。
首先,*阅读 MSDN 上的Winsock tutorial。这是连接、发送消息和断开连接的基本程序。它非常适合感受套接字编程。
这样,让我们开始吧:
注意事项:
阻塞或非阻塞
首先,您需要决定是否需要阻塞或非阻塞程序。如果您有 GUI,则需要使用非阻塞或线程来不冻结程序。我这样做的方式是使用阻塞调用,但总是在调用阻塞函数之前调用select
(稍后会详细介绍选择)。这样我就避免了线程和互斥锁之类的,但仍然使用基本的accept
、send
和receive
调用。
您不能相信您的数据包会按照您发送它们的方式到达!
您也无法控制这一点。这是我遇到的最大问题,基本上是因为网卡可以决定发送什么信息以及何时发送。我解决它的方法是创建一个networkPackageStruct
,其中包含size
和data
,其中size 是该数据包中的数据总量。请注意,您发送的一条消息可以拆分为 2 个或更多数据包,也可以与您发送的另一条消息合并。
考虑以下几点:
你发送了两条消息
"Hello"
"World!"
当您使用send
函数发送这两条消息时,您的recv
函数可能不会像这样收到它们。它可能看起来像这样:
"Hel"
"loWorld!"
或许
"HelloWorld!"
无论底层网络感觉如何。
记录(几乎)所有内容!
调试网络程序很困难,因为您无法完全控制它(因为它在两台计算机上)。如果您遇到阻塞操作,您也看不到它。这也可以称为“了解您的阻止代码”。当一侧发送一些东西时,您不知道它是否会到达另一侧,因此请跟踪发送的内容和接收的内容。
注意socket错误
Winsock 函数返回大量信息。了解您的WSAGetLastError()
功能。我不会在下面的示例中保留它,但请注意它们往往会返回大量信息。每次收到SOCKET_ERROR
或INVALID_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);
有趣的部分来了。 bind
和 listen
不会阻止,但 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
函数。
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的主要内容,如果未能解决你的问题,请参考以下文章