场景
有没有人可以提供使用Winsock的C++对等网络(P2P)的好例子?这是我为客户所必须使用的技术要求(天知道为什么)。我需要确定这是否可行。
任何帮助都将不胜感激。
编辑
我想避免使用库,以便我可以理解底层源代码并进一步提高我的知识水平。
有没有人可以提供使用Winsock的C++对等网络(P2P)的好例子?这是我为客户所必须使用的技术要求(天知道为什么)。我需要确定这是否可行。
任何帮助都将不胜感激。
我想避免使用库,以便我可以理解底层源代码并进一步提高我的知识水平。
由于我不知道您正在寻找哪些信息,所以我将尝试描述如何设置套接字程序以及我遇到的问题。
首先,*阅读MSDN上的Winsock教程。 这是一个基本的程序,用于连接、发送消息和断开连接。它非常适合了解套接字编程。
有了这个,让我们开始:
考虑:
阻塞或非阻塞
首先,您需要决定您想要阻塞或非阻塞程序。 如果您有一个GUI,您需要使用非阻塞或线程以避免冻结程序。 我的做法是使用阻塞调用,但总是在调用阻塞函数之前调用select
(稍后会更多地介绍select)。 这样,我避免了线程、互斥锁等等,但仍然使用基本的accept
、send
和receive
调用。
不能依赖数据包以您发送它们的方式到达!
您也无法控制这一点。这是我遇到的最大问题,基本上是因为网络卡可以决定发送哪些信息以及何时发送它们。我解决的方法是创建一个networkPackageStruct
,其中包含一个size
和data
,其中size
是该数据包中所有数据的总量。请注意,您发送的消息可能会分成2个或更多数据包,并且还可以与您发送的另一条消息合并。
考虑以下情况:
您发送两条消息
"Hello"
"World!"
当您使用send
函数发送这两个消息时,您的recv
函数可能无法像这样收到它们。 它可能看起来像这样: "Hel"
"loWorld!"
或许
"HelloWorld!"
无论底层网络如何。
记录(几乎)所有内容!调试网络程序很困难,因为您无法完全控制它(因为它在两台计算机上)。如果遇到阻止操作,您也看不到它。这也可以称为“了解您的阻止代码”。当一侧发送某些内容时,您不知道它是否会到达另一侧,因此请跟踪已发送和已接收的内容。
注意套接字错误。Winsock函数返回大量信息。了解您的WSAGetLastError()
函数。我不会在下面的示例中保留它,但请注意它们 tend 返还很多信息。每次收到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
函数。
shutdown
将使您的对等方select
触发。 recv
不会接收任何数据,而是返回0。我没有注意到recv
返回0的其他情况,因此可以(在某种程度上)安全地说,这可以视为关闭代码。调用shutdown
是最好的选择。shutdown
只是一种冷酷无情的方式,但当然也是可行的。即使使用shutdown
,您仍然需要处理错误,因为可能不是您的程序关闭连接。一个好的错误代码是10054,它是WSAECONNRESET:对等方重置连接
。
select()
的通用技术,因为它可能会导致竞争条件:而且仅使用select()
进行可写性保证套接字发送缓冲区中有一些空间,而不是有足够的空间来发送所有要发送的数据,因为它不知道这些数据的情况,所以无法保证后续的send()
不会被阻塞。没有必要调用shutdown()
。关闭套接字具有完全相同的效果。当对等方关闭或关闭连接时,recv()
返回零。 - user207421