Linux 多线程服务端编程读书笔记(七)

Linux 多线程服务端编程读书笔记(七)

第七章 muduo编程示例

1、UNP 中五个简单的示例
  1. discard:丢弃所有收到的数据,简单的长连接TCP应用层协议

    void DiscardServer::onMessage(const TcpConnectionPtr& conn,
                                  Buffer* buf,
                                  Timestamp time)
    {
      string msg(buf->retrieveAllAsString());
      LOG_INFO << conn->name() << " discards " << msg.size()
               << " bytes received at " << time.toString();
    }
    
  2. daytime:短连接协议,在发送完当前时间后,由服务器主动断开连接

    void DaytimeServer::onConnection(const TcpConnectionPtr& conn)
    {
      LOG_INFO << "DaytimeServer - " << conn->peerAddress().toIpPort() << " -> "
               << conn->localAddress().toIpPort() << " is "
               << (conn->connected() ? "UP" : "DOWN");
      if (conn->connected())
      {
        conn->send(Timestamp::now().toFormattedString() + "\n");
        conn->shutdown();//主动断开连接
      }
    }
    
  3. time : 与daytime极其相似,只不过它返回的不是日期时间字符串,而是一个32bit的整数

    void TimeServer::onConnection(const muduo::net::TcpConnectionPtr& conn)
    {
      LOG_INFO << "TimeServer - " << conn->peerAddress().toIpPort() << " -> "
               << conn->localAddress().toIpPort() << " is "
               << (conn->connected() ? "UP" : "DOWN");
      if (conn->connected())
      {
        time_t now = ::time(NULL);
        int32_t be32 = sockets::hostToNetwork32(static_cast<int32_t>(now));
        conn->send(&be32, sizeof be32);
        conn->shutdown();
      }
    }
    

    time客户端:time服务端发送的是二进制数据,不易读取,因此客户端来解析

    void onMessage(const TcpConnectionPtr& conn, Buffer* buf, Timestamp receiveTime)
      {
        if (buf->readableBytes() >= sizeof(int32_t))
        {
          const void* data = buf->peek();
          int32_t be32 = *static_cast<const int32_t*>(data);
          buf->retrieve(sizeof(int32_t));
          time_t time = sockets::networkToHost32(be32);
          Timestamp ts(time * Timestamp::kMicroSecondsPerSecond);
          LOG_INFO << "Server time = " << time << ", " << ts.toFormattedString();
        }
        else
        {
          LOG_INFO << conn->name() << " no enough data " << buf->readableBytes()
                   << " at " << receiveTime.toFormattedString();
        }
      }
    };
    
  4. echo:前面的都是一个单向接收和发送数据,这是第一个双向发送的协议,即将服务端发送的数据原封不动的发送回去

    void EchoServer::onMessage(const muduo::net::TcpConnectionPtr& conn,
                               muduo::net::Buffer* buf,
                               muduo::Timestamp time)
    {
      muduo::string msg(buf->retrieveAllAsString());
      LOG_INFO << conn->name() << " echo " << msg.size() << " bytes, "
               << "data received at " << time.toString();
      conn->send(msg);
    }
    
    
  5. Chargen: 只发送数据,不接受数据,且发送数据的速度不能快过客户端接收的速度

    void ChargenServer::onConnection(const TcpConnectionPtr& conn)
    {
      LOG_INFO << "ChargenServer - " << conn->peerAddress().toIpPort() << " -> "
               << conn->localAddress().toIpPort() << " is "
               << (conn->connected() ? "UP" : "DOWN");
      if (conn->connected())
      {
        conn->setTcpNoDelay(true);
        conn->send(message_);
      }
    }
    
    void ChargenServer::onMessage(const TcpConnectionPtr& conn,
                                  Buffer* buf,
                                  Timestamp time)
    {
      string msg(buf->retrieveAllAsString());
      LOG_INFO << conn->name() << " discards " << msg.size()
               << " bytes received at " << time.toString();
    }
    
  6. 五合一

    前面的五个程序都用到了Evenloop,其实是一个Reactor,用于注册和分发IO事件

    int main()
    {
      LOG_INFO << "pid = " << getpid();
      EventLoop loop;  // one loop shared by multiple servers
    
      ChargenServer chargenServer(&loop, InetAddress(2019));
      chargenServer.start();
    
      DaytimeServer daytimeServer(&loop, InetAddress(2013));
      daytimeServer.start();
    
      DiscardServer discardServer(&loop, InetAddress(2009));
      discardServer.start();
    
      EchoServer echoServer(&loop, InetAddress(2007));
      echoServer.start();
    
      TimeServer timeServer(&loop, InetAddress(2037));
      timeServer.start();
    
      loop.loop();
    }
    

    这就是Reactor模式复用线程的能力,让一个单线程程序同时具备多个网络服务功能

2、 文件传输

​ 利用 onWriteComplete() 实现分段传输,做到不必一次全部读入内存。

3、 TCP的半关闭问题
  1. shutdown()没有直接关闭TCP连接,这样是为了收发数据的完整性
  2. TCP是个全双工协议,同一个文件描述符既可读也可写,shutdownWrite()关闭了“写”方向上连接,保留了读方向上的,这成为TCP的半关闭状态,如果直接close, 那么socket_fd就不能读或者写了
  3. muduo把主动关闭连接的行为分两步走,先关闭写端,等对方关闭后,在关闭本地的读端
  4. muduo这种关闭连接要求对方read到0字节后会主动关闭连接,当对方故意不关闭连接,muduo的连接就一直半开阿哲,消耗系统资源。必要时调用Tcp::connection::handleclose()强行关闭连接
  5. TCP正在关闭是在TcpConnection对象析构的时候,这里会用到RAII
4、TCP的分包问题
  1. 消息长度固定
  2. 使用特殊字符或字符串作为消息边界,如http中的heads以“\r\n"
  3. 在每条消息头部加一个长度字段
  4. 利用消息本身的格式分包,例如XML、JSON
5、muduo Buffer类的设计与使用
  1. 为什么非阻塞网络编程中应用层buffer是必须的

    • non-blocking IO 的核心思想是避免阻塞在 read() 或 write() 或其他 IO 系统调用上,这样可以最大限度地复用 thread-of-control ,让一个线程能够服务于多个 socket 连接。这就是需要应用层 buffer 的原因。
    • **TcpConnection必须要有output buffer ** :比如TCP发送了100kb的数据,但是咋write()调用,操作系统只接受了80kb,因为不想原地等待(非阻塞),所以要尽快交出控制权,返回事件循环中。
    • 对于应用程序而言,它只管生成数据,它不应该关心到底数据是一次发送还是分成几次发送
  2. TCP粘包问题

    网络库在处理“socket可读”事件的时候必须一次性把socket的数据一次性读完(从操作系统的buff搬运到应用层的buff上面),否则会反复触发POLLIN事件,造成busy-loop.这是因为采用的是LT模式

  3. Buffer的设计

    • 对外表现是一块连续的内存,以方便客户代码的编写
    • 其size()可以自动增长,适应不同的消息
    • 内部以vector来保存数据
  4. Buffer的数据结构就是三个指针,一个数组,具体看书

  5. Buffer其他设计方案

    • 自己管理内存不用vector

    • zerocopy,注意不是严格意义上的0拷贝,数据从内核到用户空间有一次拷贝,如libevent2.0.x设计方案

      内存不是连续的,是分快的

6、一种自动反射消息的Protobuf网络传输方案,

​ 此部分具体看书

7、限制服务器的最大并发连接数

​ 这里的并发连接数是指同时支持的客户端的连接数

  1. 不希望程序超载
  2. 因为fd是稀缺资源
8、定时器
  1. 与时间相关的常见任务
    1. 获取当前时间,计算时间间隔
    2. 失去转换与日期计算
    3. 定时操作
  2. muduo使用的时间操作函数
    1. 计时:只使用 gettimeofday(2) 获取当前时间
    2. 定时:只使用 timerfd_* 系列函数。
  3. 在非阻塞服务端编程中,绝对不能用 sleep() 或类似的办法(这是因为该函数的实现可能用到了SIGALM,与多线程水火不容,见读书笔记4)来让程序原地等待,这会让主事件循环被挂起,程序失去响应。
  4. 测量两台机器的网络延迟与时间差
9、用timing wheel (时间轮)踢掉空闲连接
  • 如果一个连接是如果若干秒没有收到数据,则认为是空闲连接
  • 使用的数据结构是循环队列
10、 简单的消息广播服务

​ 此后的内容具体看书

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值