Design and implementation of muduo library learning 09-perfect TcpConnection

Dongyang's study notes

The main functions of TcpConnection introduced in the previous articles are nearly complete and can meet the needs of most muduo examples. Here are a few more small functions.

One, SIGPIPE

SIGPIPE The default behavior is to end the process, which is reasonable in the command line program, but in network programming, this means that if the other party disconnects and continues to write locally, this will cause the service process to exit unexpectedly.

If the service process is busy and the disconnection of the other party is not handled in time, it may continue to send data after the connection is disconnected. The following example simulates this situation:

void onConnection(const muduo::TcpConnectionPtr& conn)
{
    
    
  if (conn->connected())
  {
    
    
    printf("onConnection(): new connection [%s] from %s\n",
           conn->name().c_str(),
           conn->peerAddress().toHostPort().c_str());
+    if (sleepSeconds > 0)
+    {
    
    
+      ::sleep(sleepSeconds);
+    }
    conn->send(message1);
    conn->send(message2);
    conn->shutdown();
  }
  else
  {
    
    
    printf("onConnection(): connection [%s] is down\n",
           conn->name().c_str());
  }
}

Assuming sleepSecond is 5s, CRTL-C disconnects the client immediately after creating a connection with nc localhost 9981, and the server process will exit after a few seconds. The solution is simple, ignore SIGPIPE at the beginning of the program, and you can use C++ global objects to do this.

  • (Disable CTRL-C)
class IgnoreSigPipe
{
    
    
 public:
  IgnoreSigPipe()
  {
    
    
    ::signal(SIGPIPE, SIG_IGN);
  }
};

IgnoreSigPipe initObj;

2. TCP No Delay sum TCP keepalive

TCP No Delay and TCP keepalive are commonly used TCP options:

  • The function of the former is to disable the Nagle algorithm to avoid delays in continuous packet sending, which is very important for writing low-latency network services
  • The role of the latter is to periodically detect the existence of a TCP connection. Generally speaking, if there is an application layer heartbeat, it is not necessary, but as a general network library, its interface should be exposed.
// TcpConnection.h
void shutdown();
void setTcpNoDelay(bool on);

// TcpConnection.cc
void TcpConnection::setTcpNoDelay(bool on)
{
    
    
  socket_->setTcpNoDelay(on);
}

// Socket.cc
void Socket::setTcpNoDelay(bool on)
{
    
    
  int optval = on ? 1 : 0;
  ::setsockopt(sockfd_, IPPROTO_TCP, TCP_NODELAY,
               &optval, sizeof optval);
  // FIXME CHECK
}

The implementation of TcpConnection::setKeepAlive() is similar, omitted here, you can refer to the muduo source code.

三、 WriteCompleteCallback 和 HighWaterMarkCallback

As mentioned earlier, sending data for non-blocking network programming is much more difficult than reading data:

  • On the one hand, when to pay attention to writable events is a problem, which will bring coding difficulties;
  • On the other hand, if the speed of sending data is high and the speed of receiving data is high, it will cause data to pile up in the memory, which brings difficulties in design and security.

Muduo's solution to this is to provide two callbacks. Some network libraries call them 高水位回调and低水位回调

3.1 WriteCompleteCallback ( 如果发送缓冲区被清空, just call it)

WriteCompleteCallback is easier to understand 如果发送缓冲区被清空, just call it.
TcpConnection may trigger this callback in two places, as follows:

The first place: TcpConnection::sendInLoop()

void TcpConnection::sendInLoop(const std::string& message)
{
    
    
  loop_->assertInLoopThread();
  ssize_t nwrote = 0;
  // if no thing in output queue, try writing directly
  if (!channel_->isWriting() && outputBuffer_.readableBytes() == 0) {
    
    
    nwrote = ::write(channel_->fd(), message.data(), message.size());
    if (nwrote >= 0) {
    
    
      if (implicit_cast<size_t>(nwrote) < message.size()) {
    
    
        LOG_TRACE << "I am going to write more data";
+     } else if (writeCompleteCallback_) {
    
    
+       loop_->queueInLoop(
+           boost::bind(writeCompleteCallback_, shared_from_this()));
      }
    } else {
    
    

The second place:TcpConnection::handleWrite()

void TcpConnection::handleWrite()
{
    
    
  loop_->assertInLoopThread();
  if (channel_->isWriting()) {
    
    
    ssize_t n = ::write(channel_->fd(),
                        outputBuffer_.peek(),
                        outputBuffer_.readableBytes());
    if (n > 0) {
    
    
      outputBuffer_.retrieve(n);
      if (outputBuffer_.readableBytes() == 0) {
    
    
        channel_->disableWriting();
+       if (writeCompleteCallback_) {
    
    
+          loop_->queueInLoop(
+              boost::bind(writeCompleteCallback_, shared_from_this()));
+       }
        if (state_ == kDisconnecting) {
    
    
          shutdownInLoop();
        }
	// ...

TcpConnection and TcpServer also need to expose the interface of WriteCompleteCallback accordingly.

3.2 HighWaterMarkCallback

If the length of the output buffer exceeds the size specified by the user, a callback ( 只在上升沿触发一次) will be triggered .

If you write a proxy in a non-blocking way, the proxy has two connections, C and S. Only consider the data stream sent by the server to the client (and vice versa), in order to prevent the data sent by the server from bursting the output buffer of C.

  • One method is to stop reading the data of S in the HighWaterMarkCallback of C, and resume reading the data of S in the WriteCompleteCallback of C. This is the same as using a thick water pipe to fill a bucket and a thin water pipe to discharge water. The upper and lower faucets must be turned on and off in turn, similar to PWM.

Guess you like

Origin blog.csdn.net/qq_22473333/article/details/113751334