examples/asio/chat/server.cc 单线程
examples/asio/chat/server_threaded.cc,多线程TcpServer,并用mutex来保护共享数据
examples/asio/chat/server_threaded_efficient.cc,借shared_ptr实现copy-on-write的手法来降低锁竞争
examples/asio/chat/server_threaded_highperformance.cc,采用thread local变量实现多线程高效转发
消息分为包头与包体,每条消息有一个4字节的头部,以网络序存放字符串的长度。包体是一个字符串,字符串也不一定以’\0’结尾。比方说有两条消息"hello"和"chenshuo",那么打包后的字节流是:0x00,0x00,0x00,0x05, 'h','e','l','l','o',0x00,0x00,0x00,0x08,'c','h', 'e','n','s','h','u','o'共21字节。
shared_ptr 指针
借shared_ptr实现copy on write
shared_ptr是引用计数智能指针,如果当前只有一个观察者,那么引用计数为1,可以用shared_ptr::unique()来判断对于write端,如果发现引用计数为1,这时可以安全地修改对象,不必担心有人在读它。对于read端,在读之前把引用计数加1,读完之后减1,这样可以保证在读的期间其引用计数大于1,可以阻止并发写。比较难的是,对于write端,如果发现引用计数大于1,该如何处理?既然要更新数据,肯定要加锁,如果这时候其他线程正在读,那么不能在原来的数据上修改,得创建一个副本,在副本上修改,修改完了再替换。如果没有用户在读,那么可以直接修改。
#ifndef MUDUO_EXAMPLES_ASIO_CHAT_CODEC_H
#define MUDUO_EXAMPLES_ASIO_CHAT_CODEC_H
#include
#include
#include
#include
#include
#include
class LengthHeaderCodec : boost::noncopyable
{
public:
typedef boost::function StringMessageCallback;
explicit LengthHeaderCodec(const StringMessageCallback& cb)
: messageCallback_(cb)
{
}
/*消息到达的回调函数*/
void onMessage(const muduo::net::TcpConnectionPtr& conn,
muduo::net::Buffer* buf,
muduo::Timestamp receiveTime)
{
/*这里可能有多条信息一起到达*/
while (buf->readableBytes() >= kHeaderLen) // kHeaderLen == 4
{
// FIXME: use Buffer::peekInt32()
/*这里的消息包括消息头(包头)和消息尾(包体)*/
const void* data = buf->peek(); //这里只是查看一下数据而已,并没有取出数据
/*读出的是对方发过来的网络字节序(大端)的前4个字节(header)*/
int32_t be32 = *static_cast(data); // SIGBUS
/*把网络字节转为主机字节序*/
const int32_t len = muduo::net::sockets::networkToHost32(be32);
/*这里假设消息的包体长度不超过64k */
if (len > 65536 || len < 0) //消息不合法
{
LOG_ERROR << "Invalid length " << len;
conn->shutdown(); // FIXME: disable reading
break;
}
else if (buf->readableBytes() >= len + kHeaderLen)
{
buf->retrieve(kHeaderLen);
/*这里还没有取出消息的包体,只是peek一下*/
muduo::string message(buf->peek(), len);
/*回调应用程序,让应用层来处理包体*/
messageCallback_(conn, message, receiveTime);
/*取出包体*/
buf->retrieve(len);
}
/*未达到完整的一条消息*/
else
{
break;
}
}
}
// FIXME: TcpConnectionPtr
/*编码函数*/
void send(muduo::net::TcpConnection* conn,
const muduo::StringPiece& message)
{
muduo::net::Buffer buf;
buf.append(message.data(), message.size());
int32_t len = static_cast(message.size());
int32_t be32 = muduo::net::sockets::hostToNetwork32(len);
buf.prepend(&be32, sizeof be32);
/*编完码后,发送出去*/
conn->send(&buf);
}
private:
StringMessageCallback messageCallback_;
const static size_t kHeaderLen = sizeof(int32_t);
};
#include "codec.h"
#include
#include
#include
#include
#include
#include
#include
using namespace muduo;
using namespace muduo::net;
/*
Program :这是一个单线程的程序,不需要mutex
*/
class ChatServer : boost::noncopyable
{
public:
ChatServer(EventLoop* loop,
const InetAddress& listenAddr)
: loop_(loop),
server_(loop, listenAddr, "ChatServer"),
/*消息编解码初始化,邋onString錗essage()为编解码完后的回调函数*/
codec_(boost::bind(&ChatServer::onStringMessage, this, _1, _2, _3))
{
server_.setConnectionCallback(
boost::bind(&ChatServer::onConnection, this, _1));
/*消息达到时的回调函数*/
server_.setMessageCallback(
boost::bind(&LengthHeaderCodec::onMessage, &codec_, _1, _2, _3));
}
void start()
{
server_.start();
}
private:
/*只有一个IO线程,因而这里的connection_不需要mutex保护*/
/*连接到达对等方对开连接时的回调函数*/
void onConnection(const TcpConnectionPtr& conn)
{
LOG_INFO << conn->localAddress().toIpPort() << " -> "
<< conn->peerAddress().toIpPort() << " is "
<< (conn->connected() ? "UP" : "DOWN");
/*如果已经连接了,回调*/
if (conn->connected())
{
connections_.insert(conn);
}
/*连接断开*/
else
{
connections_.erase(conn);
}
}
/*编解码class 的回调函数*/
/*转发消息给所有客户端*/
void onStringMessage(const TcpConnectionPtr&,
const string& message,
Timestamp)
{
/*只有一个IO线程,因而这里的connections_不需要mutex保护;
转发信息给所有客户端
*/
for (ConnectionList::iterator it = connections_.begin();
it != connections_.end();
++it)
{
codec_.send(get_pointer(*it), message);
}
}
typedef std::set ConnectionList;
EventLoop* loop_;
TcpServer server_;
/*消息编解码class*/
LengthHeaderCodec codec_;
/*连接列表*/
ConnectionList connections_;
};
int main(int argc, char* argv[])
{
LOG_INFO << "pid = " << getpid();
if (argc > 1)
{
EventLoop loop;
uint16_t port = static_cast(atoi(argv[1]));
InetAddress serverAddr(port);
ChatServer server(&loop, serverAddr);
server.start();
loop.loop();
}
else
{
printf("Usage: %s port\n", argv[0]);
}
}
#include "codec.h"
#include
#include
#include
#include
#include
#include
#include
using namespace muduo;
using namespace muduo::net;
/*这是一个典型的多线程聊天程序,multipleReactor 模型*/
class ChatServer : boost::noncopyable
{
public:
ChatServer(EventLoop* loop,
const InetAddress& listenAddr)
: loop_(loop),
server_(loop, listenAddr, "ChatServer"),
codec_(boost::bind(&ChatServer::onStringMessage, this, _1, _2, _3))
{
server_.setConnectionCallback(
boost::bind(&ChatServer::onConnection, this, _1));
server_.setMessageCallback(
boost::bind(&LengthHeaderCodec::onMessage, &codec_, _1, _2, _3));
}
void setThreadNum(int numThreads)
{
server_.setThreadNum(numThreads);
}
void start()
{
server_.start();
}
private:
void onConnection(const TcpConnectionPtr& conn)
{
LOG_INFO << conn->localAddress().toIpPort() << " -> "
<< conn->peerAddress().toIpPort() << " is "
<< (conn->connected() ? "UP" : "DOWN");
MutexLockGuard lock(mutex_);
if (conn->connected())
{
connections_.insert(conn);
}
else
{
connections_.erase(conn);
}
}
void onStringMessage(const TcpConnectionPtr&,
const string& message,
Timestamp)
{
/*多线程需要保护连接列表*/
MutexLockGuard lock(mutex_);
for (ConnectionList::iterator it = connections_.begin();
it != connections_.end();
++it)
{
codec_.send(get_pointer(*it), message);
}
}
typedef std::set ConnectionList;
EventLoop* loop_;
TcpServer server_;
LengthHeaderCodec codec_;
MutexLock mutex_;
ConnectionList connections_;
};
int main(int argc, char* argv[])
{
LOG_INFO << "pid = " << getpid();
if (argc > 1)
{
EventLoop loop;
uint16_t port = static_cast(atoi(argv[1]));
InetAddress serverAddr(port);
ChatServer server(&loop, serverAddr);
if (argc > 2)
{
server.setThreadNum(atoi(argv[2]));
}
server.start();
loop.loop();
}
else
{
printf("Usage: %s port [thread_num]\n", argv[0]);
}
}
#include "codec.h"
#include
#include
#include
#include
#include
#include
#include
#include
using namespace muduo;
using namespace muduo::net;
/*这是一个典型的多线程聊天程序multipleReactor 模型,
但是这里使用了一些编程技巧,达到一些优化*/
class ChatServer : boost::noncopyable
{
public:
ChatServer(EventLoop* loop,
const InetAddress& listenAddr)
: loop_(loop),
server_(loop, listenAddr, "ChatServer"),//loop : acceptor loop
codec_(boost::bind(&ChatServer::onStringMessage, this, _1, _2, _3)),
connections_(new ConnectionList)//初始化时,share_ptr的引用为1
{
server_.setConnectionCallback(
boost::bind(&ChatServer::onConnection, this, _1));
server_.setMessageCallback(
boost::bind(&LengthHeaderCodec::onMessage, &codec_, _1, _2, _3));
}
void setThreadNum(int numThreads)
{
server_.setThreadNum(numThreads);
}
void start()
{
server_.start();
}
private:
void onConnection(const TcpConnectionPtr& conn)
{
LOG_INFO << conn->localAddress().toIpPort() << " -> "
<< conn->peerAddress().toIpPort() << " is "
<< (conn->connected() ? "UP" : "DOWN");
MutexLockGuard lock(mutex_);
if (!connections_.unique())//说明引用计数大于1
{//new ConnectionList(*connections_) 这段代码拷贝了一份ConnectionList
//connections_原来的引用计数减1,而connections_现在的引用计数
// 等于1
connections_.reset(new ConnectionList(*connections_));
}
//所以这里断言才会成功
assert(connections_.unique());
/*在复本上修改,不会影响读者,所以读者在遍历列表的时候,
不需要mutex保护*/
if (conn->connected())
{
connections_->insert(conn);
}
else
{
connections_->erase(conn);
}
}
typedef std::set ConnectionList;
typedef boost::shared_ptr ConnectionListPtr;
/*读操作*/
void onStringMessage(const TcpConnectionPtr&,
const string& message,
Timestamp)
{
/*引用计数加1,mutex保护的临时区大大缩短*/
ConnectionListPtr connections = getConnectionList();;//栈上变量
/*可能大家会有疑问,不受mutex保护,写者更改了连接列表怎么办�*
实际上,写者是在另一个副本上修改,所以无需担心*/
for (ConnectionList::iterator it = connections->begin();
it != connections->end();
++it)
{
/*这里也是无法减少第一个和第二个连接发送所需的时间,
因为他们都是在同步发送的,就是所要等到转发完一条消息到
一个connection后,然后才能转发下一个连接connection.
实质就是调用这个函数的IO负责转发*/
codec_.send(get_pointer(*it), message);
}
/*这个断言不一定成立
assert(!connections.uniquer())。
这是由于Onconnection()---->connections_.reset(new ConnectionList(*connections_));*/
/*当connection这个栈上的变量销毁的时候,引用计数减1*/
}
ConnectionListPtr getConnectionList()
{
/*保护区域变小了<>*/
MutexLockGuard lock(mutex_);
return connections_;
}
EventLoop* loop_;
TcpServer server_; /*tcpserver服务器*/
LengthHeaderCodec codec_;
MutexLock mutex_;
ConnectionListPtr connections_;
};
int main(int argc, char* argv[])
{
LOG_INFO << "pid = " << getpid();
if (argc > 1)
{
EventLoop loop;
uint16_t port = static_cast(atoi(argv[1]));
InetAddress serverAddr(port);
ChatServer server(&loop, serverAddr);
if (argc > 2)
{
/*IO线程个数*/
server.setThreadNum(atoi(argv[2]));
}
server.start();
loop.loop();
}
else
{
printf("Usage: %s port [thread_num]\n", argv[0]);
}
}
#include "codec.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
using namespace muduo;
using namespace muduo::net;
/*这个主要是针对第二个进行改正的,*/
class ChatServer : boost::noncopyable
{
public:
ChatServer(EventLoop* loop,
const InetAddress& listenAddr)
: loop_(loop),
server_(loop, listenAddr, "ChatServer"),
codec_(boost::bind(&ChatServer::onStringMessage, this, _1, _2, _3))
{
server_.setConnectionCallback(
boost::bind(&ChatServer::onConnection, this, _1));
server_.setMessageCallback(
boost::bind(&LengthHeaderCodec::onMessage, &codec_, _1, _2, _3));
}
void setThreadNum(int numThreads)
{
/*设置sub IO线程池的大小*/
server_.setThreadNum(numThreads);
}
void start()
{/*设置线程的初始化函数*/
server_.setThreadInitCallback(boost::bind(&ChatServer::threadInit, this, _1));
server_.start();
}
private:
void onConnection(const TcpConnectionPtr& conn)
{
LOG_INFO << conn->localAddress().toIpPort() << " -> "
<< conn->peerAddress().toIpPort() << " is "
<< (conn->connected() ? "UP" : "DOWN");
if (conn->connected())
{
connections_.instance().insert(conn);
}
else
{
connections_.instance().erase(conn);
}
cout<<"connection adress :"<<&connections_<<"\t"<<"connection size :"<::iterator it = loops_.begin();
it != loops_.end();
++it)
{/*
1.让对应的IO线程来执行distributeMessage
2.distributeMessage放到IO线程队列中执行,因此,这里的mutex_锁竞争大大减小
3.distributeMesssge 不受mutex_保护
*/
(*it)->queueInLoop(f);
}
LOG_DEBUG;
}
typedef std::set ConnectionList;
void distributeMessage(const string& message)
{
LOG_DEBUG << "begin";
// connectionList_是线程局部变量
for (ConnectionList::iterator it = connections_.instance().begin();
it != connections_.instance().end();
++it)
{
codec_.send(get_pointer(*it), message);
}
LOG_DEBUG << "end";
}
/*IO线程执行前时的前回调函数*/
void threadInit(EventLoop* loop)
{
assert(connections_.pointer() == NULL);
/*实例化一个对象*/
connections_.instance();
assert(connections_.pointer() != NULL);
MutexLockGuard lock(mutex_);
loops_.insert(loop);
}
EventLoop* loop_; //loop_传递给server_
TcpServer server_;
LengthHeaderCodec codec_;
/*线程局部单例变量,每个线程都有一个connections_(连接列表)实例*/
ThreadLocalSingleton connections_;
MutexLock mutex_;
std::set loops_; //eventLoop列表
};
int main(int argc, char* argv[])
{
LOG_INFO << "pid = " << getpid();
if (argc > 1)
{
EventLoop loop;//acceptor loop
uint16_t port = static_cast(atoi(argv[1]));
InetAddress serverAddr(port);
ChatServer server(&loop, serverAddr);
if (argc > 2)
{
server.setThreadNum(atoi(argv[2])); //多个subIO线程
}
server.start();
loop.loop();
}
else
{
printf("Usage: %s port [thread_num]\n", argv[0]);
}
}