2024/03/24 阴, 约了许久没见面的妹妹吃了一个饭,她去找他同学玩了,我到咖啡店找点下周要开发的nvidia jetson xavier的资料, 由于板子没在身边,具体型号也不记得,所以有时间记录一下去年遇到的一个boost asio 同步通讯中,客户端设置超时的坑, 顺便记录一下boost udp 客户端 通讯的example, 同步,异步应该是大同小异, 我使用的boost 版本是1.83
0. boost udp 同步超时陷阱
遇到这个问题时,我在开发一个gige 协议的客户端,用于windows和linux环境,使用gige相机。gige中通过gvcp和gvsp来对相机进行交互和取流,gvcp是udp协议,所以我用的是udp socket。开始时,我在windows上开发, 使用下面这段代码设置超时时间 ,它可以正常的工作 ,当超时发生时,返回超时的结果。
代码中先创建了endpoint和udp socket,然后把它们绑定在一起,然后设置socket的超时时间 为5000毫秒
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
void foo()
{
try {
boost::asio::io_service io_service;
boost::asio::ip::udp::socket socket(io_service);
socket.open(boost::asio::ip::udp::v4());
boost::asio::ip::udp::endpoint if_endpoint(boost::asio::ip::address::from_string("0.0.0.0"), 0);
socket.bind(if_endpoint);
boost::asio::ip::udp::endpoint endpoint(boost::asio::ip::address::from_string(devIp), 3956);
boost::array<unsigned char,16> hex_data = {/* ... */ };
socket.send_to(boost::asio::buffer(hex_data), endpoint);
// set timeout 5000ms
socket.set_option(boost::asio::detail::socket_option::integer<SOL_SOCKET, SO_RCVTIMEO>{ 5000 });
boost::array<char, 600> recv_buffer;
boost::asio::ip::udp::endpoint remote_endpoint;
boost::system::error_code error;
size_t bytes_received = socket.receive_from(boost::asio::buffer(recv_buffer), remote_endpoint, 0, error);
Q_UNUSED(bytes_received)
if (!error) {
// do stuff
}
} else {
std::cerr << "Error receiving message: " << error.message() << std::endl;
}
} catch (const boost::system::system_error& e) {
// timeout
if (e.code() == boost::asio::error::timed_out) {
std::cerr << "Timeout: No response received within 1 second." << std::endl;
} else {
std::cerr << "Error receiving message: " << e.what() << std::endl;
}
}
catch (const std::exception& e) {
std::cerr << "Exception: " << e.what() << std::endl;
}
return "";
}
|
但是当我把这段代码放到linux上运行时,当超时发生时,会导致receive_from
永远不会返回。
1. window设置超时有效的原因
window和 linux的receive_from在经过平台适配层后,分发到平台系统api,
windows的api在boost socket_ops.ipp中为sync_recvfrom1中,api为WSARecvFrom
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
signed_size_type recvfrom(socket_type s, buf* bufs, size_t count,
int flags, void* addr, std::size_t* addrlen, boost::system::error_code& ec)
{
#if defined(BOOST_ASIO_WINDOWS) || defined(__CYGWIN__)
// Receive some data.
DWORD recv_buf_count = static_cast<DWORD>(count);
DWORD bytes_transferred = 0;
DWORD recv_flags = flags;
int tmp_addrlen = (int)*addrlen;
int result = ::WSARecvFrom(s, bufs, recv_buf_count, &bytes_transferred, //调用 window api
&recv_flags, static_cast<socket_addr_type*>(addr), &tmp_addrlen, 0, 0);
get_last_error(ec, true);
*addrlen = (std::size_t)tmp_addrlen;
if (ec.value() == ERROR_NETNAME_DELETED)
ec = boost::asio::error::connection_reset;
else if (ec.value() == ERROR_PORT_UNREACHABLE)
ec = boost::asio::error::connection_refused;
else if (ec.value() == WSAEMSGSIZE || ec.value() == ERROR_MORE_DATA)
result = 0;
if (result != 0)
return socket_error_retval;
boost::asio::error::clear(ec);
return bytes_transferred;
#else // defined(BOOST_ASIO_WINDOWS) || defined(__CYGWIN__)
//...省略
#endif // defined(BOOST_ASIO_WINDOWS) || defined(__CYGWIN__)
}
|
可以通过设置SO_RCVTIMEO
来使 WSARecvFrom
,recvfrom
等阻塞式的读数据的函数超时返回
1
2
3
4
5
6
7
8
9
10
11
12
13
|
int main()
{
int iSock = socket(AF_INET, SOCK_DGRAM, 0);
char szBuf[1024] = {0};
struct timeval tv;
tv.tv_sec = 1;
tv.tv_usec = 0;
setsockopt(iSock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
int iRet = recvfrom(iSock, szBuf, sizeof(szBuf) - 1, 0, NULL, NULL);
printf("iRet is [%d]\n", iRet);
close(iSock);
}
|
因为我们通过boost的方式设置了SO_RCVTIMEO,
1
|
socket.set_option(boost::asio::detail::socket_option::integer<SOL_SOCKET, SO_RCVTIMEO>{ 5000 });
|
2. linux设置超时无效的原因
而linux调用系统api的位置在 boost socket_ops.ipp文件中的poll_read中,外部传入的超时时间为常量-1,即无限等待
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
int poll_read(socket_type s, state_type state,
int msec, boost::system::error_code& ec)
{
if (s == invalid_socket)
{
ec = boost::asio::error::bad_descriptor;
return socket_error_retval;
}
#if defined(BOOST_ASIO_WINDOWS) //widnows逻辑
//略
#else // linux逻辑
pollfd fds;
fds.fd = s;
fds.events = POLLIN;
fds.revents = 0;
int timeout = (state & user_set_non_blocking) ? 0 : msec;
int result = ::poll(&fds, 1, timeout); //timeout实参常数-1
get_last_error(ec, result < 0);
#endif
if (result == 0)
if (state & user_set_non_blocking)
ec = boost::asio::error::would_block;
return result;
}
|
3. 解法
一种即达到同步等等,windows和linux又不需要维护两个版本的方法,是使用异步接口写同步等待
同步等待不必非得用同步接口,asio 擅长的本身就是异步
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
|
void foo()
{
std::string result;
boost::asio::io_service io_service;
boost::asio::io_service::work work(io_service);
std::thread thread([&io_service](){ io_service.run(); });
boost::asio::ip::udp::socket socket(io_service);
socket.open(boost::asio::ip::udp::v4());
boost::asio::ip::udp::endpoint if_endpoint(boost::asio::ip::address::from_string("0.0.0.0"), 0);
socket.bind(if_endpoint);
boost::asio::ip::udp::endpoint endpoint(boost::asio::ip::address::from_string(devIp), 3956);
// send datas
boost::array<unsigned char,16> hex_data = { /* ... */ };
// send
std::future<size_t> bytes_send = socket.async_send_to(boost::asio::buffer(hex_data), endpoint,boost::asio::use_future);
boost::array<char, 600> recv_buffer;
boost::asio::ip::udp::endpoint remote_endpoint;
bytes_send.get();
std::future<size_t> bytes_received = socket.async_receive_from(boost::asio::buffer(recv_buffer), remote_endpoint,boost::asio::use_future);
std::future_status status = bytes_received.wait_for(std::chrono::milliseconds(5000));
if (std::future_status::timeout == status) {
socket.cancel();
// timeout
} else if (status == std::future_status::ready) {
//do stuff
}
else
{
//other errors
}
}
|
>> Home
Comments