基于TCP协议的应用成广播
#include "examples/hub/codec.h"
#include "muduo/base/Logging.h"
#include "muduo/net/EventLoop.h"
#include "muduo/net/TcpServer.h"
#include <map>
#include <set>
#include <stdio.h>
using namespace muduo;
using namespace muduo::net;
namespace pubsub
{
typedef std::set<string> ConnectionSubscription;
class Topic : public muduo::copyable
{
public:
Topic(const string& topic)
: topic_(topic)
{
}
void add(const TcpConnectionPtr& conn)
{
// 保存订阅该topic的client连接
audiences_.insert(conn);
if (lastPubTime_.valid())
{
// 给这个client发送
conn->send(makeMessage());
}
}
void remove(const TcpConnectionPtr& conn)
{
// conn不订阅这个topic
audiences_.erase(conn);
}
// 订阅这个topic的client连接发送请求
void publish(const string& content, Timestamp time)
{
content_ = content;
lastPubTime_ = time;
string message = makeMessage();
// 遍历订阅该topic的client
for (std::set<TcpConnectionPtr>::iterator it = audiences_.begin();
it != audiences_.end();
++it)
{
(*it)->send(message);
}
}
private:
string makeMessage()
{
return "pub " + topic_ + "\r\n" + content_ + "\r\n";
}
// topic的名字
string topic_;
// 最近次该topic publish的内容
string content_;
// 最近一次publish时间
Timestamp lastPubTime_;
// 保存订阅这个topic的client连接
std::set<TcpConnectionPtr> audiences_;
};
class PubSubServer : noncopyable
{
public:
PubSubServer(muduo::net::EventLoop* loop,
const muduo::net::InetAddress& listenAddr)
: loop_(loop),
server_(loop, listenAddr, "PubSubServer")
{
server_.setConnectionCallback(
std::bind(&PubSubServer::onConnection, this, _1));
server_.setMessageCallback(
std::bind(&PubSubServer::onMessage, this, _1, _2, _3));
loop_->runEvery(1.0, std::bind(&PubSubServer::timePublish, this));
}
void start()
{
server_.start();
}
private:
void onConnection(const TcpConnectionPtr& conn)
{
if (conn->connected())
{
// ConnectionSubscription 保存所有topic的名字
conn->setContext(ConnectionSubscription());
}
else
{
const ConnectionSubscription& connSub
= boost::any_cast<const ConnectionSubscription&>(conn->getContext());
// subtle: doUnsubscribe will erase *it, so increase before calling.
for (ConnectionSubscription::const_iterator it = connSub.begin();
it != connSub.end();)
{
// 删除Topic与conn的关联
doUnsubscribe(conn, *it++);
}
}
}
void onMessage(const TcpConnectionPtr& conn,
Buffer* buf,
Timestamp receiveTime)
{
ParseResult result = kSuccess;
while (result == kSuccess)
{
string cmd;
string topic;
string content;
result = parseMessage(buf, &cmd, &topic, &content);
if (result == kSuccess)
{
if (cmd == "pub")
{
doPublish(conn->name(), topic, content, receiveTime);
}
else if (cmd == "sub")
{
LOG_INFO << conn->name() << " subscribes " << topic;
doSubscribe(conn, topic);
}
else if (cmd == "unsub")
{
doUnsubscribe(conn, topic);
}
else
{
conn->shutdown();
result = kError;
}
}
else if (result == kError)
{
conn->shutdown();
}
}
}
void timePublish()
{
Timestamp now = Timestamp::now();
doPublish("internal", "utc_time", now.toFormattedString(), now);
}
void doSubscribe(const TcpConnectionPtr& conn,
const string& topic)
{
ConnectionSubscription* connSub
= boost::any_cast<ConnectionSubscription>(conn->getMutableContext());
// 这个hub中存在topic主题
connSub->insert(topic);
// 将连接conn保存topic中
getTopic(topic).add(conn);
}
void doUnsubscribe(const TcpConnectionPtr& conn,
const string& topic)
{
LOG_INFO << conn->name() << " unsubscribes " << topic;
// 这个topic删除这个连接conn
getTopic(topic).remove(conn);
// topic could be the one to be destroyed, so don't use it after erasing.
ConnectionSubscription* connSub
= boost::any_cast<ConnectionSubscription>(conn->getMutableContext());
// 这个连接也删除订阅topic
connSub->erase(topic);
}
void doPublish(const string& source,
const string& topic,
const string& content,
Timestamp time)
{
//向topic的所有client发送content
getTopic(topic).publish(content, time);
}
Topic& getTopic(const string& topic)
{
// 查找topic对应的Topic对象
std::map<string, Topic>::iterator it = topics_.find(topic);
if (it == topics_.end())
{
// 没有找到,插入topic对象
it = topics_.insert(make_pair(topic, Topic(topic))).first;
}
return it->second;
}
EventLoop* loop_;
TcpServer server_;
// 每一个topic对应一个Topic对象
std::map<string, Topic> topics_;
};
} // namespace pubsub
int main(int argc, char* argv[])
{
if (argc > 1)
{
uint16_t port = static_cast<uint16_t>(atoi(argv[1]));
EventLoop loop;
if (argc > 2)
{
//int inspectPort = atoi(argv[2]);
}
pubsub::PubSubServer server(&loop, InetAddress(port));
server.start();
loop.loop();
}
else
{
printf("Usage: %s pubsub_port [inspect_port]\n", argv[0]);
}
}