Dav*_*vid 2 networking udp network-programming c++builder winsock
我正在设置一个UDP套接字,并试图绑定应该是一个有效的网络广播地址(192.168.202.255:23456),但bind失败,错误10049 ,WSAEADDRNOTAVAIL . 如果我使用localhost广播地址127.0.0.255,它会成功.
WSAEADDRNOTAVAIL该文档说"请求的地址在其上下文中无效.这通常是由于尝试绑定到对本地计算机无效的地址.这也可能是由connect,sendto,WSAConnect,WSAJoinLeaf或者WSASendTo远程地址或端口对远程计算机无效(例如,地址或端口0)." 但我认为这个地址192.168.202.255应该是一个有效的广播地址,因为运行时有以下条目ipconfig:

可能是什么问题?
我是Winsock编程的新手,我可能犯了一个基本错误,但我找不到它.我到目前为止的代码是:
m_ulAddress = ParseIPAddress(strAddress);
// Winsock 2.2 is supported in XP
const WORD wVersionRequested = MAKEWORD(2, 2);
WSADATA oWSAData;
const int iError = WSAStartup(wVersionRequested, &oWSAData);
if (iError != 0) {
PrintLine(L"Error starting the network connection: WSAStartup error " + IntToStr(iError));
} else if (LOBYTE(oWSAData.wVersion) != 2 || HIBYTE(oWSAData.wVersion) != 2) {
PrintLine(L"Error finding version 2.2 of Winsock; got version " + IntToStr(LOBYTE(oWSAData.wVersion)) + L"." + IntToStr(HIBYTE(oWSAData.wVersion)));
} else {
m_oSocket = socket(AF_INET, SOCK_DGRAM /*UDP*/, IPPROTO_UDP);
if (m_oSocket == INVALID_SOCKET) {
PrintLine(L"Error creating the network socket");
} else {
// Socket needs to be able to send broadcast messages
int iBroadcast = true; // docs say int sized, but boolean values
if (setsockopt(m_oSocket, SOL_SOCKET, SO_BROADCAST, (const char*)&iBroadcast, sizeof(iBroadcast)) != 0) {
PrintLine(L"Error setting socket to allow broadcast addresses; error " + IntToStr(WSAGetLastError()));
} else {
m_oServer.sin_family = AF_INET;
m_oServer.sin_port = m_iPort;
m_oServer.sin_addr.S_un.S_addr = m_ulAddress;
// !!! This is the failing call
if (bind(m_oSocket, (sockaddr*)&m_oServer, sizeof(m_oServer)) == -1) {
PrintLine(L"Error binding address " + String(strAddress.c_str()) + L":" + IntToStr(m_iPort) + L" to socket; error " + IntToStr(WSAGetLastError()));
} else {
m_bInitialisedOk = true;
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
ParseIPAddress是一个包装inet_addr; 检查m_oServer.sin_addr.S_un.S_addr它的价值似乎是正确的. m_oSocket是一个SOCKET.我添加了调用,setsockopt因为默认情况下你不能通过TCP播放任何内容(参见备注中的第二段sendto); 这个电话没有任何区别. PrintLine是控制台输出的包装器.String / c_str()由于我使用的是C++ Builder及其VCL库,所以奇怪的强制转换是从C++ wstrings转换为VCL Unicode字符串.IP地址是一个窄(字符)字符串.
该sendto文档指出"如果打开套接字,则进行setsockopt调用,然后进行sendto调用,Windows套接字执行隐式绑定函数调用." 这意味着bind根本不需要.如果我省略了这个电话,那么就这样打电话sendto:
const int iLengthBytes = strMessage.length() * sizeof(char); // Narrow string
const int iSentBytes = sendto(m_oSocket, strMessage.c_str(), iLengthBytes, 0, (sockaddr*)&m_oServer, sizeof(m_oServer));
if (iSentBytes != iLengthBytes) {
PrintLine(L"Error sending network message; error: " + IntToStr(WSAGetLastError()));
Run Code Online (Sandbox Code Playgroud)
失败,错误10047,WSAEAFNOSUPPORT"协议族不支持地址族".
netsh winsock show catalog (在socket备注底部提到)的输出很长,但确实包括提及UDP和IPv4的几个条目.
可能的复杂性是它在VMWare Fusion主机中运行; Fusion确实为网络设置了奇怪的设置.我还配置了一个运行回我办公室的Cisco VPN.连接和断开连接没有区别.
对我来说似乎SOCKET有点狡猾的一件事就是强调m_oSocket sockaddr,但是当我阅读这些例子时,这似乎是Winsock编程的常规做法.可能需要读取它,因为基础解释取决于协议族.它似乎是一个潜在的错误来源,但我不确定如何避免它.
有任何想法吗?我很难过:)
这里很混乱。我将逐点解决它以供您启发,但如果您只想要工作代码,请跳到最后。
// Winsock 2.2 is supported in XP
实际上,Winsock 2.2 可以追溯到 NT 4 SP4,它的历史可以追溯到 1998 年。因此,我不会费心检查oWSAData.wVersion错误情况。这种情况基本上不可能再发生了。
如果您的目标是广泛的可移植性,我会以 Winsock 1.1 为目标,这是您展示的代码所需的全部内容,并且可以让代码在支持 Winsock 的任何设备上构建和运行,甚至回到 Windows 3.x。
m_oSocket = socket(AF_INET, SOCK_DGRAM /*UDP*/, IPPROTO_UDP);
作风不好。你应该PF_INET在这里使用而不是AF_INET. 它们具有相同的值,但您不是AF在此处指定地址族 ( ),而是指定了协议族 ( PF)。此外,第三个参数可以安全地为零,因为它由前两个参数隐含。同样,这只是一个样式修复,而不是一个功能修复。
int iBroadcast = true; // docs say int sized, but boolean values
是的。不要猜测文档并bool在此处使用。请记住,Winsock 基于 BSD 套接字,这可以追溯到 C++ 出现之前的日子。
m_oServer.sin_addr.S_un.S_addr = m_ulAddress;
你真的不应该以sockaddr_in这种方式深入研究结构的内部。sockets API 有一个快捷方式,它更短并且隐藏了一些内部实现细节。这是:
m_oServer.sin_addr.s_addr = m_ulAddress;
继续...
if (bind(m_oSocket, ...
尽管 Remy 认为bind()调用不正确是正确的,但您实际上根本不需要它。您可以依靠系统的路由层将数据包从正确的接口发送出去。您不需要通过电话“帮助”它bind()。
默认情况下,您不能通过 TCP 以外的任何方式进行广播(请参阅 sendto 备注中的第二段);
您误解了 MSDN 告诉您的内容。当您看到术语“TCP/IP”时,它通常(但不总是!)包括 UDP。他们在这里以一般意义上使用它。
您指向的 MSDN 部分讨论了 TCP/IP,因为 Winsock 是在 TCP/IP 尚未赢得网络协议战争的世界中创建的。他们试图将讨论限制在 TCP/IP(实际上是 UDP)上,因此您不会认为他们所说的内容适用于早期 Winsock 堆栈支持的其他网络传输:NetBIOS、IPX、DECNet ...
实际上,您只能使用 UDP 套接字进行广播(或多播)。TCP 只是点对点。
对我来说似乎很狡猾的一件事是将 SOCKET m_oSocket 硬铸造到 sockaddr,
这也是套接字中多网络传输支持的一部分。除此之外sockaddr_in,还有sockaddr_ipxIPX、sockaddr_dnDECnet……Winsock 是C API,而不是C++ API,所以我们不能子类化sockaddr并传递对基类的引用,或为每个变体创建函数重载。这种铸造结构的技巧是一种典型的 C 方法来获得一种多态性。
这里有一个工作示例,它与建立MinGW的,g++ foo.cpp -o foo.exe -lwsock32:
#include <winsock.h>
#include <iostream>
#include <string.h>
using namespace std;
int main(int argc, char* argv[])
{
WSADATA wsa;
if (WSAStartup(MAKEWORD(1, 1), &wsa)) {
cerr << "Failed to init Winsock!" << endl;
return 1;
}
// Get datagram socket to send message on
SOCKET sd = socket(PF_INET, SOCK_DGRAM, 0);
if (sd < 0) {
cerr << "socket() failed: " << WSAGetLastError() << endl;
return 1;
}
// Enable broadcasts on the socket
int bAllow = 1;
if (setsockopt(sd, SOL_SOCKET, SO_BROADCAST, (char*)&bAllow,
sizeof(bAllow)) < 0) {
cerr << "setsockopt() failed: " << WSAGetLastError() << endl;
closesocket(sd);
return 1;
}
// Broadcast the request
string msg = "Hello, world!";
const int kMsgLen = msg.length();
struct sockaddr_in sin;
memset(&sin, 0, sizeof(sin));
const uint16_t kPort = 54321;
sin.sin_port = htons(kPort);
sin.sin_family = AF_INET;
if (argc == 1) {
sin.sin_addr.s_addr = INADDR_BROADCAST;
}
else if ((sin.sin_addr.s_addr = inet_addr(argv[1])) == INADDR_NONE) {
cerr << "Couldn't parse IP '" << argv[1] << "'!" << endl;
}
int nBytes = sendto(sd, msg.c_str(), kMsgLen, 0,
(sockaddr*)&sin, sizeof(struct sockaddr_in));
closesocket(sd);
// How well did that work out, then?
if (nBytes < 0) {
cerr << "sendto() IP " << inet_ntoa(sin.sin_addr) <<
" failed" << WSAGetLastError() << endl;
return 1;
}
else if (nBytes < kMsgLen) {
cerr << "WARNING: Short send, " << nBytes << " bytes! "
"(Expected " << kMsgLen << ')' << endl;
return 1;
}
else {
cerr << "Sent " << kMsgLen << "-byte msg to " <<
inet_ntoa(sin.sin_addr) << ':' << kPort << '.' << endl;
}
return 0;
}
Run Code Online (Sandbox Code Playgroud)
INADDR_BROADCAST默认情况下,它会发送到 255.255.255.255 ( ),但如果您将定向广播 IP(例如您的 192.168.202.255 值)作为第一个参数传递,它将改为使用该 IP。
你不应该bind()使用广播IP地址.您需要改为bind()使用单个网络适配器IP.如果要发送广播消息,请bind()转到要发送广播的适配器,然后发送sendto()广播IP.如果要接收广播消息,请bind()转到IP与发送的广播IP匹配的特定适配器.
| 归档时间: |
|
| 查看次数: |
10577 次 |
| 最近记录: |