最近boostを真面目に使い始めたので、その辺を書いてみます。
今回はudpサーバの作り方です。
TCPじゃなくてUDP?って感じですが、UDPの方がシンプルなのでまずはこちらを理解しましょう。
#include <ctime>
#include <iostream>
#include <string>
#include <boost/array.hpp>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/asio.hpp>
using boost::asio::ip::udp;
class udp_server
{
udp::socket socket_;
udp::endpoint remote_endpoint_;
boost::array<char, 512> recv_buffer_;
public:
udp_server(boost::asio::io_service& io_service, int port)
: socket_(io_service, udp::endpoint(udp::v4(), port)){
start_receive();
}
private:
void start_receive(){
socket_.async_receive_from(
boost::asio::buffer(recv_buffer_), remote_endpoint_,
boost::bind(&udp_server::handle_receive, this,
boost::asio::placeholders::error, _2));
}
void handle_receive(const boost::system::error_code& error, size_t len){
if (!error || error == boost::asio::error::message_size){
std::string message(recv_buffer_.data(), len);
std::cout << remote_endpoint_.address() << ":" << remote_endpoint_.port() << std::endl ;
std::cout << message << std::endl;
socket_.send_to(boost::asio::buffer(recv_buffer_, len), remote_endpoint_);
start_receive();
}
}
};
int main(){
try{
boost::asio::io_service io_service;
udp_server userver(io_service, 10000);
io_service.run();
}
catch (std::exception& e) {
std::cerr << e.what() << std::endl;
}
return 0;
}
udp_serverクラスを見てみましょう。まずは変数部分。
udp::socket socket_;
udp::endpoint remote_endpoint_;
boost::array<char, 512> recv_buffer_;
変数は3つ、ソケット、エンドポイント、それと受信用バッファですね。
エンドポイントってのは、IPやらポートやらを格納するクラスです。
今回はパケットの送信元を格納するために使います。
バッファにboost::arrayを使っていますが、従来のchar [512]の書式だと、
どれだけの長さのバッファが確保されているのか、ってのを取得する方法がないんですね。
(sizeofを使った取得方法が一応ありますけど…)
foreachやイテレータにも対応しているので、使えるときにはこちらを使いましょう。
udp_server(boost::asio::io_service& io_service, int port)
: socket_(io_service, udp::endpoint(udp::v4(), port)){
start_receive();
}
コンストラクタでは、ソケットにio_serviceとUDPサーバに必要な物を渡します。
そして、start_receiveって関数を呼んでいます。
void start_receive(){
socket_.async_receive_from(
boost::asio::buffer(recv_buffer_), remote_endpoint_,
boost::bind(&udp_server::handle_receive, this,
boost::asio::placeholders::error, _2));
}
start_recieveってのはパケットを受信した時のイベント登録をする関数です。
async_で始まるのは非同期イベントの登録を意味します。
つまり、UDPのパケットを受け取ったら、handle_receive関数を呼び出すイベントを登録しています。
boost::bindのおかげでメンバー関数をコールバックできる当たりが素敵。
void handle_receive(const boost::system::error_code& error, size_t len){
if (!error || error == boost::asio::error::message_size){
std::string message(recv_buffer_.data(), len);
std::cout << remote_endpoint_.address() << ":" << remote_endpoint_.port() << std::endl ;
std::cout << message << std::endl;
socket_.send_to(boost::asio::buffer(recv_buffer_, len), remote_endpoint_);
start_receive();
}
}
その呼び出されるhandle_receiveの中身。
handle_receive関数はエラーでも呼び出されるので、エラーでない時だけ通常処理をします。
受信したデータをIP、ポートと一緒に画面に表示し、送信元に投げ返します。
その後、再度イベントを登録します。
一度イベントが呼び出された時点でイベントの登録が無効になるので、
もう一度登録しなおさないと駄目なんですね。
ちなみに、message_sizeってのはバッファがあふれた時のエラーです。
このエラーは無視します。
int main(){
boost::asio::io_service io_service;
udp_server userver(io_service, 10000);
io_service.run();
return 0;
}
最後はmain関数。例外部分は省きました。
io_serviceってのがイベントの面倒をみるクラスです。
今回はudpserverクラスしか登録していませんが、同時にtcpserverクラスも登録して
両方の面倒を見てもらう、という使い方もできます。
イベントをゴニョゴニョ!っと登録したらio_service.run()で待ち受け開始!
後は延々とUDPサーバが動き続けます。
ということで、わずか50行程度でUDPサーバができちゃうわけですよ、すごい!
しかも、WindowsだのLinuxだのというOSを意識しないってのもいいですね。