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]); }}
- muduo实现简单了聊天功能(44-45)
- muduo实现聊天服务器
- XMPP实现简单聊天功能
- 用tcl实现的简单聊天功能
- struts实现栈内简单聊天功能
- 用UDP实现简单的聊天功能
- 通过WCF实现简单的聊天功能
- socket 编程 TCP 实现简单聊天功能
- python socket 简单实现聊天功能
- 实现简单多人聊天并@功能
- 实现简单的聊天功能部分Vue
- SpringBoot WebSocket 实现简单的聊天功能
- js实现简单聊天功能,非真正聊天
- 实现最简单的网络聊天功能(服务器版)
- 实现最简单的网络聊天功能(客户端版)
- javaweb webSocket 实现简单的点对点聊天功能
- javaweb webSocket 实现简单的点对点聊天功能
- java TCP/IP实现简单的多人聊天功能
- C语言中跳出多重循环
- java学习:eclipse + Weblogic 12c + svn 集成开发环境搭建
- NYOJ-74-小学生算术-2013年08月16日00:44:54
- 动态规划之所有点对的最短路径问题(Floyd算法)
- C++调用系统终端执行命令,将输出保存到文件中
- muduo实现简单了聊天功能(44-45)
- SVN使用:用TortoiseSVN查看Log 无法显示最新的版本和Log信息问题
- OpenGL学习入门之VS2010环境配置
- NYOJ-28-大数阶乘-2013年08月16日09:27:05
- 限制服务器最大并发连接数(47)
- TCMalloc源码阅读(二)--线程局部缓存ClassSize分析
- 九 Django 1.5.4 Bootstrap
- NYOJ-103-A+B Problem II-2013年08月16日23:56:46
- Linux makefile 教程 非常详细,且易懂