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

聊天服务器(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]);
  }
}



  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值