boost学习之asio—chat

最近,工作从C切换到了C++,项目中大量用到Boost,就开始进行相关的boost学习,下面是参考boost example实现的chat。基于asio的tcp。

Talk is cheap. Show me the code.


1、简单的消息封装格式(Header+Body),Header为4个字节,为body的长度;Body可为任意字符串。

//
//  JfyChatMessage.hpp
//  TestRunner
//
//  Created by zhaipillary on 16/12/21.
//  Copyright © 2016年 zhaipillary. All rights reserved.
//

#ifndef JfyChatMessage_h
#define JfyChatMessage_h

#include <cstdio>
#include <cstdlib>
#include <cstring>

//移除文件目录
#ifdef WIN32
#define TrimFilePath(x) strrchr(x,'\\')?strrchr(x,'\\')+1:x
#else //*nix
#define TrimFilePath(x) strrchr(x,'/')?strrchr(x,'/')+1:x
#endif

//简单日志封装,打印到屏幕上
#define LogDebug(fmt, ...)   \
printf("[DEBUG] [%s(%d)] : " fmt"\n",TrimFilePath(__FILE__),__LINE__,##__VA_ARGS__)
#define LogInfo(fmt, ...)    \
printf("[INFO ] [%s(%d)] : " fmt"\n",TrimFilePath(__FILE__),__LINE__,##__VA_ARGS__)
#define LogWarn(fmt, ...)    \
printf("[WARN ] [%s(%d)] : " fmt"\n",TrimFilePath(__FILE__),__LINE__,##__VA_ARGS__)
#define LogError(fmt, ...)   \
printf("[ERROR] [%s(%d)] : " fmt"\n",TrimFilePath(__FILE__),__LINE__,##__VA_ARGS__)



class CChatMessage
{
public:
    enum { MSG_HEADER_LEN = 4 };
    enum { MSG_BODY_LEN_MAX = 512 };
    
public:
    CChatMessage()
    {
        Clear();
    }
    
    const char* GetData() const
    {
        return m_szData;
    }
    
    char* GetData()
    {
        return m_szData;
    }
    
    size_t Length() const
    {
        return (MSG_HEADER_LEN + m_nBodyLen);
    }
    
    const char* GetBody() const
    {
        return m_szData + MSG_HEADER_LEN;
    }
    
    char* GetBody()
    {
        return m_szData + MSG_HEADER_LEN;
    }
    
    size_t BodyLength() const
    {
        return m_nBodyLen;
    }
    
    void BodyLength(size_t nNewLen)
    {
        m_nBodyLen = (nNewLen > MSG_BODY_LEN_MAX) ? MSG_BODY_LEN_MAX : nNewLen;
    }
    
    void Clear()
    {
        m_nBodyLen = 0;
        memset(m_szData,0,(MSG_HEADER_LEN+MSG_BODY_LEN_MAX));
    }
    
    bool DecodeHeader()
    {
        char szHeader[MSG_HEADER_LEN+1] = {0};
        std::strncat(szHeader,m_szData,MSG_HEADER_LEN);
        
        m_nBodyLen = std::atoi(szHeader);
        if(m_nBodyLen > MSG_BODY_LEN_MAX)
        {
            LogError("%s,BodyLen[%lu]>MaxLen[%d]",__FUNCTION__,m_nBodyLen,MSG_BODY_LEN_MAX);
            m_nBodyLen = 0;
            return false;
        }
        
        return true;
    }
    
    void EncodeHeader()
    {
        char szHeader[MSG_HEADER_LEN+1] = {0};
        std::sprintf(szHeader,"%4d",static_cast<int>(m_nBodyLen));
        std::memcpy(m_szData,szHeader,MSG_HEADER_LEN);
    }
    
private:
    char m_szData[MSG_HEADER_LEN+MSG_BODY_LEN_MAX];
    size_t m_nBodyLen;

};


#endif /* JfyChatMessage_h */
 


2、Client端代码

//
//  JfyChatClient.hpp
//  TestRunner
//
//  Created by zhaipillary on 16/12/21.
//  Copyright © 2016年 zhaipillary. All rights reserved.
//

#ifndef JfyChatClient_h
#define JfyChatClient_h


#include "JfyChatMessage.hpp"

#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/thread/thread.hpp>

#include <deque>
#include <iostream>

#include <cstdlib>

using namespace std;

using boost::asio::ip::tcp;

typedef deque<CChatMessage> ChatMsgQue;

class CChatClient
{
public:
    CChatClient(boost::asio::io_service& ios,tcp::endpoint ep)
    : m_ios(ios),m_socket(ios)
    {
        m_socket.async_connect(
                               ep,
                               boost::bind(&CChatClient::HandlerConnect,
                                           this,
                                           boost::asio::placeholders::error) );
    }
    
    void Write(const CChatMessage& msg)
    {
        LogInfo("send msg[%lu]:%s",msg.BodyLength(),msg.GetBody() );
        
        m_ios.post(boost::bind(&CChatClient::DoWrite,this,msg));
    }
    
    void Close()
    {
        m_ios.post(boost::bind(&CChatClient::DoClose,this) );
    }
    
private:
    void HandlerConnect(const boost::system::error_code& ec)
    {
        if(!ec)
        {
            LogInfo("connect server:[%s:%d]",
                    m_socket.remote_endpoint().address().to_string().c_str(),
                    m_socket.remote_endpoint().port() );
            
            boost::asio::async_read(m_socket,
                                    boost::asio::buffer(m_msgRead.GetData(),
                                                        CChatMessage::MSG_HEADER_LEN),
                                    boost::bind(&CChatClient::HandlerRead,
                                                this,
                                                boost::asio::placeholders::error) );
        }
        else
        {
            LogError("%s errorCode[%d]:%s",__FUNCTION__,ec.value(),ec.message().c_str());
        }
        
    }
    
    void HandlerRead(const boost::system::error_code& ec)
    {
        if (!ec && m_msgRead.DecodeHeader() )
        {
            boost::asio::async_read(m_socket,
                                    boost::asio::buffer(m_msgRead.GetBody(),
                                                        m_msgRead.BodyLength()),
                                    boost::bind(&CChatClient::HandlerReadBody,
                                                this,
                                                boost::asio::placeholders::error) );
        }
        else
        {
            LogError("%s errorCode[%d]:%s",__FUNCTION__,ec.value(),ec.message().c_str());
            DoClose();
        }
        
    }
    
    void HandlerReadBody(const boost::system::error_code& ec)
    {
        if(!ec)
        {
            LogInfo("recv msg[%lu]:%s",m_msgRead.BodyLength(),m_msgRead.GetBody() );
            
            m_msgRead.Clear();
            
            boost::asio::async_read(m_socket,
                                    boost::asio::buffer(m_msgRead.GetData(),
                                                        CChatMessage::MSG_HEADER_LEN),
                                    boost::bind(&CChatClient::HandlerRead,
                                                this,
                                                boost::asio::placeholders::error) );
        }
        else
        {
            LogError("%s errorCode[%d]:%s",__FUNCTION__,ec.value(),ec.message().c_str());
            DoClose();
        }
        
    }
    
    void HandlerWrite(const boost::system::error_code& ec)
    {
        if (!ec)
        {
            m_msgWrite.pop_front();
            if (!m_msgWrite.empty())
            {
                boost::asio::async_write(m_socket,
                                         boost::asio::buffer(m_msgWrite.front().GetData(),
                                                             m_msgWrite.front().Length() ),
                                         boost::bind(&CChatClient::HandlerWrite,
                                                     this,
                                                     boost::asio::placeholders::error) );
            }
        }
        else
        {
            LogError("%s errorCode[%d]:%s",__FUNCTION__,ec.value(),ec.message().c_str());
            DoClose();
        }
    }
    
    void DoWrite(CChatMessage msg)
    {
        bool bWrite = !m_msgWrite.empty();
        m_msgWrite.push_back(msg);
        
        if(!bWrite)
        {
            boost::asio::async_write(m_socket,
                                     boost::asio::buffer(m_msgWrite.front().GetData(),
                                                         m_msgWrite.front().Length() ),
                                     boost::bind(&CChatClient::HandlerWrite,
                                                 this,
                                                 boost::asio::placeholders::error) );
        }
    }
    
    void DoClose()
    {
        LogInfo("chat client do close");
        m_socket.close();
    }
    
private:
    boost::asio::io_service& m_ios;
    tcp::socket m_socket;
    CChatMessage m_msgRead;
    ChatMsgQue m_msgWrite;
};


int ChatClientRun(string strIp,unsigned short nPort)
{
    LogInfo("Begin %s",__FUNCTION__);
    try
    {
        boost::asio::io_service ios;
        tcp::endpoint ep(boost::asio::ip::address::from_string(strIp),nPort);
        
        CChatClient client(ios,ep);
        boost::thread thr(boost::bind(&boost::asio::io_service::run,&ios));
        
        char szText[CChatMessage::MSG_BODY_LEN_MAX+1] = {0};
        while (cin.getline(szText,CChatMessage::MSG_BODY_LEN_MAX) )
        {
            CChatMessage msg;
            msg.BodyLength(strlen(szText));
            memcpy(msg.GetBody(),szText,msg.BodyLength());
            msg.EncodeHeader();
            client.Write(msg);
            memset(szText,0,sizeof(szText));
        }
        
        client.Close();
        thr.join();
    }
    catch (std::exception& e)
    {
        LogError("%s Exception :%s",__FUNCTION__,e.what());
        return -1;
    }
    
    
    return 0;
}


#endif /* JfyChatClient_h */


3、Server端代码

//
//  JfyChatServer.hpp
//  TestRunner
//
//  Created by zhaipillary on 16/12/21.
//  Copyright © 2016年 zhaipillary. All rights reserved.
//

#ifndef JfyChatServer_h
#define JfyChatServer_h

#include "JfyChatMessage.hpp"

#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>

#include <set>
#include <list>
#include <deque>
#include <algorithm>
#include <iostream>

#include <cstdlib>

using namespace std;


using boost::asio::ip::tcp;

class CChatParticipant;
class CChatSession;
typedef deque<CChatMessage> ChatMsgQue;

typedef boost::shared_ptr<CChatParticipant> ChatPartPtr;
typedef boost::shared_ptr<CChatSession> ChatSessionPtr;

class CChatParticipant
{
public:
    virtual ~CChatParticipant() {};
    virtual void Deliver(const CChatMessage& msg) = 0;
};


class CChatRoom
{
public:
    void Join(ChatPartPtr part)
    {
        LogInfo("part[%p] join room",&part);
        
        m_setParts.insert(part);
        for_each(m_queMsg.begin(), m_queMsg.end(),
                 boost::bind(&CChatParticipant::Deliver,part,_1) );
    }
    
    void Leave(ChatPartPtr part)
    {
        LogInfo("part[%p] leave room",&part);
        m_setParts.erase(part);
    }
    
    void Deliver(const CChatMessage& msg)
    {
        LogInfo("ChatRoom Deliver msg[%lu]:%s",msg.BodyLength(),msg.GetBody());
        
        m_queMsg.push_back(msg);
        while(m_queMsg.size() > MAX_RECENT_MSG_NUM )
        {
            m_queMsg.pop_front();
        }
        
        for_each(m_setParts.begin(), m_setParts.end(),
                 boost::bind(&CChatParticipant::Deliver,_1,boost::ref(msg) ) );
    }
    
private:
    set<ChatPartPtr> m_setParts;
    ChatMsgQue m_queMsg;
    
private:
    enum { MAX_RECENT_MSG_NUM = 100 };
};

class CChatSession
: public CChatParticipant,public boost::enable_shared_from_this<CChatSession>
{
    
public:
    CChatSession(boost::asio::io_service& ios,CChatRoom& room)
    : m_socket(ios),m_room(room)
    {
    }
    
    tcp::socket& Socket()
    {
        return m_socket;
    }
    
    void Start()
    {
        m_room.Join(shared_from_this());
        
        boost::asio::async_read(m_socket,
                                boost::asio::buffer(m_msgRead.GetData(),
                                                    CChatMessage::MSG_HEADER_LEN),
                                boost::bind(&CChatSession::HandlerRead,
                                            shared_from_this(),
                                            boost::asio::placeholders::error));
    }
    
    void Deliver(const CChatMessage& msg)
    {
        bool bWrite = !m_msgWrite.empty();
        m_msgWrite.push_back(msg);
        
        if(!bWrite)
        {
            boost::asio::async_write(m_socket,
                                     boost::asio::buffer(m_msgWrite.front().GetData(),
                                                         m_msgWrite.front().Length()),
                                     boost::bind(&CChatSession::HandlerWrite,
                                                 shared_from_this(),
                                                 boost::asio::placeholders::error) );
        }
    }
    
private:
    void HandlerRead(const boost::system::error_code& ec)
    {
        if(!ec && m_msgRead.DecodeHeader())
        {
            boost::asio::async_read(m_socket,
                                     boost::asio::buffer(m_msgRead.GetBody(),
                                                         m_msgRead.BodyLength()),
                                     boost::bind(&CChatSession::HandlerReadBody,
                                                 shared_from_this(),
                                                 boost::asio::placeholders::error) );
        }
        else
        {
            LogError("%s errorCode[%d]:%s",__FUNCTION__,ec.value(),ec.message().c_str());
            m_room.Leave(shared_from_this());
        }
    }
    
    void HandlerReadBody(const boost::system::error_code& ec)
    {
        if (!ec)
        {
            LogInfo("receive client:[%s:%d],msg[%lu]:%s",
                    m_socket.remote_endpoint().address().to_string().c_str(),
                    m_socket.remote_endpoint().port(),
                    m_msgRead.BodyLength(),
                    m_msgRead.GetBody() );
            
            m_room.Deliver(m_msgRead);
            boost::asio::async_read(m_socket,
                                    boost::asio::buffer(m_msgRead.GetData(),
                                                        CChatMessage::MSG_HEADER_LEN),
                                    boost::bind(&CChatSession::HandlerRead,
                                                shared_from_this(),
                                                boost::asio::placeholders::error));
        }
        else
        {
            LogError("%s errorCode[%d]:%s",__FUNCTION__,ec.value(),ec.message().c_str());
            m_room.Leave(shared_from_this());
        }
        
    }
    
    void HandlerWrite(const boost::system::error_code& ec)
    {
        if(!ec)
        {
            m_msgWrite.pop_front();
            if (!m_msgWrite.empty())
            {
                boost::asio::async_write(m_socket,
                                         boost::asio::buffer(m_msgWrite.front().GetData(),
                                                             m_msgWrite.front().Length()),
                                         boost::bind(&CChatSession::HandlerWrite,
                                                     shared_from_this(),
                                                     boost::asio::placeholders::error));
            }
        }
        else
        {
            m_room.Leave(shared_from_this());
            LogError("%s errorCode[%d]:%s",__FUNCTION__,ec.value(),ec.message().c_str());
        }
    }
    
private:
    tcp::socket m_socket;
    CChatRoom& m_room;
    CChatMessage m_msgRead;
    ChatMsgQue m_msgWrite;
};

class CChatServer
{
public:
    CChatServer(boost::asio::io_service& ios,const tcp::endpoint& ep)
    :m_ios(ios),m_acceptor(ios,ep)
    {
        Start();
    }
    
private:
    void Start()
    {
        ChatSessionPtr session(new CChatSession(m_ios,m_room));
        
        m_acceptor.async_accept(session->Socket(),
                                boost::bind(&CChatServer::HandlerAccept,
                                            this,
                                            session,
                                            boost::asio::placeholders::error));
    }
    
    void HandlerAccept(ChatSessionPtr session, const boost::system::error_code& ec)
    {
        if (!ec)
        {
            LogInfo("accept client:[%s:%d]",
                    session->Socket().remote_endpoint().address().to_string().c_str(),
                    session->Socket().remote_endpoint().port() );
            
            session->Start();
        }
        else
        {
            LogError("%s errorCode[%d]:%s",__FUNCTION__,ec.value(),ec.message().c_str());
        }
        
        Start();
    }
    
private:
    boost::asio::io_service& m_ios;
    tcp::acceptor m_acceptor;
    CChatRoom m_room;
};


int ChatServerRun(unsigned short nPort)
{
    LogInfo("Begin %s",__FUNCTION__);
    try
    {
        boost::asio::io_service ios;
        tcp::endpoint ep(tcp::v4(),nPort);
        CChatServer server(ios,ep);
        
        ios.run();
        
    }
    catch (std::exception& e)
    {
        LogError("%s Exception :%s",__FUNCTION__,e.what());
        return -1;
    }
    
    return 0;
}


#endif /* JfyChatServer_h */

4、Main函数代码

//
//  main.cpp
//  TestRunner
//
//  Created by zhaipillary on 16/12/21.
//  Copyright © 2016年 zhaipillary. All rights reserved.
//

//#include "JfyLog.h"

#include "JfyChatServer.hpp"
#include "JfyChatClient.hpp"



int main(int argc, char* argv[])
{
    LogInfo("Begin working !");
    

    if (2 == argc && 0 == strncmp("client",argv[1],6))
    {
        ChatClientRun("127.0.0.1", 12345);
    }
    else
    {
        ChatServerRun(12345);
    }


    LogInfo("End %s",__FUNCTION__);
    return 0;
}


评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值