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网络编程分包和解包(二)

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

Socket 粘包 封包 拆包

这两天看csdn有一些关于socket粘包,socket缓冲区设置的问题,发现自己不是很清楚,所以查资料了解记录一下:  一 .两个简单概念长连接与短连接: 1.长连接     Clie...
  • OOLOoo
  • OOLOoo
  • 2014年07月28日 23:31
  • 1636

Socket TCP粘包拆包

TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务器端)都要有一一成对的socket,因此,发送端为了将多个发...
  • robinjwong
  • robinjwong
  • 2015年12月03日 01:26
  • 2644

Muduo 网络编程示例之零:前言

我将会写一系列文章,介绍用 muduo 网络库完成常见的 TCP 网络编程任务。这些例子都比较简单,逻辑不复杂,代码也很短,适合摘取关键部分放到博客上。其中一些有一定的代表性与针对性,比如“如何传输完...
  • Solstice
  • Solstice
  • 2011年02月02日 00:58
  • 31982

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

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

TCP粘包拆包问题

摘要: 粘包拆包问题是处于网络比较底层的问题,在数据链路层、网络层以及传输层都有可能发生。我们日常的网络应用开发大都在传输层进行,由于UDP有消息保护边界,不会发生这个问题,因此这篇文章只讨论发生在传...
  • yuanbinquan
  • yuanbinquan
  • 2016年11月15日 16:33
  • 346

TCP握手挥手以及分包发送

一、首先我们先了解一下Tcp的三次握手流程 1)第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchroni...
  • loveheronly
  • loveheronly
  • 2015年04月23日 21:29
  • 1233

7-muduo编程示例

Please indicate the source: http://blog.csdn.net/gaoxiangnumber1Welcome to my github: https://github...
  • gaoxiangnumber1
  • gaoxiangnumber1
  • 2017年03月29日 15:09
  • 356

开源网络库的分析libevent muduo nginx ....

最经看关于网络编程的一些书,对于网络编程中的一些基本东西,开源库已经封装的很好了,但是库归根结底还是使用的基本API,所以就想着分析一下,尤其是在看了各个库的介绍以后,所以这段时间想在这个方向投入一点...
  • yusiguyuan
  • yusiguyuan
  • 2013年12月15日 23:02
  • 5613

C#高性能大容量SOCKET并发(五):粘包、分包、解包

使用TCP长连接就会引入粘包的问题,粘包是指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。粘包可能由发送方造成,也可能由接收方造成。TCP为提高传输...
  • SQLDebug_Fan
  • SQLDebug_Fan
  • 2014年03月07日 11:40
  • 32345
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:muduo网络编程分包和解包(一)
举报原因:
原因补充:

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