[C++]楽々UDPサーバ
ということで、今回はTCPサーバです。
#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::tcp;
class tcp_connection
: public boost::enable_shared_from_this<tcp_connection>{
tcp::socket socket_;
boost::array<char, 4096> recv_buffer_;
private:
tcp_connection(boost::asio::io_service& io_service)
: socket_(io_service){}
void handle_write(boost::shared_ptr<std::vector<char> > /* sendbuf */, size_t /* len */ ){}
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 << message << std::endl;
/* エコー */
boost::shared_ptr<std::vector<char> > sendbuf(
new std::vector<char>(recv_buffer_.begin(), recv_buffer_.begin() + len) );
boost::asio::async_write(socket_, boost::asio::buffer(*sendbuf),
boost::bind(&tcp_connection::handle_write, shared_from_this(), sendbuf, len));
start_receive();
}
}
void start_receive(){
socket_.async_receive(
boost::asio::buffer(recv_buffer_),
boost::bind(&tcp_connection::handle_receive, shared_from_this(),
boost::asio::placeholders::error, _2));
}
public:
typedef boost::shared_ptr<tcp_connection> pointer;
static pointer create(boost::asio::io_service& io_service){
return pointer(new tcp_connection(io_service));
}
tcp::socket& socket(){
return socket_;
}
void start(){
std::cout << socket_.remote_endpoint().address() << ":" << socket_.remote_endpoint().port() << std::endl ;
start_receive();
}
};
class tcp_server{
tcp::acceptor acceptor_;
void start_accept(){
tcp_connection::pointer new_connection =
tcp_connection::create(acceptor_.io_service());
acceptor_.async_accept(new_connection->socket(),
boost::bind(&tcp_server::handle_accept, this, new_connection,
boost::asio::placeholders::error));
}
void handle_accept(tcp_connection::pointer new_connection,
const boost::system::error_code& error){
if (!error){
new_connection->start();
start_accept();
}
}
public:
tcp_server(boost::asio::io_service& io_service, int port)
: acceptor_(io_service, tcp::endpoint(tcp::v4(), port)){
start_accept();
}
};
int main(){
try{
boost::asio::io_service io_service;
tcp_server server1(io_service, 10000);
// udp_server server2(io_service, 10000);
io_service.run();
}
catch (std::exception& e){
std::cerr << e.what() << std::endl;
}
return 0;
}
TCPのサーバを作った人ならわかるでしょうが、TCPには2つの段階があります。
まず、相手からの接続を受け付けるためのリスナーを作成します。
その後、誰かが接続してくればそこで接続を確立し、再度リスナーは待ち受けに戻ります。
まずはその部分から見てみましょう。
tcp_server(boost::asio::io_service& io_service, int port)
: acceptor_(io_service, tcp::endpoint(tcp::v4(), port)){
start_accept();
}
コンストラクタです。
UDPとほぼ同じと言っていいでしょう。
void start_accept(){
tcp_connection::pointer new_connection =
tcp_connection::create(acceptor_.io_service());
acceptor_.async_accept(new_connection->socket(),
boost::bind(&tcp_server::handle_accept, this, new_connection,
boost::asio::placeholders::error));
}
ここで、リスナーのイベントを登録します。
リスナーの待ち受けにソケットクラスが必要なので、
この時点で接続した時のクラスを作っておきます。
void handle_accept(tcp_connection::pointer new_connection,
const boost::system::error_code& error){
if (!error){
new_connection->start();
start_accept();
}
}
接続時のハンドルクラスです。
new_connectionに接続した時の処理を行ない、再度リスナーに待ち受けイベントを登録します。
次は接続したときの相手をするtcp_connectionクラスです。
コンストラクタ、createは省略します。ま、見ればわかりますね。
なお、コンストラクタがprivateにあるのはnewで他のクラスから生成できないようにするためです。
newの代わりにcreate関数を使ってね、って事ですね。
void start(){
std::cout << socket_.remote_endpoint().address() << ":" << socket_.remote_endpoint().port() << std::endl ;
start_receive();
}
接続してきたら、誰か接続してきたのかを表示して、パケットが飛んできた時の待ち受けを行ないます。
socket_.remote_endpoint()で誰が接続したのかを取り出すことが出来ます。
なお、socket_.local_endpoint()は自分自身のIPやポートが入っていますので注意。
start_receive関数も省略。ただイベント登録しているだけですね。
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 << message << std::endl;
/* エコー */
boost::shared_ptr<std::vector<char> > sendbuf(
new std::vector<char>(recv_buffer_.begin(), recv_buffer_.begin() + len) );
boost::asio::async_write(socket_, boost::asio::buffer(*sendbuf),
boost::bind(&tcp_connection::handle_write, shared_from_this(), sendbuf, len));
start_receive();
}
}
UDPサーバと同じく、相手から送られたメッセージを画面に表示して、
その後送られたパケットをそのまま投げ返しています。
なお、async_writeというのを使っています。
UDPと同じく、send関数を使ってもいいのですが、非同期送信を使ってみました。
送信が終了したらhandle_write関数が呼び出されます。
send関数を使う場合は送信用バッファをローカルに確保しても良いのですが、
非同期送信なのでローカルに確保するとまずいことになります。
また、メンバー変数として確保してもやはり先にhandle_recieveが先に2回呼ばれてしまうと、
最初のバッファが2回目のデータで上書きされてしまいます。
そこで、shared_ptrを使い、毎回送信用バッファを作成しているわけです。
handle_writeは何もしていません。
特に送信後に何かを行うイベントがないのでこうなっています。
ではこの関数は要らないの?と思いますが、引数に送信バッファを持っています。
つまり、このshared_ptrがこの関数が終わるまで生きているわけです。
この関数は送信後に呼ばれるため、送信用バッファを送信後に破棄するためにこの関数が必要になります。
ということで、TCPサーバも100行程度で作れました。
いやぁ、boost便利ですね!