muduo网络编程分包和解包(一)

原创 2015年07月08日 14:21:40

non-blocking网络编程中,在Tcp这种字节流协议上做应用层分包是网络编程的基本需求。(muduo p194)
什么叫分包?
分包指的是在发送一个消息或一帧数据时,通过一定的处理,让接收方能从字节流中识别并截取(还原)出一个个消息。
为什么需要打包和分包?
因为tcp一次发送的数据可能不足一个完整的消息,或者包含多个消息。tcp一次发送数据的大小与内核中发送缓冲区的大小有关,如果当前发送缓冲区空余空间很小,那么可能一次就发送不了一个完整的消息;也有可能内核经过优化把两次send的数据仅合并成一次发送,这样对方收到的数据就会包含多个消息。所以我们在发送数据时需要在一条消息数据中再多添加一些额外信息让接受端可以正确识别一个个消息。
常用的打包方式有哪些?
对于短连接的tcp服务,分包不是一个问题,只要发送方主动关闭连接,就表示一个消息发送完毕,接收方read返回0,从而知道消息的结尾。
对于长连接的tcp服务,打包有四种方法:
1) 消息长度固定
2) 使用特殊的字符或者字符串作为消息的边界。例如http协议的headers以\r\n为字段的分隔符
3) 在每条消息的头部加一个字段长度
4) 利用消息本身的格式来分包,例如xml中 的配对,或者json格式中的{…}的配对。解析这种消息格式通常会用到状态机。
muduo的打包分包基本原理:
(1)当发送端发送一个结构体时,首先使用protobuf进行序列化,然后计算序列化数据的长度,然后使用“长度+序列化数据”的打包方式发送给接收端,接收端接收到数据后对数据进行分包,首先读取数据长度,然后根据长度读取序列化数据,并对数据进行反序列化得到完整的消息结构。
(2)当发送端只发送一个简单的字符串时,可以使用“字符串长度+字符串数据”这种简单的打包方式,打包后的数据存放到muduo::net::Buffer,然后发送出去。接收的数据同样存放在muduo::net。
实现长度分包的代码:
muduo实现长度分包的代码由类LengthHeaderCodec 实现。代码位于https://github.com/chenshuo/muduo/blob/master/examples/asio/chat/codec.h
LengthHeaderCodec实现发送功能的代码为:

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);
}

send()中L4将std::string类型的message存放到muduo::net::Buffer,L5-L7首先获取message的长度,然后把长度由本地字节序转换成网络字节序,并放到message的前面。
实现接收部分的代码为:

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();
        int32_t be32 = *static_cast<const int32_t*>(data); // SIGBUS
        const int32_t len = muduo::net::sockets::networkToHost32(be32);
        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);
            muduo::string message(buf->peek(), len);
            messageCallback_(conn, message, receiveTime);
            buf->retrieve(len);
        }
        else
        {
            break;
        }
    }
}

onMessage()当收到的数据不足4个字节(用于存储message的长度)或者不足4+消息长度个字节时,onMessage直接返回。当收到的数据多于一条消息时,L5用了while循环来反复读取数据,直到Buffer中的数据不够一条完整的消息。当收到的数据恰好为一整条消息时,通过L20构造完整的message,并通过messageCallback_ 回调用户代码。所以用户需要事先通过LengthHeaderCodec 的构造函数把 messageCallback_ 传给它。

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)
  {
  }
    send()和onMessage()的代码同前    
    private:
    StringMessageCallback messageCallback_;
    const static size_t kHeaderLen = sizeof(int32_t);
}

把StringMessageCallback传给LengthHeaderCodec之后,当LengthHeaderCodec的onMessage收到数据后,就会把muduo::net::Buffer*的数据转换成包含一条完整消息的std::string&,这样让用户代码不必关心分包操作。

如何使用借助protobuf来发送结构体,见muduo网络编码分包和解包(二)

版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

muduo实现聊天服务器

muduo实现聊天服务器muduo实现一个聊天室服务器,客户发送的消息将广播到连入的所有客户(包括自己)。1.消息编码消息的字节流定义成这种形式 0xXX 0xXX 0xXX 0xXX XXXXXX,...

7-muduo编程示例

Please indicate the source: http://blog.csdn.net/gaoxiangnumber1Welcome to my github: https://github...

muduo网络编程分包和解包(二)

1. muduo发送结构体的方法如果发送端仅仅发送一段字符串,可以使用《muduo网络编程分包和解包(一)》介绍的长度+字符串的格式发送数据,但是如果想发送一个结构体或对象,需要对对象进行序列化把它转...

Muduo网络编程之使用Timing wheel 踢掉空闲连接

muduo网络编程之使用Timing Wheel踢掉空闲连接

Muduo 网络编程示例之八:用 Timing wheel 踢掉空闲连接

Muduo 网络编程示例之八:Timing wheel 踢掉空闲连接 陈硕 (giantchen_AT_gmail) Blog.csdn.net/Solstice  t.sina.com.cn/g...
  • Solstice
  • Solstice
  • 2011年05月04日 21:24
  • 26737

Muduo 网络编程示例之七:“串并转换”连接服务器及其自动化测试

Muduo 网络编程示例之七:连接服务器及其自动化测试 陈硕 (giantchen_AT_gmail) Blog.csdn.net/Solstice  t.sina.com.cn/giantc...
  • Solstice
  • Solstice
  • 2011年05月02日 19:49
  • 16706

muduo多机协作网络编程示例一:单词计数及排序

去年我写了《Muduo 网络编程示例》系列文章,这些文章已经收入《Linux 多线程服务端编程:使用 muduo C++ 网络库》一书。这些文章讲的基本都是运行在单机上的网络程序,每个例子都只有一个程...
  • Solstice
  • Solstice
  • 2013年01月13日 03:59
  • 34232

Muduo 网络编程示例之一:五个简单 TCP 协议

这是《Muduo 网络编程示例》系列的第一篇文章。本文将介绍五个简单 TCP 网络服务协议的 muduo 实现,包括 echo (RFC 862)、discard (RFC 863)、chargen ...
  • Solstice
  • Solstice
  • 2011年02月02日 12:59
  • 16021

Muduo 网络编程示例之二:Boost.Asio 的聊天服务器

这是《Muduo 网络编程示例》系列的第二篇文章。 本文讲介绍一个与 Boost.Asio 的示例代码中的聊天服务器功能类似的网络服务程序,包括客户端与服务端的 muduo 实现。这个例子的主要目的是...
  • Solstice
  • Solstice
  • 2011年02月04日 08:58
  • 18721

Muduo网络编程 时间轮 Timing wheel 踢掉空闲连接

通过boost::circular_buffer + boost::unordered_set来模拟轮盘。     typedef boost::unordered_set Bucket;    ...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:muduo网络编程分包和解包(一)
举报原因:
原因补充:

(最多只允许输入30个字)