muduo实现简单了聊天功能(44-45)

来源:互联网 发布:淘宝一百多的狩猎者 编辑:程序博客网 时间:2024/06/10 00:27

聊天服务器(MuduoManual.pdf P66)

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,该如何处理?既然要更新数据,肯定要加锁,如果这时候其他线程正在读,那么不能在原来的数据上修改,得创建一个副本,在副本上修改,修改完了再替换。如果没有用户在读,那么可以直接修改。 




code.h

#ifndef MUDUO_EXAMPLES_ASIO_CHAT_CODEC_H#define MUDUO_EXAMPLES_ASIO_CHAT_CODEC_H#include <muduo/base/Logging.h>#include <muduo/net/Buffer.h>#include <muduo/net/Endian.h>#include <muduo/net/TcpConnection.h>#include <boost/function.hpp>#include <boost/noncopyable.hpp>class LengthHeaderCodec : boost::noncopyable{ public:  typedef boost::function<void (const muduo::net::TcpConnectionPtr&,                                const muduo::string& message,                                muduo::Timestamp)> 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<const int32_t*>(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<int32_t>(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);};







examples/asio/chat/server.cc 单线程

#include "codec.h"#include <muduo/base/Logging.h>#include <muduo/base/Mutex.h>#include <muduo/net/EventLoop.h>#include <muduo/net/TcpServer.h>#include <boost/bind.hpp>#include <set>#include <stdio.h>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<TcpConnectionPtr> 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<uint16_t>(atoi(argv[1]));    InetAddress serverAddr(port);    ChatServer server(&loop, serverAddr);    server.start();    loop.loop();  }  else  {    printf("Usage: %s port\n", argv[0]);  }}





examples/asio/chat/server_threaded.cc,多线程TcpServer,并用mutex来保护共享数据

#include "codec.h"#include <muduo/base/Logging.h>#include <muduo/base/Mutex.h>#include <muduo/net/EventLoop.h>#include <muduo/net/TcpServer.h>#include <boost/bind.hpp>#include <set>#include <stdio.h>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<TcpConnectionPtr> 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<uint16_t>(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]);  }}





examples/asio/chat/server_threaded_efficient.cc,借shared_ptr实现copy-on-write的手法来降低锁竞争

#include "codec.h"#include <muduo/base/Logging.h>#include <muduo/base/Mutex.h>#include <muduo/net/EventLoop.h>#include <muduo/net/TcpServer.h>#include <boost/bind.hpp>#include <boost/shared_ptr.hpp>#include <set>#include <stdio.h>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<TcpConnectionPtr> ConnectionList;  typedef boost::shared_ptr<ConnectionList> 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<uint16_t>(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]);  }}






examples/asio/chat/server_threaded_highperformance.cc,采用thread local变量实现多线程高效转发

#include "codec.h"#include <muduo/base/Logging.h>#include <muduo/base/Mutex.h>#include <muduo/base/ThreadLocalSingleton.h>#include <muduo/net/EventLoop.h>#include <muduo/net/TcpServer.h>#include <boost/bind.hpp>#include <boost/shared_ptr.hpp>#include <set>#include <stdio.h>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 :"<<connections_.size() ;  }  void onStringMessage(const TcpConnectionPtr&,                       const string& message,                       Timestamp)  {  /*把消息"转发"作为IO线程的任务来处理*/    EventLoop::Functor f = boost::bind(&ChatServer::distributeMessage, this, message);    LOG_DEBUG;    /*转发消息给所有客户端,高效率(多线程来转发),转发到不同的IO线程,    */    MutexLockGuard lock(mutex_);    /*for 循环和f达到异步进行*/    for (std::set<EventLoop*>::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<TcpConnectionPtr> 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<ConnectionList> connections_;  MutexLock mutex_;  std::set<EventLoop*> loops_;        //eventLoop列表};int main(int argc, char* argv[]){  LOG_INFO << "pid = " << getpid();  if (argc > 1)  {    EventLoop loop;//acceptor loop    uint16_t port = static_cast<uint16_t>(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]);  }}



原创粉丝点击