最近,工作从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;
}