如何在 WINDOWS 中使用 C/C++ 在 UDP 套接字中设置超时?
How to set timeout in UDP socket with C/C++ in WINDOWS?
我一直在尝试在我的 DatagramSocket
实现 class 中为 WINDOWS 在 C/C++ 中设置 timeout
但我做不到,我尝试了 setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char *) &timeout, sizeof(struct timeval));
像 linux 并使用 select
功能但没有发生延迟,我做错了什么?
这是接口文件:
#ifndef __DatagramSocket_H__
#define __DatagramSocket_H__
#ifdef linux
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#else
// #include <ws2tcpip.h>
#include <winsock2.h>
// #pragma comment(lib,"ws2_32.lib") // Winsock Library, it only works in the microsoft compiler, MinGW ignores it
#endif
#include <iostream>
#include <strings.h>
#include <unistd.h>
#include <sys/time.h>
#include <errno.h>
#include "DatagramPacket.h"
class DatagramSocket {
public:
DatagramSocket(uint16_t iport, const std::string & addr);
~DatagramSocket();
void unbind();
int send(DatagramPacket &);
int receive(DatagramPacket &);
void setTimeout(long, long);
int receiveTimeout(DatagramPacket &, time_t, time_t);
private:
sockaddr_in localAddress, remoteAddress;
struct timeval timeout;
bool timeout_set;
#ifdef linux
int s;
#else
SOCKET s;
struct fd_set fds;
#endif
};
#endif
DatagramPacket 定义为:
class DatagramPacket {
public:
// data, len, ip, port
DatagramPacket(char* , size_t, const string &, uint16_t );
DatagramPacket(char* , size_t);
DatagramPacket();
~DatagramPacket();
string getAddress();
char *getData();
size_t getLength();
uint16_t getPort();
void setAddress(const string &);
void setData(char* , size_t);
void setLength(size_t);
void setPort(uint16_t);
private:
char *data;
uint16_t port;
string ip;
size_t length;
};
成员函数的定义来自DatagramSocket
class :
DatagramSocket::DatagramSocket(uint16_t iport, const std::string &addr): timeout_set(false) {
#ifdef _WIN32 // detect windows of 32 and 64 bits
WSAData wsaData;
WORD word = MAKEWORD(2, 2);
if (WSAStartup(word, &wsaData) != 0) {
std::cerr << "Server: WSAStartup failed with error: " << WSAGetLastError() << std::endl;
exit(1);
}
#endif
s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
// bzero((char *)&localAddress, sizeof(localAddress));
memset((char *) &localAddress, 0, sizeof(localAddress));
localAddress.sin_family = AF_INET;
localAddress.sin_addr.s_addr = inet_addr(addr.c_str());
localAddress.sin_port = htons(iport);
bind(s, (struct sockaddr *) &localAddress, sizeof(localAddress));
}
void DatagramSocket::setTimeout(long secs, long u_secs) {
timeout = {
.tv_sec = secs,
.tv_usec = u_secs };
timeout_set = true;
#ifdef __linux__
setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char *) &timeout, sizeof(struct timeval));
#else
FD_ZERO(&fds);
FD_SET(s, &fds);
#endif
}
int DatagramSocket::receiveTimeout(DatagramPacket &p, time_t secs, time_t u_secs) {
int len = sizeof(remoteAddress);
if (!timeout_set) setTimeout(secs, u_secs);
#ifdef _WIN32
int sret = select(0, &fds, NULL, NULL, &timeout);
if (sret == 0) {
cout << "Timeout! " << sret << endl;
return NULL;
}
#endif
int n = recvfrom(s, p.getData(), p.getLength(), 0, (struct sockaddr *) &remoteAddress, &len);
if (n < 0) { // deal with errors
#ifdef __linux__
if (errno == EWOULDBLOCK)
fprintf(stderr, "Timeout \n");
else
fprintf(stderr, "Error in recvfrom. \n");
#endif
return n;
}
p.setPort(remoteAddress.sin_port);
p.setAddress(string(inet_ntoa(remoteAddress.sin_addr)));
p.setLength(n);
return n;
}
我包含了 ws2tcpip.h 和 winsock2.h 库并使用 -lwsock32 标志编译。我在 Windows 10 中使用 MinGW。我已经可以在 linux 中完成此任务,但我不能在 Windows 中完成,我一直在互联网上寻找答案但没有代码为我工作。正如我所说,设置超时在 linux.
中起作用
提前致谢。
在 Windows 上,SO_RCVTIMEO
需要 DWORD
指定毫秒,而不是像其他平台上那样的 timeval
结构:
void DatagramSocket::setTimeout(long secs, long u_secs) {
timeout = {
.tv_sec = secs,
.tv_usec = u_secs };
timeout_set = true;
#ifdef _WIN32
DWORD dw = (secs * 1000) + ((u_secs + 999) / 1000);
setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char *) &dw, sizeof(dw));
#else
setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char *) &timeout, sizeof(struct timeval));
#endif
}
另请注意,SO_RCVTIMEO
仅适用于阻塞读取,不适用于非阻塞读取。您显示的代码没有将套接字置于非阻塞模式,因此如果您使用 SO_RCVTIMEO
:
则使用 select()
真的没有意义
int DatagramSocket::receiveTimeout(DatagramPacket &p, time_t secs, time_t u_secs) {
setTimeout(secs, u_secs);
int len = sizeof(remoteAddress);
int n = recvfrom(s, p.getData(), p.getLength(), 0, (struct sockaddr *) &remoteAddress, &len);
if (n < 0) { // deal with errors
#ifdef _WIN32
if (WSAGetLastError() == WSAETIMEDOUT)
#else
if (errno == EAGAIN || errno == EWOULDBLOCK)
#endif
std::cout << "Timeout!" << std::endl;
else
std::cerr << "Error in recvfrom." << std::endl;
return -1;
}
p.setPort(remoteAddress.sin_port);
p.setAddress(string(inet_ntoa(remoteAddress.sin_addr)));
p.setLength(n);
return n;
}
关于 select()
本身,它会修改输出中传递的 fd_set
(s),因此每次调用 select()
时都需要重置 fds
变量。因此,如果您打算使用 select()
,那么您应该将 select()
变量移动到本地 receiveTimeout()
,并且不要使用 SO_RCVTIMEO
:
int DatagramSocket::receiveTimeout(DatagramPacket &p, time_t secs, time_t u_secs) {
timeval timeout = {
.tv_sec = secs,
.tv_usec = u_secs };
fd_set fds;
FD_ZERO(&fds);
FD_SET(s, &fds);
int nfds;
#ifdef _WIN32
nfds = 0;
#else
nfds = s + 1;
#endif
int sret = select(nfds, &fds, NULL, NULL, &timeout);
if (sret <= 0) {
if (sret == 0) {
std::cout << "Timeout!" << std::endl;
else
std::cerr << "Error in select." << std::endl;
return -1;
}
int len = sizeof(remoteAddress);
int n = recvfrom(s, p.getData(), p.getLength(), 0, (struct sockaddr *) &remoteAddress, &len);
if (n < 0) { // deal with errors
std::cerr << "Error in recvfrom." << std::endl;
return -1;
}
p.setPort(remoteAddress.sin_port);
p.setAddress(string(inet_ntoa(remoteAddress.sin_addr)));
p.setLength(n);
return n;
}
我一直在尝试在我的 DatagramSocket
实现 class 中为 WINDOWS 在 C/C++ 中设置 timeout
但我做不到,我尝试了 setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char *) &timeout, sizeof(struct timeval));
像 linux 并使用 select
功能但没有发生延迟,我做错了什么?
这是接口文件:
#ifndef __DatagramSocket_H__
#define __DatagramSocket_H__
#ifdef linux
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#else
// #include <ws2tcpip.h>
#include <winsock2.h>
// #pragma comment(lib,"ws2_32.lib") // Winsock Library, it only works in the microsoft compiler, MinGW ignores it
#endif
#include <iostream>
#include <strings.h>
#include <unistd.h>
#include <sys/time.h>
#include <errno.h>
#include "DatagramPacket.h"
class DatagramSocket {
public:
DatagramSocket(uint16_t iport, const std::string & addr);
~DatagramSocket();
void unbind();
int send(DatagramPacket &);
int receive(DatagramPacket &);
void setTimeout(long, long);
int receiveTimeout(DatagramPacket &, time_t, time_t);
private:
sockaddr_in localAddress, remoteAddress;
struct timeval timeout;
bool timeout_set;
#ifdef linux
int s;
#else
SOCKET s;
struct fd_set fds;
#endif
};
#endif
DatagramPacket 定义为:
class DatagramPacket {
public:
// data, len, ip, port
DatagramPacket(char* , size_t, const string &, uint16_t );
DatagramPacket(char* , size_t);
DatagramPacket();
~DatagramPacket();
string getAddress();
char *getData();
size_t getLength();
uint16_t getPort();
void setAddress(const string &);
void setData(char* , size_t);
void setLength(size_t);
void setPort(uint16_t);
private:
char *data;
uint16_t port;
string ip;
size_t length;
};
成员函数的定义来自DatagramSocket
class :
DatagramSocket::DatagramSocket(uint16_t iport, const std::string &addr): timeout_set(false) {
#ifdef _WIN32 // detect windows of 32 and 64 bits
WSAData wsaData;
WORD word = MAKEWORD(2, 2);
if (WSAStartup(word, &wsaData) != 0) {
std::cerr << "Server: WSAStartup failed with error: " << WSAGetLastError() << std::endl;
exit(1);
}
#endif
s = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
// bzero((char *)&localAddress, sizeof(localAddress));
memset((char *) &localAddress, 0, sizeof(localAddress));
localAddress.sin_family = AF_INET;
localAddress.sin_addr.s_addr = inet_addr(addr.c_str());
localAddress.sin_port = htons(iport);
bind(s, (struct sockaddr *) &localAddress, sizeof(localAddress));
}
void DatagramSocket::setTimeout(long secs, long u_secs) {
timeout = {
.tv_sec = secs,
.tv_usec = u_secs };
timeout_set = true;
#ifdef __linux__
setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char *) &timeout, sizeof(struct timeval));
#else
FD_ZERO(&fds);
FD_SET(s, &fds);
#endif
}
int DatagramSocket::receiveTimeout(DatagramPacket &p, time_t secs, time_t u_secs) {
int len = sizeof(remoteAddress);
if (!timeout_set) setTimeout(secs, u_secs);
#ifdef _WIN32
int sret = select(0, &fds, NULL, NULL, &timeout);
if (sret == 0) {
cout << "Timeout! " << sret << endl;
return NULL;
}
#endif
int n = recvfrom(s, p.getData(), p.getLength(), 0, (struct sockaddr *) &remoteAddress, &len);
if (n < 0) { // deal with errors
#ifdef __linux__
if (errno == EWOULDBLOCK)
fprintf(stderr, "Timeout \n");
else
fprintf(stderr, "Error in recvfrom. \n");
#endif
return n;
}
p.setPort(remoteAddress.sin_port);
p.setAddress(string(inet_ntoa(remoteAddress.sin_addr)));
p.setLength(n);
return n;
}
我包含了 ws2tcpip.h 和 winsock2.h 库并使用 -lwsock32 标志编译。我在 Windows 10 中使用 MinGW。我已经可以在 linux 中完成此任务,但我不能在 Windows 中完成,我一直在互联网上寻找答案但没有代码为我工作。正如我所说,设置超时在 linux.
中起作用提前致谢。
在 Windows 上,SO_RCVTIMEO
需要 DWORD
指定毫秒,而不是像其他平台上那样的 timeval
结构:
void DatagramSocket::setTimeout(long secs, long u_secs) {
timeout = {
.tv_sec = secs,
.tv_usec = u_secs };
timeout_set = true;
#ifdef _WIN32
DWORD dw = (secs * 1000) + ((u_secs + 999) / 1000);
setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char *) &dw, sizeof(dw));
#else
setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (char *) &timeout, sizeof(struct timeval));
#endif
}
另请注意,SO_RCVTIMEO
仅适用于阻塞读取,不适用于非阻塞读取。您显示的代码没有将套接字置于非阻塞模式,因此如果您使用 SO_RCVTIMEO
:
select()
真的没有意义
int DatagramSocket::receiveTimeout(DatagramPacket &p, time_t secs, time_t u_secs) {
setTimeout(secs, u_secs);
int len = sizeof(remoteAddress);
int n = recvfrom(s, p.getData(), p.getLength(), 0, (struct sockaddr *) &remoteAddress, &len);
if (n < 0) { // deal with errors
#ifdef _WIN32
if (WSAGetLastError() == WSAETIMEDOUT)
#else
if (errno == EAGAIN || errno == EWOULDBLOCK)
#endif
std::cout << "Timeout!" << std::endl;
else
std::cerr << "Error in recvfrom." << std::endl;
return -1;
}
p.setPort(remoteAddress.sin_port);
p.setAddress(string(inet_ntoa(remoteAddress.sin_addr)));
p.setLength(n);
return n;
}
关于 select()
本身,它会修改输出中传递的 fd_set
(s),因此每次调用 select()
时都需要重置 fds
变量。因此,如果您打算使用 select()
,那么您应该将 select()
变量移动到本地 receiveTimeout()
,并且不要使用 SO_RCVTIMEO
:
int DatagramSocket::receiveTimeout(DatagramPacket &p, time_t secs, time_t u_secs) {
timeval timeout = {
.tv_sec = secs,
.tv_usec = u_secs };
fd_set fds;
FD_ZERO(&fds);
FD_SET(s, &fds);
int nfds;
#ifdef _WIN32
nfds = 0;
#else
nfds = s + 1;
#endif
int sret = select(nfds, &fds, NULL, NULL, &timeout);
if (sret <= 0) {
if (sret == 0) {
std::cout << "Timeout!" << std::endl;
else
std::cerr << "Error in select." << std::endl;
return -1;
}
int len = sizeof(remoteAddress);
int n = recvfrom(s, p.getData(), p.getLength(), 0, (struct sockaddr *) &remoteAddress, &len);
if (n < 0) { // deal with errors
std::cerr << "Error in recvfrom." << std::endl;
return -1;
}
p.setPort(remoteAddress.sin_port);
p.setAddress(string(inet_ntoa(remoteAddress.sin_addr)));
p.setLength(n);
return n;
}