这篇文章主要解析,聊天室服务器的设计。
所用的函数基本都来自于boost库,通过阅读官方的文档,来了解具体的实现操作以及函数的作用。
首先,他是通过一个TCP连接来提供服务。
通过asio的io_service来实现与底层操作系统io之间的对接。
通过,async_()来实现异步的收发消息。
通过一个双向队列deque来实现存储消息的数据结构。
通过tcp::endpoint来监听消息传递
通过 tcp::socket 设置套接桥梁,消息传输管道
using chat_message_queue = std::deque<chat_message>;
**实现一个聊天室功能,所有的人进来后都存在于聊天室中,发出的消息其他处在聊天室中的人都可以看到。
添加客户端加入,退出功能。
实现群发分发消息功能。
并且存储一百条消息记录,使用一个双向链表来实现消息的淘汰和刷新**
class chat_room {
public:
public:
void join(chat_session_ptr);
void leave(chat_session_ptr);
void deliver(const chat_message&);
private:
std::set<chat_session_ptr> participants_;
enum { max_recent_msgs = 100 };
chat_message_queue recent_msgs_;
};
//有人加入后存储客户,分发以前的消息。
void chat_room::join(chat_session_ptr participant) {
participants_.insert(participant);
for (const auto& msg : recent_msgs_)
participant->deliver(msg);
}
//有人离开后 删除客户
void chat_room::leave(chat_session_ptr participant) {
participants_.erase(participa3nt);
}
//新客户进来之后 分发存储的100条消息 让新客户知道聊了点什么
void chat_room::deliver(const chat_message &msg) {
recent_msgs_.push_back(msg);
while (recent_msgs_.size() > max_recent_msgs)
recent_msgs_.pop_front();
for (auto& participant : participants_)
participant->deliver(msg);
}
/**实现消息的处理功能,一个新消息进来后,服务器将会接收到消息的类型和消息的实在。接收到之后,要分发出去让其他的客户看到。**/
/实现消息的处理功能,一个新消息进来后,服务器将会接收到消息的类型和消息的实在。接收到之后,要分发出去让其他的客户看到。/
/*读写消息
**** boost::asio::async_read表示使用异步的方式,异步相对于同步而言更加的灵活而且更加的健壮,效率更高。/**
而在读取头部之后如果没有发生错误,就去立马读取消息实体,读完消息实体之后又返回去读取吓一跳消息的头部。如此循环就实现了消息的不断读取。
class chat_session : public std::enable_shared_from_this<chat_session> {
public:
chat_session(tcp::socket socket, chat_room &room)
: socket_(std::move(socket)), room_(room) {
//读取消息头部
void do_read_header() {
auto self(shared_from_this());
boost::asio::async_read(
socket_,
boost::asio::buffer(read_msg_.data(), chat_message::header_length),
[this, self](boost::system::error_code ec, std::size_t /*length*/) {
if (!ec && read_msg_.decode_header()) {
do_read_body();
});
}
//读取消息实体
void do_read_body() {
auto self(shared_from_this());
boost::asio::async_read(
socket_, boost::asio::buffer(read_msg_.body(), read_msg_.body_length()),
[this, self](boost::system::error_code ec, std::size_t /*length*/) {
if (!ec) {
//room_.deliver(read_msg_);
handleMessage();
do_read_header();
});
}
到此,发现还需要一个方法去分析如何处理这些信息。
如果是客户发过来的消息类型或者消息实体,如何去处理他。
而服务器如何将受到的消息广播给每一个客户收取。也需要去具体化。以下函数就是对于收发消息的选择与控制。
void handleMessage() {
if (read_msg_.type() == MT_BIND_NAME) {
auto bindName = toObject<SBindName>();
m_name = bindName.bindName();
} else if (read_msg_.type() == MT_CHAT_INFO) {
auto chat = toObject<SChatInfo>();
m_chatInformation = chat.chatInformation()
auto rinfo = buildRoomInfo();
chat_message msg;
msg.setMessage(MT_ROOM_INFO, rinfo);
room_.deliver(msg)
//服务器写入消息,为了广播给所有的客户
void do_write() {
auto self(shared_from_this());
boost::asio::async_write(
socket_, boost::asio::buffer(write_msgs_.front().data(),
write_msgs_.front().length()),
[this, self](boost::system::error_code ec, std::size_t /*length*/) {
if (!ec) {
write_msgs_.pop_front();
if (!write_msgs_.empty())
do_write();
//服务器接收到消息后处理
void do_accept() {
acceptor_.async_accept(socket_, [this](boost::system::error_code ec) {
if (!ec) {
auto session =
std::make_shared<chat_session>(std::move(socket_), room_);
session->start();
}
boost::asio::io_service io_service;//使用io_service实现与操作系统之间io操作
tcp::endpoint endpoint(tcp::v4(), std::atoi(argv[i]));//使用enpoint监听
servers.emplace_back(io_service, endpoint);//监听到的消息插入io_service
io_service.run();
emplace_back: 在容器尾部添加一个元素,这个元素原地构造,不需要触发拷贝构造和转移构造。而且调用形式更加简洁,直接根据参数初始化临时对象的成员。 相对于push_back来说更加的省空间。
服务器实现的巧妙而又简单,通过deque来完美展现了缓存消息的处理,又使用了交替的读写结构模拟不断的收发消息。
error提供了简单而又稳定的错误监测结构,避免了在出错时抛出异常,卡死线程,进而崩溃掉。
总的来说,设计简单而又巧妙,值得初学者去解析和阅读。