From d0c3eb74dd61827862344d7883f32f0174f447e2 Mon Sep 17 00:00:00 2001 From: chris Date: Wed, 12 Nov 2003 13:51:04 +0000 Subject: [PATCH] Added chat example. --- asio/src/Makefile.am | 4 + asio/src/Makefile.bor | 2 + asio/src/Makefile.mgw | 2 + asio/src/Makefile.msc | 4 + asio/src/examples/chat/.cvsignore | 8 + asio/src/examples/chat/chat_client.cpp | 138 ++++++++++++++++ asio/src/examples/chat/chat_message.hpp | 131 +++++++++++++++ asio/src/examples/chat/chat_server.cpp | 206 ++++++++++++++++++++++++ 8 files changed, 495 insertions(+) create mode 100644 asio/src/examples/chat/.cvsignore create mode 100644 asio/src/examples/chat/chat_client.cpp create mode 100644 asio/src/examples/chat/chat_message.hpp create mode 100644 asio/src/examples/chat/chat_server.cpp diff --git a/asio/src/Makefile.am b/asio/src/Makefile.am index a7804708..3ed663b0 100644 --- a/asio/src/Makefile.am +++ b/asio/src/Makefile.am @@ -15,6 +15,8 @@ noinst_PROGRAMS = \ tests/timed_dgram_recv_test \ tests/timer_test \ tests/tpc_echo_server_test \ + examples/chat/chat_client \ + examples/chat/chat_server \ examples/echo/async_tcp_echo_server \ examples/echo/async_udp_echo_server \ examples/echo/blocking_tcp_echo_client \ @@ -40,6 +42,8 @@ tests_timed_connect_test_SOURCES = tests/timed_connect_test.cpp tests_timed_dgram_recv_test_SOURCES = tests/timed_dgram_recv_test.cpp tests_timer_test_SOURCES = tests/timer_test.cpp tests_tpc_echo_server_test_SOURCES = tests/tpc_echo_server_test.cpp +examples_chat_chat_client_SOURCES = examples/chat/chat_client.cpp +examples_chat_chat_server_SOURCES = examples/chat/chat_server.cpp examples_echo_async_tcp_echo_server_SOURCES = examples/echo/async_tcp_echo_server.cpp examples_echo_async_udp_echo_server_SOURCES = examples/echo/async_udp_echo_server.cpp examples_echo_blocking_tcp_echo_client_SOURCES = examples/echo/blocking_tcp_echo_client.cpp diff --git a/asio/src/Makefile.bor b/asio/src/Makefile.bor index 84494d71..f07ca2f4 100644 --- a/asio/src/Makefile.bor +++ b/asio/src/Makefile.bor @@ -19,6 +19,8 @@ all: \ tests\timed_dgram_recv_test.exe \ tests\timer_test.exe \ tests\tpc_echo_server_test.exe \ + examples\chat\chat_client.exe \ + examples\chat\chat_server.exe \ examples\echo\async_tcp_echo_server.exe \ examples\echo\async_udp_echo_server.exe \ examples\echo\blocking_tcp_echo_client.exe \ diff --git a/asio/src/Makefile.mgw b/asio/src/Makefile.mgw index 96f6c716..8b177779 100644 --- a/asio/src/Makefile.mgw +++ b/asio/src/Makefile.mgw @@ -21,6 +21,8 @@ TEST_EXES = \ tests/tpc_echo_server_test.exe EXAMPLE_EXES = \ + examples\chat\chat_client.exe \ + examples\chat\chat_server.exe \ examples\echo\async_tcp_echo_server.exe \ examples\echo\async_udp_echo_server.exe \ examples\echo\blocking_tcp_echo_client.exe \ diff --git a/asio/src/Makefile.msc b/asio/src/Makefile.msc index f54f510b..d2cdc638 100644 --- a/asio/src/Makefile.msc +++ b/asio/src/Makefile.msc @@ -19,6 +19,8 @@ all: \ tests\timed_dgram_recv_test.exe \ tests\timer_test.exe \ tests\tpc_echo_server_test.exe \ + examples\chat\chat_client.exe \ + examples\chat\chat_server.exe \ examples\echo\async_tcp_echo_server.exe \ examples\echo\async_udp_echo_server.exe \ examples\echo\blocking_tcp_echo_client.exe \ @@ -42,6 +44,8 @@ tests\timed_connect_test.exe: tests\timed_connect_test.obj tests\timed_dgram_recv_test.exe: tests\timed_dgram_recv_test.obj tests\timer_test.exe: tests\timer_test.obj tests\tpc_echo_server_test.exe: tests\tpc_echo_server_test.obj +examples\chat\chat_client.exe: examples\chat\chat_client.obj +examples\chat\chat_server.exe: examples\chat\chat_server.obj examples\echo\async_tcp_echo_server.exe: examples\echo\async_tcp_echo_server.obj examples\echo\async_udp_echo_server.exe: examples\echo\async_udp_echo_server.obj examples\echo\blocking_tcp_echo_client.exe: examples\echo\blocking_tcp_echo_client.obj diff --git a/asio/src/examples/chat/.cvsignore b/asio/src/examples/chat/.cvsignore new file mode 100644 index 00000000..0e76ed0e --- /dev/null +++ b/asio/src/examples/chat/.cvsignore @@ -0,0 +1,8 @@ +.deps +.dirstamp +*.exe +*_server +*_client +*.ilk +*.pdb +*.tds diff --git a/asio/src/examples/chat/chat_client.cpp b/asio/src/examples/chat/chat_client.cpp new file mode 100644 index 00000000..a0cb1cc0 --- /dev/null +++ b/asio/src/examples/chat/chat_client.cpp @@ -0,0 +1,138 @@ +#include +#include +#include +#include +#include +#include +#include +#include "asio.hpp" +#include "chat_message.hpp" + +typedef std::list chat_message_list; + +class chat_client +{ +public: + chat_client(asio::demuxer& d, short port, const std::string& host) + : demuxer_(d), + connector_(d), + socket_(d) + { + connector_.async_connect(socket_, asio::inet_address_v4(port, host), + boost::bind(&chat_client::handle_connect, this, _1)); + } + + void send(const chat_message& msg) + { + demuxer_.operation_immediate( + boost::bind(&chat_client::do_send, this, msg)); + } + + void close() + { + demuxer_.operation_immediate(boost::bind(&chat_client::do_close, this)); + } + +private: + + void handle_connect(const asio::socket_error& error) + { + if (!error) + { + async_recv_chat_message(socket_, recv_msg_, + boost::bind(&chat_client::handle_recv, this, _1, _2, _3)); + } + } + + void handle_recv(const asio::socket_error& error, size_t length, + size_t last_length) + { + if (!error && last_length > 0) + { + std::cout.write(recv_msg_.body(), recv_msg_.body_length()); + std::cout << "\n"; + async_recv_chat_message(socket_, recv_msg_, + boost::bind(&chat_client::handle_recv, this, _1, _2, _3)); + } + } + + void do_send(chat_message msg) + { + bool send_in_progress = !send_msgs_.empty(); + send_msgs_.push_back(msg); + if (!send_in_progress) + { + async_send_chat_message(socket_, send_msgs_.front(), + boost::bind(&chat_client::handle_send, this, _1, _2, _3)); + } + } + + void handle_send(const asio::socket_error& error, size_t length, + size_t last_length) + { + if (!error && last_length > 0) + { + send_msgs_.pop_front(); + if (!send_msgs_.empty()) + { + async_send_chat_message(socket_, send_msgs_.front(), + boost::bind(&chat_client::handle_send, this, _1, _2, _3)); + } + } + } + + void do_close() + { + connector_.close(); + socket_.close(); + } + +private: + asio::demuxer& demuxer_; + asio::socket_connector connector_; + asio::stream_socket socket_; + chat_message recv_msg_; + chat_message_list send_msgs_; +}; + +int main(int argc, char* argv[]) +{ + try + { + if (argc != 3) + { + std::cerr << "Usage: chat_client \n"; + return 1; + } + + asio::demuxer d; + + using namespace std; // For atoi, strlen, strcpy and sprintf. + chat_client c(d, atoi(argv[2]), argv[1]); + + asio::detail::thread t(boost::bind(&asio::demuxer::run, &d)); + + char line[chat_message::max_body_length + 1]; + while (std::cin.getline(line, chat_message::max_body_length + 1)) + { + chat_message msg; + msg.length(chat_message::header_length + strlen(line)); + sprintf(msg.data(), "%4d", msg.body_length()); + strncpy(msg.body(), line, msg.body_length()); + c.send(msg); + } + + c.close(); + t.join(); + } + catch (asio::socket_error& e) + { + std::cerr << "Socket error: " << e.message() << "\n"; + } + catch (std::exception& e) + { + std::cerr << "Exception: " << e.what() << "\n"; + } + + return 0; +} diff --git a/asio/src/examples/chat/chat_message.hpp b/asio/src/examples/chat/chat_message.hpp new file mode 100644 index 00000000..15ac23c1 --- /dev/null +++ b/asio/src/examples/chat/chat_message.hpp @@ -0,0 +1,131 @@ +#ifndef CHAT_MESSAGE_HPP +#define CHAT_MESSAGE_HPP + +#include +#include +#include +#include "asio.hpp" + +class chat_message +{ +public: + enum { header_length = 4 }; + enum { max_body_length = 512 }; + + const char* data() const + { + return &data_[0]; + } + + char* data() + { + return &data_[0]; + } + + size_t length() const + { + return data_.size(); + } + + void length(size_t l) + { + data_.resize(l); + } + + const char* body() const + { + return &data_[0] + header_length; + } + + char* body() + { + return &data_[0] + header_length; + } + + size_t body_length() const + { + return data_.size() - header_length; + } + +private: + std::vector data_; +}; + +template +size_t send_chat_message(Stream& s, chat_message& msg) +{ + return asio::send_n(s, msg.data(), msg.length()); +} + +template +void async_send_chat_message(Stream& s, chat_message& msg, Handler handler) +{ + asio::async_send_n(s, msg.data(), msg.length(), handler); +} + +template +size_t recv_chat_message(Stream& s, chat_message& msg) +{ + msg.length(chat_message::header_length); + if (asio::recv_n(s, msg.data(), chat_message::header_length) == 0) + return 0; + std::istrstream is(msg.data(), chat_message::header_length); + size_t body_length = 0; + is >> body_length; + if (!is || body_length > chat_message::max_body_length) + return 0; + msg.length(chat_message::header_length + body_length); + return asio::recv_n(s, msg.body(), msg.body_length()); +} + +template +class recv_chat_message_handler +{ +public: + recv_chat_message_handler(Stream& stream, chat_message& msg, Handler handler) + : stream_(stream), + msg_(msg), + handler_(handler) + { + } + + template + void operator()(const Error& error, size_t length, size_t last_length) + { + if (!error && last_length > 0) + { + std::istrstream is(msg_.data(), chat_message::header_length); + size_t body_length = 0; + is >> body_length; + if (is && body_length <= chat_message::max_body_length) + { + msg_.length(chat_message::header_length + body_length); + asio::async_recv_n(stream_, msg_.body(), msg_.body_length(), handler_); + } + else + { + last_length = 0; + handler_(error, length, last_length); + } + } + else + { + handler_(error, length, last_length); + } + } + +private: + Stream& stream_; + chat_message& msg_; + Handler handler_; +}; + +template +void async_recv_chat_message(Stream& s, chat_message& msg, Handler handler) +{ + msg.length(chat_message::header_length); + asio::async_recv_n(s, msg.data(), chat_message::header_length, + recv_chat_message_handler(s, msg, handler)); +} + +#endif // CHAT_MESSAGE_HPP diff --git a/asio/src/examples/chat/chat_server.cpp b/asio/src/examples/chat/chat_server.cpp new file mode 100644 index 00000000..4ba26c0a --- /dev/null +++ b/asio/src/examples/chat/chat_server.cpp @@ -0,0 +1,206 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "asio.hpp" +#include "chat_message.hpp" + +//---------------------------------------------------------------------- + +typedef std::list chat_message_list; + +//---------------------------------------------------------------------- + +class chat_participant +{ +public: + virtual ~chat_participant() {} + virtual void deliver(const chat_message& msg) = 0; +}; + +typedef boost::shared_ptr chat_participant_ptr; + +//---------------------------------------------------------------------- + +class chat_room +{ +public: + void join(chat_participant_ptr participant) + { + participants_.insert(participant); + std::for_each(recent_msgs_.begin(), recent_msgs_.end(), + boost::bind(&chat_participant::deliver, participant, _1)); + } + + void leave(chat_participant_ptr participant) + { + participants_.erase(participant); + } + + void deliver(const chat_message& msg) + { + recent_msgs_.push_back(msg); + while (recent_msgs_.size() > max_recent_msgs) + recent_msgs_.pop_front(); + + std::for_each(participants_.begin(), participants_.end(), + boost::bind(&chat_participant::deliver, _1, boost::ref(msg))); + } + +private: + std::set participants_; + enum { max_recent_msgs = 100 }; + chat_message_list recent_msgs_; +}; + +//---------------------------------------------------------------------- + +class chat_session + : public chat_participant, + public boost::enable_shared_from_this +{ +public: + chat_session(asio::demuxer& d, chat_room& r) + : socket_(d), + room_(r) + { + } + + asio::stream_socket& socket() + { + return socket_; + } + + void start() + { + room_.join(shared_from_this()); + async_recv_chat_message(socket_, recv_msg_, boost::bind( + &chat_session::handle_recv, shared_from_this(), _1, _2, _3)); + } + + void deliver(const chat_message& msg) + { + bool send_in_progress = !send_msgs_.empty(); + send_msgs_.push_back(msg); + if (!send_in_progress) + { + async_send_chat_message(socket_, send_msgs_.front(), boost::bind( + &chat_session::handle_send, shared_from_this(), _1, _2, _3)); + } + } + + void handle_recv(const asio::socket_error& error, size_t length, + size_t last_length) + { + if (!error && last_length > 0) + { + room_.deliver(recv_msg_); + async_recv_chat_message(socket_, recv_msg_, boost::bind( + &chat_session::handle_recv, shared_from_this(), _1, _2, _3)); + } + else + { + room_.leave(shared_from_this()); + } + } + + void handle_send(const asio::socket_error& error, size_t length, + size_t last_length) + { + if (!error && last_length > 0) + { + send_msgs_.pop_front(); + if (!send_msgs_.empty()) + { + async_send_chat_message(socket_, send_msgs_.front(), boost::bind( + &chat_session::handle_send, shared_from_this(), _1, _2, _3)); + } + } + else + { + room_.leave(shared_from_this()); + } + } + +private: + asio::stream_socket socket_; + chat_room& room_; + chat_message recv_msg_; + chat_message_list send_msgs_; +}; + +typedef boost::shared_ptr chat_session_ptr; + +//---------------------------------------------------------------------- + +class chat_server +{ +public: + chat_server(asio::demuxer& d, short port) + : demuxer_(d), + acceptor_(d, asio::inet_address_v4(port)) + { + chat_session_ptr new_session(new chat_session(demuxer_, room_)); + acceptor_.async_accept(new_session->socket(), + boost::bind(&chat_server::handle_accept, this, new_session, _1)); + } + + void handle_accept(chat_session_ptr session, const asio::socket_error& error) + { + if (!error) + { + session->start(); + chat_session_ptr new_session(new chat_session(demuxer_, room_)); + acceptor_.async_accept(new_session->socket(), + boost::bind(&chat_server::handle_accept, this, new_session, _1)); + } + } + +private: + asio::demuxer& demuxer_; + asio::socket_acceptor acceptor_; + chat_room room_; +}; + +typedef boost::shared_ptr chat_server_ptr; +typedef std::list chat_server_list; + +//---------------------------------------------------------------------- + +int main(int argc, char* argv[]) +{ + try + { + if (argc < 2) + { + std::cerr << "Usage: chat_server [ ...]\n"; + return 1; + } + + asio::demuxer d; + + chat_server_list servers; + for (int i = 1; i < argc; ++i) + { + using namespace std; // For atoi. + chat_server_ptr server(new chat_server(d, atoi(argv[i]))); + servers.push_back(server); + } + + d.run(); + } + catch (asio::socket_error& e) + { + std::cerr << "Socket error: " << e.message() << "\n"; + } + catch (std::exception& e) + { + std::cerr << "Exception: " << e.what() << "\n"; + } + + return 0; +}