同步UDP客户端
UDP是面向无连接的,使用起来比较简单,打开socke之后,指定目标端口,直接进行接收和发送:
void test_udp_echo_client()
{
try
{
io_service io;
udp::endpoint remote_ep(ip::address_v4::from_string("127.0.0.1"), 1024);
udp::socket socket(io);
socket.open(udp::v4());
char line[1024];
while (std::cin.getline(line, 1024))
{
socket.send_to(boost::asio::buffer(line, std::strlen(line)),remote_ep);
auto size = socket.receive_from(boost::asio::buffer(line),remote_ep);
std::cout.write(line,size);
}
socket.close();
}
catch (std::exception& e)
{
std::cerr << e.what() << std::endl;
}
}
socket本身提供了一些接口:
- socket.send_to 同步发送接口
- socket.receive_from 同步接收接口
Boost.Asio
也有一些接口用来进行发送和接收,可以参见后续的发送/接收函数组;
需要注意的是,boost.asio.buffer
是一种接口适配器,通过接口进行发送和接收,必须有对应的数据缓冲区提供数据或者存储空间。
同步UDP服务器
同步接收同步发送的UDP服务器也比较简单,创建一个绑定到本地端口的socket,然后就是接收及发送动作:
void test_udp_echo_server()
{
try
{
io_service io;
ip::udp::socket socket(io, udp::endpoint(udp::v4(), 1024));
for (;;)
{
std::array<char,1024> recv_buf;
ip::udp::endpoint remote_socket;
boost::system::error_code error;
//同步接收
auto size = socket.receive_from(boost::asio::buffer(recv_buf),remote_socket,0,error);
if (error && error!= boost::asio::error::message_size)
{
throw boost::system::system_error(error);
}
std::cout.write(recv_buf.data(),size);
//发送回去
socket.send_to(boost::asio::buffer(recv_buf,size),remote_socket);
}
}
catch (std::exception& e)
{
std::cerr<<e.what()<<std::endl;
}
}
同步操作是不需要运行IO服务的,以最常规的方式来进行发送和接收,注意接收时如果接收到全部消息,即EOF也是通过报错形式,错误码为error::message_size
。
异步UDP服务器的实现问题
实现异步的UDP服务器就略显复杂,需要保证IO服务运行,发起异步操作时要注意数据缓冲区生命周期:
- 启动IO服务
启动IO服务可以直接执行io_service.run
,由于IO服务的多线程安全特性,也可以启动线程来执行,譬如:
boost::asio::io_service io_;
std::thread task([&](){ io_.run();});
task.detach();
-
停止IO服务
停止IO服务可以直接执行io_service.stop
,会立即从运行状态退出,直到reset之后才能重新启动。 -
保证IO服务执行
IO服务的run
方法只有在有异步操作未完成的时候才能一直运行,一旦没有异步操作就会退出,因而需要在run
之前保证有异步操作发起,在过程中不断发起异步操作就能够保证IO服务一直运行。 -
数据缓冲区生命周期
发起异步操作后,会立即退出,但是异步操作并没有执行,这就要求提供的数据缓冲区生命周期要足够长,存活到异步操作执行完,即在完成回调中再释放数据缓冲区,通常可以采用智能指针或者new出来的对象。
异步UDP服务器实现
class async_udp_echo_server
{
public:
async_udp_echo_server()
:socket_(io_,udp::endpoint(udp::v4(),1024))
{
do_recv();
std::thread task([&](){ io_.run();});
task.detach();
}
void do_recv()
{
//保证发送完成之前一直有效
char* recv_buf = new char[1024];
socket_.async_receive_from(boost::asio::buffer(recv_buf,1024), remote_ep_,
[recv_buf, this](const boost::system::error_code& error,std::size_t bytes_transferred){
if (!error || error == boost::asio::error::message_size)
{
do_send(recv_buf,bytes_transferred, std::move(remote_ep_));
}
else
{
std::cout << error.message() << "\n";
}
do_recv();
});
}
void do_send(char* send_buf,std::size_t size,udp::endpoint ep)
{
socket_.async_send_to(boost::asio::buffer(send_buf,size),ep,
[send_buf](const boost::system::error_code& error, std::size_t bytes_transferred){
if (!error)
{
std::cout<<"echo finished\n";
}
delete[] send_buf;
});
}
void stop()
{
io_.stop();
}
~async_udp_echo_server()
{
stop();
}
private:
boost::asio::io_service io_;
udp::socket socket_;
udp::endpoint remote_ep_;
};
可以看到do_recv
方法发起了一个异步接收操作,在操作完成回调中再次发起,构造服务器时率先调用了do_recv
,从而保证IO服务一直运行。
do_recv
方法在发起异步操作前申请了一块内存,接收的内容被保存在这块内存之中,当do_send
发起异步发送操作时被借用,直到发送完成才将这段内存释放掉。
在构造函数中启动了一个线程来执行IO服务,并detach掉线程,从而保证服务器不阻塞,在析构函数停止了IO服务。
需要注意到的是remote_ep_
在执行do_send
时被move
了,由于remote_ep_
标识了远程端口,而且被声明为成员变量,在接受操作中会被填充远程端口内容,如果多个远程主机同时发起,单个remote_ep_
是无法正常处理的,所以一旦内容被填充后,就会转移出去给发送操作使用[个人理解,没有实际测试和验证]。
使用方法
async_udp_echo_server server_;
char line[1024];
while (std::cin.getline(line, 1024)){
if(line[0] == 'Q')
break;
};
server_.stop();
网友评论