C++ Winsock P2P
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2843277/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
C++ Winsock P2P
提问by Goober
Scenario
设想
Does anyone have any good examples of peer-to-peer (p2p) networking in C++ using Winsock? It's a requirement I have for a client who specifically needs to use this technology (god knows why). I need to determine whether this is feasible.
有没有人有使用 Winsock 在 C++ 中进行点对点 (p2p) 网络的好例子?这是我对特别需要使用这项技术的客户的要求(天知道为什么)。我需要确定这是否可行。
Any help would be greatly appreciated.
任何帮助将不胜感激。
EDIT
编辑
And I would like to avoid using libraries so that I can understand the underlying source code and further my knoweldge.
我想避免使用库,以便我可以理解底层源代码并进一步了解我的知识。
回答by Default
Since I don't know what information you are looking for, I'll try to describe how to set up a socket program and what pitfalls I've run into.
由于我不知道您要查找什么信息,我将尝试描述如何设置套接字程序以及我遇到的陷阱。
To start with, read the Winsock tutorialfrom MSDN. This is a basic program to connect, send a message and disconnect. It's great for getting a feel for socket programming.
首先,阅读MSDN 上的Winsock 教程。这是连接、发送消息和断开连接的基本程序。非常适合了解套接字编程。
With that, lets start:
有了这个,让我们开始:
Considerations:
注意事项:
blocking or non-blocking
阻塞或非阻塞
First of, you would need to determine if you want a blocking or non-blocking program. The big difference is that if you have a GUI you would need to use non-blocking or threading in order to not freeze the program. The way I did it was to use the blocking calls, but always calling select
before calling the blocking functions (more on select later). This way I avoid threading and mutex's and whatnot but still use the basic accept
, send
and receive
calls.
首先,您需要确定您想要阻塞程序还是非阻塞程序。最大的区别在于,如果您有一个 GUI,您将需要使用非阻塞或线程,以免冻结程序。我这样做的方法是使用阻塞调用,但总是select
在调用阻塞函数之前调用(稍后将详细介绍 select)。通过这种方式,我避免了线程和互斥锁等等,但仍然使用基本的accept
,send
和receive
调用。
You cannot rely on that your packages will arrive the way you send them!
您不能指望您的包裹会以您发送的方式到达!
You have no impact on this either. This was the biggest issue I ran into, basically because the network card can decide what information to send and when to send it. The way I solved it was to make a networkPackageStruct
, containing a size
and data
, where size is the total amound of data in that package. Note that a message that you send can be split into 2- or more messages and can also be merged with another message you send.
你对此也没有影响。这是我遇到的最大问题,主要是因为网卡可以决定发送什么信息以及何时发送。我解决它的方法是制作一个networkPackageStruct
,包含一个size
and data
,其中 size 是该包中的数据总量。请注意,您发送的一条消息可以拆分为 2 条或更多条消息,也可以与您发送的另一条消息合并。
Consider the following: You send two messages
考虑以下事项: 您发送了两条消息
"Hello"
"World!"
When you send these two messages with the send
function your recv
function might not get them like this. It could look like this:
当您使用send
函数发送这两条消息时,您的recv
函数可能不会像这样获取它们。它可能看起来像这样:
"Hel"
"loWorld!"
or perhaps
也许
"HelloWorld!"
whatever the underlying network feels like..
无论底层网络感觉如何..
Log (almost) everything!
记录(几乎)一切!
Debugging a network program is hard because you don't have full control over it (since it's on two computers). If you run into a blocking operation you can't see it either. This could as well be called "Know your blocking code".. When one side sends something you don't know if it will arrive on the other side, so keep track of what is sent and what is received.
调试网络程序很困难,因为您无法完全控制它(因为它在两台计算机上)。如果您遇到阻塞操作,您也看不到它。这也可以称为“了解您的阻塞代码”。当一侧发送某些内容时,您不知道它是否会到达另一侧,因此请跟踪发送的内容和接收的内容。
Pay attention to socket errors
注意socket错误
winsock functions return alot of information. Know your WSAGetLastError()
function. I'll won't keep it in the examples below, but note that they tend to return alot of information. Everytime you get a SOCKET_ERROR
or INVALID_SOCKET
check the Winsock Error Messagesto look it up
winsock 函数返回大量信息。了解你的WSAGetLastError()
功能。我不会在下面的示例中保留它,但请注意,它们往往会返回大量信息。每次您收到SOCKET_ERROR
或INVALID_SOCKET
检查Winsock 错误消息以进行查找时
Setting up the connection:
设置连接:
Since you don't want a server, all clients would need a listening socket to accept new connections. The easiest is:
由于您不需要服务器,因此所有客户端都需要一个侦听套接字来接受新连接。最简单的是:
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;
The INADDR_ANY is great - it actually makes your socket listen on all your networks instead of just one ipaddress.
INADDR_ANY 很棒 - 它实际上使您的套接字侦听所有网络,而不仅仅是一个 IP 地址。
bind(s, (SOCKADDR*)&localAddress, sizeof(localAddress));
listen(s, SOMAXCONN);
here comes the interesting part. bind
and listen
won't block but accept
will. The trick is to use select
to check if there is an incoming connection. So the above code is just to set the socket up. in your program loop you check for new data in socket.
有趣的部分来了。bind
并且listen
不会阻止但accept
会。诀窍是用来select
检查是否有传入连接。所以上面的代码只是为了设置socket。在您的程序循环中,您检查套接字中的新数据。
Exchanging data
交换数据
The way I solved it is was to use select
alot. Basically you see if there are anything you need to respond to on any of your sockets. This is done with the FD_xxx
functions.
我解决它的方法是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
in the newSocket
you now have a new peer. So that was for receiving data. But note!send
is also blocking! One of the "head scratching errors" I got was that send
blocked me. This was however also solved with select
.
在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
}
Shutting down
关闭
Finally, there are two ways to shutdown. You either just disconnect by closing your program, or you call the shutdown
function.
最后,有两种关闭方式。您要么通过关闭程序来断开连接,要么调用该shutdown
函数。
- Calling shutdown will make your peer
select
trigger.recv
will however not receive any data, but will instead return 0. I have not noticed any other case whererecv
returns 0, so it is (somewhat) safe to say that this can be considered a shutdown-code. callingshutdown
is the nicest thing to do.. - Shutting down the connection without calling shutdown just is cold-hearted, but of course works. You still need to handle the error even if you use
shutdown
, since it might not be your program that closes the connection. A good error code to remember is 10054 which is WSAECONNRESET: Connection reset by peer..
- 调用 shutdown 将使您的对等方
select
触发。recv
然而,不会接收任何数据,而是返回 0。我没有注意到任何其他recv
返回 0 的情况,因此(有点)可以肯定地说这可以被视为关闭代码。打电话shutdown
是最好的事情.. - 在不调用 shutdown 的情况下关闭连接是冷酷的,但当然有效。即使您使用
shutdown
,您仍然需要处理错误,因为它可能不是关闭连接的程序。要记住的一个很好的错误代码是 10054,它是WSAECONNRESET: Connection reset by peer。.
回答by Yi Zhao
If you just want to implement a P2P application on Microsoft Windows, you can try with Windows Peer-to-Peer Networking
如果您只想在 Microsoft Windows 上实现 P2P 应用程序,可以尝试使用Windows Peer-to-Peer Networking
If you want to implement a new P2P protocol of your own, you can study the eMule protocol, and eMule source code. You could do further if you look into Shareaza source code, it do eMule/Guntella/Gnutella/BitTorrent.
如果你想自己实现一个新的P2P协议,可以研究一下电骡协议,还有电骡源码。如果你查看Shareaza 源代码,你可以做进一步的工作,它可以做 eMule/Guntella/Gnutella/BitTorrent。