在boost用asio设计TCP服务器的关键技术中,设计了tcp连接的管理类channel和处理io_service中多线程的工作方法,针对单个tcp连接的处理类session,核心的方法是接收和发送数据。
其中async_read_until与boost::asio::streambuf的组合,据说是可用处理粘包的问题,我打算下一个版本再用,这里用到的接收数据的buffer基于char来构造的。
重点是session类的设计,头文件如下
#pragma once
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include "CircledBuffer.h"
//::public boost::asio::ip::tcp::socket
class channel;
class session;
typedef boost::shared_ptr<session> session_ptr;
class session: public boost::enable_shared_from_this<session>
{
public:
session(boost::asio::io_service &io_service,channel& _channel);
~session();
bool started_;
bool started() const;
void start();
void stop();
void start_send(char* cmd);
boost::asio::ip::tcp::socket &socket();
std::string getRemoteAddr();
int getRemotePort();
int getSessionId(){return sessionId;};
int heartbeat_count_;
static long LastSessionId;
private:
boost::asio::ip::tcp::socket socket_;
boost::asio::streambuf sbuf_;
//TODO use circleBuffer instead of them.
enum{max_msg=256};
unsigned char write_buffer[max_msg];
///
CircledBuffer readBuffer;
unsigned int readIndex;
int sessionId;
channel& p_channel;
boost::asio::strand strand_;
boost::asio::deadline_timer sessionAliveTimer;
/
//not understand why to use this two.
// std::size_t check_frame(const boost::system::error_code &ec, std::size_t bytes_transferred);//check data
// void parse_frame(const boost::system::error_code &ec, std::size_t bytes_transferred);//parse data.
void receive_handler(const boost::system::error_code &ec, std::size_t bytes_transferred);
void send_handler(const boost::system::error_code &ec);
void processCmd(std::size_t bytes_transferred);
void _processCmd(std::size_t bytes_transferred);
void _start();
void _stop();
void _start_send(char* cmd);
void _setTimer(int sec);
void _checkHeartBeat(const boost::system::error_code& /*e*/, boost::asio::deadline_timer* t);
private:
session(const session& other);
const session operator=( const session& rhs);
};
这里的CmdBuffer的数组CircledBuffer就是我基于char构建的tcp命令的接收缓冲区,首先session进入开始状态。
void session::_start() {
started_=true;
CmdBuffer* cmd;
std::cout<<getRemoteAddr()<<":"<<getRemotePort()<<"is connected \n";
cmd= readBuffer.GetLast();
readBuffer.MoveNext();
_setTimer(2);
socket_.async_receive(boost::asio::buffer(cmd->data),
strand_.wrap( boost::bind(&session::receive_handler,
shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred)));
}
session的开始状态,是以receive开始的。收到数据后,调用receive_handler的函数。
void session::receive_handler(const boost::system::error_code &ec, std::size_t bytes_transferred)
{
if (ec)
{
if (ec.value() == boost::asio::error::eof)
{
stop();
}
return;
}
if (!started())return;
processCmd(bytes_transferred);
CmdBuffer* cmd;
cmd= readBuffer.GetLast();
readBuffer.MoveNext();
socket_.async_receive(boost::asio::buffer(cmd->data),
strand_.wrap( boost::bind(&session::receive_handler,
shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred)));
}
receive_handler是对收到的数据进行处理,调用processCmd来进行,processCmd仅仅做了printf,由于有buffer缓存,可以根据分隔符来处理命令,处理完数据后,继续接收数据,继续调用async_receive的方法,构成了回调的循环结构。
对于数据的发送,是通过上层函数发起的,session的上层接口是channel,只需要留出发送数据的接口就可以了。
void session::_start_send(char* cmd)
{
char* wBufferPtr = (char*)write_buffer;
memset(wBufferPtr,0,max_msg);
sprintf(wBufferPtr,"%s\r\n",cmd);
socket_.async_send(boost::asio::buffer(write_buffer,strlen(wBufferPtr)),
strand_.wrap( boost::bind(&session::send_handler,
shared_from_this(),
boost::asio::placeholders::error)));
}
对于send_handler,是发送之后做的处理,这里可以为空就可以了。
最后,介绍一下boost在网络编程中,需要用到的endpoint的类,这个类的作用相当于sockaddr_in,提供一些端口,ip等信息。
获取远端的ip和端口的方法:
boost::asio::ip::tcp::endpoint remoteEp = socket_.remote_endpoint();
ip = remoteEp.address().to_string();
port = remoteEp.port()
一会儿,我再整理一下tcp服务器中,比较实用的串行化工具strand和异步定时器。