您的位置:首页 > 其它

ZMQ源码分析(六)--编码器和解码器

2016-01-28 17:17 363 查看
zmq的编码器和解码器负责和stream_engine合作收发网络数据,zmtp3.0使用v2_decoder和v2_encoder进行收发数据,本文也只对该版本进行分析。

解码器

zmq中v1和v2解码器都继承自decoder_base_t,raw_decoder则直接继承自i_decoder:

template <typename T> class decoder_base_t : public i_decoder
{
public:
inline decoder_base_t (size_t bufsize_) :
next (NULL),
read_pos (NULL),
to_read (0),
bufsize (bufsize_)
{
buf = (unsigned char*) malloc (bufsize_);
alloc_assert (buf);
}

//  The destructor doesn't have to be virtual. It is mad virtual
//  just to keep ICC and code checking tools from complaining.
inline virtual ~decoder_base_t ()
{
free (buf);
}

//  Returns a buffer to be filled with binary data.
inline void get_buffer (unsigned char **data_, size_t *size_)
{
//  If we are expected to read large message, we'll opt for zero-
//  copy, i.e. we'll ask caller to fill the data directly to the
//  message. Note that subsequent read(s) are non-blocking, thus
//  each single read reads at most SO_RCVBUF bytes at once not
//  depending on how large is the chunk returned from here.
//  As a consequence, large messages being received won't block
//  other engines running in the same I/O thread for excessive
//  amounts of time.
if (to_read >= bufsize) {
*data_ = read_pos;
*size_ = to_read;
return;
}

*data_ = buf;
*size_ = bufsize;
}

//  Processes the data in the buffer previously allocated using
//  get_buffer function. size_ argument specifies nemuber of bytes
//  actually filled into the buffer. Function returns 1 when the
//  whole message was decoded or 0 when more data is required.
//  On error, -1 is returned and errno set accordingly.
//  Number of bytes processed is returned in byts_used_.
inline int decode (const unsigned char *data_, size_t size_,
size_t &bytes_used_)
{
bytes_used_ = 0;

//  In case of zero-copy simply adjust the pointers, no copying
//  is required. Also, run the state machine in case all the data
//  were processed.
if (data_ == read_pos) {
zmq_assert (size_ <= to_read);
read_pos += size_;
to_read -= size_;
bytes_used_ = size_;

while (!to_read) {
const int rc = (static_cast <T*> (this)->*next) ();
if (rc != 0)
return rc;
}
return 0;
}

while (bytes_used_ < size_) {
//  Copy the data from buffer to the message.
const size_t to_copy = std::min (to_read, size_ - bytes_used_);
memcpy (read_pos, data_ + bytes_used_, to_copy);
read_pos += to_copy;
to_read -= to_copy;
bytes_used_ += to_copy;
//  Try to get more space in the message to fill in.
//  If none is available, return.
while (to_read == 0) {
const int rc = (static_cast <T*> (this)->*next) ();
if (rc != 0)
return rc;
}
}

return 0;
}

protected:

//  Prototype of state machine action. Action should return false if
//  it is unable to push the data to the system.
typedef int (T::*step_t) ();

//  This function should be called from derived class to read data
//  from the buffer and schedule next state machine action.
inline void next_step (void *read_pos_, size_t to_read_, step_t next_)
{
read_pos = (unsigned char*) read_pos_;
to_read = to_read_;
next = next_;
}

private:

//  Next step. If set to NULL, it means that associated data stream
//  is dead. Note that there can be still data in the process in such
//  case.
step_t next;

//  Where to store the read data.
unsigned char *read_pos;

//  How much data to read before taking next step.
size_t to_read;

//  The duffer for data to decode.
size_t bufsize;
unsigned char *buf;

decoder_base_t (const decoder_base_t&);
const decoder_base_t &operator = (const decoder_base_t&);
};


解码器的next函数指针同样是一个状态机,每次调用状态机都会重置read_pos和to_read两个变量,表示下一步需要把数据读到什么位置以及需要读取的数据的大小。get_buffer方法主要是返回一个可以读取数据的缓存以及该缓存的大小。如果是小数据,则先使用解码器自带的缓存buf,该缓存的大小为bufsize。如果是大数据,则直接向next返回的read_pos中读取数据,这样可以避免一次数据拷贝。decode同样分为两种情况,如果是之前没有使用自带缓存,则直接移动指针即可。如果是小数据,则需要把数据从缓存中考入到read_pos位置。如果to_read为0,说明当前状态下的所有数据已经处理完毕,需要移动到下一个状态,调用next重置read_pos和to_read。

下面看一下v2_decoder_t的实现:

//  Decoder for ZMTP/2.x framing protocol. Converts data stream into messages.
class v2_decoder_t : public decoder_base_t <v2_decoder_t>
{
public:

v2_decoder_t (size_t bufsize_, int64_t maxmsgsize_);
virtual ~v2_decoder_t ();

//  i_decoder interface.
virtual msg_t *msg () { return &in_progress; }

private:

int flags_ready ();
int one_byte_size_ready ();
int eight_byte_size_ready ();
int message_ready ();

unsigned char tmpbuf [8];
unsigned char msg_flags;
msg_t in_progress;

const int64_t maxmsgsize;

v2_decoder_t (const v2_decoder_t&);
void operator = (const v2_decoder_t&);
};


v2_decoder_t有四个状态机方法分别对应四种状态,同时有一个8字节的缓存,in_progress是解码器正在处理的消息。解码器解析出来的msg都保存在这里。maxmsgsize是一个最大消息长度的阀值。下面看着四种状态的转换关系:

zmq::v2_decoder_t::v2_decoder_t (size_t bufsize_, int64_t maxmsgsize_) :
decoder_base_t <v2_decoder_t> (bufsize_),
msg_flags (0),
maxmsgsize (maxmsgsize_)
{
int rc = in_progress.init ();
errno_assert (rc == 0);

//  At the beginning, read one byte and go to flags_ready state.
next_step (tmpbuf, 1, &v2_decoder_t::flags_ready);
}

zmq::v2_decoder_t::~v2_decoder_t ()
{
int rc = in_progress.close ();
errno_assert (rc == 0);
}

int zmq::v2_decoder_t::flags_ready ()
{
msg_flags = 0;
if (tmpbuf [0] & v2_protocol_t::more_flag)
msg_flags |= msg_t::more;
if (tmpbuf [0] & v2_protocol_t::command_flag)
msg_flags |= msg_t::command;

//  The payload length is either one or eight bytes,
//  depending on whether the 'large' bit is set.
if (tmpbuf [0] & v2_protocol_t::large_flag)
next_step (tmpbuf, 8, &v2_decoder_t::eight_byte_size_ready);
else
next_step (tmpbuf, 1, &v2_decoder_t::one_byte_size_ready);

return 0;
}

int zmq::v2_decoder_t::one_byte_size_ready ()
{
//  Message size must not exceed the maximum allowed size.
if (maxmsgsize >= 0)
if (unlikely (tmpbuf [0] > static_cast <uint64_t> (maxmsgsize))) {
errno = EMSGSIZE;
return -1;
}

//  in_progress is initialised at this point so in theory we should
//  close it before calling zmq_msg_init_size, however, it's a 0-byte
//  message and thus we can treat it as uninitialised...
int rc = in_progress.init_size (tmpbuf [0]);
if (unlikely (rc)) {
errno_assert (errno == ENOMEM);
rc = in_progress.init ();
errno_assert (rc == 0);
errno = ENOMEM;
return -1;
}

in_progress.set_flags (msg_flags);
next_step (in_progress.data (), in_progress.size (),
&v2_decoder_t::message_ready);

return 0;
}

int zmq::v2_decoder_t::eight_byte_size_ready ()
{
//  The payload size is encoded as 64-bit unsigned integer.
//  The most significant byte comes first.
const uint64_t msg_size = get_uint64 (tmpbuf);

//  Message size must not exceed the maximum allowed size.
if (maxmsgsize >= 0)
if (unlikely (msg_size > static_cast <uint64_t> (maxmsgsize))) {
errno = EMSGSIZE;
return -1;
}

//  Message size must fit into size_t data type.
if (unlikely (msg_size != static_cast <size_t> (msg_size))) {
errno = EMSGSIZE;
return -1;
}

//  in_progress is initialised at this point so in theory we should
//  close it before calling init_size, however, it's a 0-byte
//  message and thus we can treat it as uninitialised.
int rc = in_progress.init_size (static_cast <size_t> (msg_size));
if (unlikely (rc)) {
errno_assert (errno == ENOMEM);
rc = in_progress.init ();
errno_assert (rc == 0);
errno = ENOMEM;
return -1;
}

in_progress.set_flags (msg_flags);
next_step (in_progress.data (), in_progress.size (),
&v2_decoder_t::message_ready);

return 0;
}

int zmq::v2_decoder_t::message_ready ()
{
//  Message is completely read. Signal this to the caller
//  and prepare to decode next message.
next_step (tmpbuf, 1, &v2_decoder_t::flags_ready);
return 1;
}


在构造函数中调用

next_step (tmpbuf, 1, &v2_decoder_t::flags_ready)


代表接下来想tmpbuf中读入一个字节的数据,下一个状态机状态是flags_ready方法。flags_ready中会分析这条数据是否为长消息,如果是说明接下来的八个字节是消息长度,如果不是说明截下来一个字节是消息长度。这是zmtp规定的数据格式。

if (tmpbuf [0] & v2_protocol_t::large_flag)
next_step (tmpbuf, 8, &v2_decoder_t::eight_byte_size_ready);
else
next_step (tmpbuf, 1, &v2_decoder_t::one_byte_size_ready);


以长消息为例,截下来向tmpbuf中读入8字节长度数据,读取之后进入到eight_byte_size_ready状态。eight_byte_size_ready中已经知道了消息的长度,则用该长度初始化in_progress的大小,下一个状态是

next_step (in_progress.data (), in_progress.size (),&v2_decoder_t::message_ready)


代表向in_progress读入之前得到的数据长度,下一个状态设置成message_ready。当调用message_ready时候说明一条完整的msg已经处理完成了。message_ready方法把状态及设置成初始状态来读取下一条msg。message_ready返回1表明一条完整数据已经读取,其他状态都返回0。

v2_decoder_t主要用于stream_engine中的in_event方法中

void zmq::stream_engine_t::in_event ()
{
zmq_assert (!io_error);

//  If still handshaking, receive and process the greeting message.
if (unlikely (handshaking))
if (!handshake ())
return;

zmq_assert (decoder);

//  If there has been an I/O error, stop polling.
if (input_stopped) {
rm_fd (handle);
io_error = true;
return;
}

//  If there's no data to process in the buffer...
if (!insize) {

//  Retrieve the buffer and read as much data as possible.
//  Note that buffer can be arbitrarily large. However, we assume
//  the underlying TCP layer has fixed buffer size and thus the
//  number of bytes read will be always limited.
size_t bufsize = 0;
decoder->get_buffer (&inpos, &bufsize);

const int rc = tcp_read (s, inpos, bufsize);
if (rc == 0) {
error (connection_error);
return;
}
if (rc == -1) {
if (errno != EAGAIN)
error (connection_error);
return;
}

//  Adjust input size
insize = static_cast <size_t> (rc);
}

int rc = 0;
size_t processed = 0;

while (insize > 0) {
rc = decoder->decode (inpos, insize, processed);
zmq_assert (processed <= insize);
inpos += processed;
insize -= processed;
if (rc == 0 || rc == -1)
break;
rc = (this->*process_msg) (decoder->msg ());
if (rc == -1)
break;
}

//  Tear down the connection if we have failed to decode input data
//  or the session has rejected the message.
if (rc == -1) {
if (errno != EAGAIN) {
error (protocol_error);
return;
}
input_stopped = true;
reset_pollin (handle);
}

session->flush ();
}


如果insize是0,则调用get_buffer,把inpos指向v2_decoder_t的缓存或者是直接指向v2_decoder_t中的in_progress(数据长度大于v2_decoder_t的缓存长度,默认是8192),然后调用tcp_read读入数据。while循环处理当前的读入的数据,如果独到一条完整的消息,则交给process_msg处理,如果剩下的数据不足一条msg,则跳出循环,等待下一次in_event的调用。出错的话则停止监听数据。

编码器

zmq中v1和v2编码器都继承自encoder_base_t,raw_encoder则直接继承自i_encoder:

template <typename T> class encoder_base_t : public i_encoder
{
public:

inline encoder_base_t (size_t bufsize_) :
bufsize (bufsize_),
in_progress (NULL)
{
buf = (unsigned char*) malloc (bufsize_);
alloc_assert (buf);
}

//  The destructor doesn't have to be virtual. It is made virtual
//  just to keep ICC and code checking tools from complaining.
inline virtual ~encoder_base_t ()
{
free (buf);
}

//  The function returns a batch of binary data. The data
//  are filled to a supplied buffer. If no buffer is supplied (data_
//  points to NULL) decoder object will provide buffer of its own.
inline size_t encode (unsigned char **data_, size_t size_)
{
unsigned char *buffer = !*data_ ? buf : *data_;
size_t buffersize = !*data_ ? bufsize : size_;

if (in_progress == NULL)
return 0;

size_t pos = 0;
while (pos < buffersize) {

//  If there are no more data to return, run the state machine.
//  If there are still no data, return what we already have
//  in the buffer.
if (!to_write) {
if (new_msg_flag) {
int rc = in_progress->close ();
errno_assert (rc == 0);
rc = in_progress->init ();
errno_assert (rc == 0);
in_progress = NULL;
break;
}
(static_cast <T*> (this)->*next) ();
}

//  If there are no data in the buffer yet and we are able to
//  fill whole buffer in a single go, let's use zero-copy.
//  There's no disadvantage to it as we cannot stuck multiple
//  messages into the buffer anyway. Note that subsequent
//  write(s) are non-blocking, thus each single write writes
//  at most SO_SNDBUF bytes at once not depending on how large
//  is the chunk returned from here.
//  As a consequence, large messages being sent won't block
//  other engines running in the same I/O thread for excessive
//  amounts of time.
if (!pos && !*data_ && to_write >= buffersize) {
*data_ = write_pos;
pos = to_write;
write_pos = NULL;
to_write = 0;
return pos;
}

//  Copy data to the buffer. If the buffer is full, return.
size_t to_copy = std::min (to_write, buffersize - pos);
memcpy (buffer + pos, write_pos, to_copy);
pos += to_copy;
write_pos += to_copy;
to_write -= to_copy;
}

*data_ = buffer;
return pos;
}

void load_msg (msg_t *msg_)
{
zmq_assert (in_progress == NULL);
in_progress = msg_;
(static_cast <T*> (this)->*next) ();
}

protected:

//  Prototype of state machine action.
typedef void (T::*step_t) ();

//  This function should be called from derived class to write the data
//  to the buffer and schedule next state machine action.
inline void next_step (void *write_pos_, size_t to_write_,
step_t next_, bool new_msg_flag_)
{
write_pos = (unsigned char*) write_pos_;
to_write = to_write_;
next = next_;
new_msg_flag = new_msg_flag_;
}

private:

//  Where to get the data to write from.
unsigned char *write_pos;

//  How much data to write before next step should be executed.
size_t to_write;

//  Next step. If set to NULL, it means that associated data stream
//  is dead.
step_t next;

bool new_msg_flag;

//  The buffer for encoded data.
size_t bufsize;
unsigned char *buf;

encoder_base_t (const encoder_base_t&);
void operator = (const encoder_base_t&);

protected:

msg_t *in_progress;


encoder_base_t比decoder_base_t逻辑稍微复杂一些,但也是使用状态机实现的。encoder_base_t最重要的是encode方法,在分析encode方法之前,先看一下encoder_base_t的使用方式,它主要使用在stream_engine的out_event中:

void zmq::stream_engine_t::out_event ()
{
zmq_assert (!io_error);

//  If write buffer is empty, try to read new data from the encoder.
if (!outsize) {

//  Even when we stop polling as soon as there is no
//  data to send, the poller may invoke out_event one
//  more time due to 'speculative write' optimisation.
if (unlikely (encoder == NULL)) {
zmq_assert (handshaking);
return;
}

outpos = NULL;
outsize = encoder->encode (&outpos, 0);

while (outsize < out_batch_size) {
if ((this->*next_msg) (&tx_msg) == -1)
break;
encoder->load_msg (&tx_msg);
unsigned char *bufptr = outpos + outsize;
size_t n = encoder->encode (&bufptr, out_batch_size - outsize);
zmq_assert (n > 0);
if (outpos == NULL)
outpos = bufptr;
outsize += n;
}

//  If there is no data to send, stop polling for output.
if (outsize == 0) {
output_stopped = true;
reset_pollout (handle);
return;
}
}

//  If there are any data to write in write buffer, write as much as
//  possible to the socket. Note that amount of data to write can be
//  arbitrarily large. However, we assume that underlying TCP layer has
//  limited transmission buffer and thus the actual number of bytes
//  written should be reasonably modest.
const int nbytes = tcp_write (s, outpos, outsize);

//  IO error has occurred. We stop waiting for output events.
//  The engine is not terminated until we detect input error;
//  this is necessary to prevent losing incoming messages.
if (nbytes == -1) {
reset_pollout (handle);
return;
}

outpos += nbytes;
outsize -= nbytes;

//  If we are still handshaking and there are no data
//  to send, stop polling for output.
if (unlikely (handshaking))
if (outsize == 0)
reset_pollout (handle);
}


每次调用该方法会先判断outsize是否为0,如果是0,说明之前的数据已经全部发送出去。if语句中首先调用

outpos = NULL;
outsize = encoder->encode (&outpos, 0);


将oupos指向encoder的缓存,然后不断从next_msg中读出需要发送的msg,之后调用encoder的load_msg将新的msg存入到encoder中,最后调用

size_t n = encoder->encode (&bufptr, out_batch_size - outsize);


将刚刚存入的msg写入缓存,encode不一定处理整条消息,如果空间不够可以处理部分消息。如果缓存已满或者没有新的msg可以写则调用tcp_write。out_event的设计可以使一次tcp_write发送多条msg,减少系统调用,提高效率。如果msg没有处理完整,则下次再次进入到if语句中时

outsize = encoder->encode (&outpos, 0);


会继续编码剩下的数据。

看完stream_engine是怎么样使用encoder之后,再回头看encoder的encode方法,该方法每次把buff指向自己的缓存或者是传入进来的指针,接着encoder向buff中写入数据,首先判断to_write是否为0,如果是则运行状态机,这里同样有一个避免拷贝的优化,当to_read比自带buffer大并且传入进来的*data是null,当前的pos也为0(证明之前的数据已经全部发送出去,不会造成数据混乱),则可以直接将发送缓存的指针指向msg的数据部分,这里也不会存在线程安全问题。

v2_encoder的状态机和v2_decoder相比比较简单,只有两个状态:

zmq::v2_encoder_t::v2_encoder_t (size_t bufsize_) :
encoder_base_t <v2_encoder_t> (bufsize_)
{
//  Write 0 bytes to the batch and go to message_ready state.
next_step (NULL, 0, &v2_encoder_t::message_ready, true);
}

zmq::v2_encoder_t::~v2_encoder_t ()
{
}

void zmq::v2_encoder_t::message_ready ()
{
//  Encode flags.
unsigned char &protocol_flags = tmpbuf [0];
protocol_flags = 0;
if (in_progress->flags () & msg_t::more)
protocol_flags |= v2_protocol_t::more_flag;
if (in_progress->size () > 255)
protocol_flags |= v2_protocol_t::large_flag;
if (in_progress->flags () & msg_t::command)
protocol_flags |= v2_protocol_t::command_flag;

//  Encode the message length. For messages less then 256 bytes,
//  the length is encoded as 8-bit unsigned integer. For larger
//  messages, 64-bit unsigned integer in network byte order is used.
const size_t size = in_progress->size ();
if (unlikely (size > 255)) {
put_uint64 (tmpbuf + 1, size);
next_step (tmpbuf, 9, &v2_encoder_t::size_ready, false);
}
else {
tmpbuf [1] = static_cast <uint8_t> (size);
next_step (tmpbuf, 2, &v2_encoder_t::size_ready, false);
}
}

void zmq::v2_encoder_t::size_ready ()
{
//  Write message body into the buffer.
next_step (in_progress->data (), in_progress->size (),
&v2_encoder_t::message_ready, true);
}


以上就是v2编码器和解码器的工作原理。

除了v1和v2编码器,zmq还提供raw_decode/encode 方式,这种方式比较简单,这里就不做分析了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: