聊天服务
一个服务端进程可以同时服务多个客户端,客户端接受键盘输入,以回车为界把消息发送给服务端,服务端收到消息之后,依次发送给每个连接到它的客户端,原来发送消息的客户端进程也会收到这条消息。
消息格式
每条消息有一个4字节头部,以网络序存放字节序长度。比如两条消息“hello”和“chenshuo":
打包的代码
把string message打包为muduo::net::Buffer,并通过conn发送。
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); //发送数据
}
分包的代码
void onMessage(const muduo::net::TcpConnectionPtr& conn,
muduo::net::Buffer* buf,
muduo::Timestamp receiveTime)
{
while(buf->readableBytes() >= kHeaderLen) //kHeaderLen = 4
{
//FIXME:use Buffer::peekInt32()
cont 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;
}
}
}
用while循环来反复读取数据,直到Buffer中的数据不够一条完整的消息。
编解码器LengthHeaderCodec
用来解决头部信息的编解码,当已连接套接字可读时,muduo的TcpConnction会读数据并存入input buffer中,然后回调用户的函数。通过LengthHeaderCodec这一层封装,让用户代码只关心“消息到达”而不是“数据到达”。
//muduo/examples/asio/chat/codec.h
#ifndef __CODEC_H__
#define __CODEC_H__
#include<muduo/base/Logging.h>
#include<muduo/net/Buffer.h>
#include<muduo/net/Endian.h>
#include<muduo/net/TcpConnection.h>
class LengthHeaderCodec:boost::noncopyable
{
public:
typedef boost::func