有关UDP非阻塞recvfrom超时设置的问题
本帖最后由 lingducool 于 2013-05-21 21:04:20 编辑 我现在想编这么一个程序,向目标端口发送UDP的一个包后,用recvfrom等待接受回应,等待5秒后未接到回应就继续向下执行。
我的思路是设置为非阻塞式套接字,然后设置超时5秒,但是这样我没有成功,代码如下:
#include <winsock2.h>
#include <iostream.h>
#include <stdio.h>
#pragma comment(lib,"ws2_32.lib")
void initClient();
int main()
{
initClient();
return 0;
}
void initClient()
{
WSADATA wsaData;
int error=WSAStartup(MAKEWORD(2,2),&wsaData);
if(error!=0)
{
cout<<"初始化DLL失败"<<endl;
return;
}
if(LOBYTE(wsaData.wVersion)!=2 || HIBYTE(wsaData.wVersion)!=2)
{
WSACleanup();
cout<<"版本出错"<<endl;
return;
}
SOCKET s=socket(AF_INET,SOCK_DGRAM,0);
SOCKADDR_IN sockSend;
sockSend.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
sockSend.sin_port=htons(4000);
sockSend.sin_family=AF_INET;
char buff[1024];
strcpy(buff,"hello,it's the first!");
int iMode = 1;//block
ioctlsocket(s, FIONBIO, (u_long FAR*) &iMode);
DWORD TimeOut=1000*5; //设置发送超时10秒
if(::setsockopt(s,SOL_SOCKET,SO_RCVTIMEO,(char *)&TimeOut,sizeof(TimeOut))==SOCKET_ERROR)
{
cout<<"设置失败"<<endl;
}
int lenword;
lenword=sendto(s,buff,strlen(buff)+1,0,(sockaddr *)&sockSend,sizeof(sockaddr));
cout<<lenword<<","<<sockSend.sin_port<<":"<<sockSend.sin_addr.S_un.S_addr<<endl;
char recBuff[1024];
memset(recBuff,0,1024);
SOCKADDR_IN sockRec;
int len=sizeof(SOCKADDR);
recvfrom(s,recBuff,sizeof(recBuff),0,(sockaddr *)&sockRec,&len);
printf("the receive is\n");
closesocket(s);
WSACleanup();
}
大家注意这句:int iMode = 1;不管我这一句是0还是1,都不会阻塞,程序执行后一下子就出现了打印的最后一句,根本不会等待5秒。我的这是windows平台的程序 UDP socket 阻塞 超时 Windows
[解决办法]
有几处问题:
1、“非阻塞式套接字”和“超时”是矛盾的。
非阻塞式套接字表示如果没有数据就直接返回,不会有等待超时的机制。
看你的需求,应该是阻塞式+超时。
2、windows 套接字默认是阻塞式的,也就是默认效果就是:
int iMode = 0;//0 == block, 1 == non-block
ioctlsocket(s, FIONBIO, (u_long FAR*) &iMode);
3、为什么你的socket没有阻塞,原因有点复杂。
一是因为udp是无连接的,socket没有指定本地地址的时候,IP层是没办法知道哪个数据该送达给它。
4、要想在recvfrom上实现阻塞,前提是socket已经具有一个本地地址。
一般有两个办法,一是做一次成功的send动作,自然本地地址就由主机自行分配了,二是手动给socket bind一个。
另外要注意,sendto的失败信息是通过ICMP异步返回的,所以sendto不会阻塞,但接下来的recvfrom就可以撞上这个icmp错误,然后就终止了…… 所以比较可靠的办法就是bind。
[解决办法]
想了一下,最后一点说的不全面。bind也不能防住sendto的错误。
这是使用bind的改法。sendto注释掉了,如果要启用它,得保证对面有人接收数据。不然会出错,导致socket error。
#include <winsock2.h>
#include <iostream>
#include <stdio.h>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
void initClient();
int main()
{
initClient();
return 0;
}
void initClient()
{
WSADATA wsaData;
int error=WSAStartup(MAKEWORD(2,2),&wsaData);
if(error!=0)
{
cout<<"初始化DLL失败"<<endl;
return;
}
if(LOBYTE(wsaData.wVersion)!=2
[解决办法]
HIBYTE(wsaData.wVersion)!=2)
{
WSACleanup();
cout<<"版本出错"<<endl;
return;
}
SOCKET s=socket(AF_INET,SOCK_DGRAM,0);
SOCKADDR_IN sockSend;
sockSend.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
sockSend.sin_port=htons(6788);
sockSend.sin_family=AF_INET;
char * buff = "hello,it's the first!";
int lenword;
SOCKADDR_IN sockme;
char recBuff[1024];
DWORD TimeOut=1000*5; //设置发送超时10秒
memset(recBuff,0,1024);
sockme.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
sockme.sin_port=htons(9686);
sockme.sin_family=AF_INET;
#if 1
lenword = bind(s, (sockaddr *)&sockme,sizeof(sockme));
if (lenword != 0) {
printf("bind failed with error %d\n", WSAGetLastError());
return;
}
#endif
if(::setsockopt(s,SOL_SOCKET,SO_RCVTIMEO,(char *)&TimeOut,sizeof(TimeOut))==SOCKET_ERROR)
{
cout<<"设置失败"<<endl;
}
//lenword=sendto(s,buff,strlen(buff),0,(sockaddr *)&sockSend,sizeof(sockaddr));
//cout<<lenword<<","<<sockSend.sin_port<<":"<<sockSend.sin_addr.S_un.S_addr<<endl;
lenword = recvfrom(s,recBuff,sizeof(recBuff),0, NULL, NULL);
printf("recvfrom data %d:%s\n", lenword, recBuff);
if (lenword < 0) {
printf("recvfrom failed with error %d\n", WSAGetLastError());
return;
}
closesocket(s);
WSACleanup();
}
如果能保证对面有人接收,那也可以用sendto让主机自动分析本地地址,不须bind。
#include <winsock2.h>
#include <iostream>
#include <stdio.h>
#pragma comment(lib,"ws2_32.lib")
using namespace std;
void initClient();
int main()
{
initClient();
return 0;
}
void initClient()
{
WSADATA wsaData;
int error=WSAStartup(MAKEWORD(2,2),&wsaData);
if(error!=0)
{
cout<<"初始化DLL失败"<<endl;
return;
}
if(LOBYTE(wsaData.wVersion)!=2
[解决办法]
HIBYTE(wsaData.wVersion)!=2)
{
WSACleanup();
cout<<"版本出错"<<endl;
return;
}
SOCKET s=socket(AF_INET,SOCK_DGRAM,0);
SOCKADDR_IN sockSend;
sockSend.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
sockSend.sin_port=htons(6788);
sockSend.sin_family=AF_INET;
char * buff = "hello,it's the first!";
int lenword;
SOCKADDR_IN sockme;
char recBuff[1024];
DWORD TimeOut=1000*5; //设置发送超时10秒
memset(recBuff,0,1024);
sockme.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
sockme.sin_port=htons(9686);
sockme.sin_family=AF_INET;
#if 0
lenword = bind(s, (sockaddr *)&sockme,sizeof(sockme));
if (lenword != 0) {
printf("bind failed with error %d\n", WSAGetLastError());
return;
}
#endif
if(::setsockopt(s,SOL_SOCKET,SO_RCVTIMEO,(char *)&TimeOut,sizeof(TimeOut))==SOCKET_ERROR)
{
cout<<"设置失败"<<endl;
}
lenword=sendto(s,buff,strlen(buff),0,(sockaddr *)&sockSend,sizeof(sockaddr));
cout<<lenword<<","<<sockSend.sin_port<<":"<<sockSend.sin_addr.S_un.S_addr<<endl;
lenword = recvfrom(s,recBuff,sizeof(recBuff),0, NULL, NULL);
printf("recvfrom data %d:%s\n", lenword, recBuff);
if (lenword < 0) {
printf("recvfrom failed with error %d\n", WSAGetLastError());
return;
}
closesocket(s);
WSACleanup();
}