阻塞IO下的echo实验

代码分析

echo代码位置:recipes-master/tpc/echo.cc echo_client.cc
服务端代码

int main(int argc, char* argv[])
{
  InetAddress listenAddr(3007);
  Acceptor acceptor(listenAddr);
  printf("Accepting... Ctrl-C to exit\n");
  int count = 0;
  bool nodelay = argc > 1 && strcmp(argv[1], "-D") == 0;
  while (true)
  {
    TcpStreamPtr tcpStream = acceptor.accept();
    printf("accepted no. %d client\n", ++count);
    if (nodelay)
      tcpStream->setTcpNoDelay(true);
	// 一个线程处理一个客户端连接
    // C++11 doesn't allow capturing unique_ptr in lambda, C++14 allows.
    std::thread thr([count] (TcpStreamPtr stream) {
      printf("thread for no. %d client started.\n", count);
      char buf[4096];
      int nr = 0;
      while ( (nr = stream->receiveSome(buf, sizeof(buf))) > 0)
      {
        int nw = stream->sendAll(buf, nr);
        if (nw < nr)
        {
          break;
        }
      }
      printf("thread for no. %d client ended.\n", count);
    }, std::move(tcpStream));
    thr.detach();
  }
}

服务端的逻辑主要就是,建立连接,每次读取4096个字节,然后这些数据再返回给客户端。

客户端

int main(int argc, const char* argv[])
{
  if (argc < 3)
  {
    printf("Usage: %s hostname message_length [scp]\n", argv[0]);
    return 0;
  }

  const int len = atoi(argv[2]);

  InetAddress addr(3007);
  if (!InetAddress::resolve(argv[1], &addr))
  {
    printf("Unable to resolve %s\n", argv[1]);
    return 0;
  }

  printf("connecting to %s\n", addr.toIpPort().c_str());
  TcpStreamPtr stream(TcpStream::connect(addr));
  if (!stream)
  {
    printf("Unable to connect %s\n", addr.toIpPort().c_str());
    perror("");
    return 0;
  }

  printf("connected, sending %d bytes\n", len);
	// 发送数据
  std::string message(len, 'S');
  int nw = stream->sendAll(message.c_str(), message.size());
  printf("sent %d bytes\n", nw);

  if (argc > 3)
  {
    for (char cmd : std::string(argv[3]))
    {
      if (cmd == 's')  // shutdown
      {
        printf("shutdown write\n");
        stream->shutdownWrite();
      }
      else if (cmd == 'p') // pause
      {
        printf("sleeping for 10 seconds\n");
        ::sleep(10);
        printf("done\n");
      }
      else if (cmd == 'c') // close
      {
        printf("close without reading response\n");
        return 0;
      }
      else
      {
        printf("unknown command '%c'\n", cmd);
      }
    }
  }
	// 返回数据
  std::vector<char> receive(len);
  int nr = stream->receiveAll(receive.data(), receive.size());
  printf("received %d bytes\n", nr);
  if (nr != nw)
  {
    printf("!!! Incomplete response !!!\n");
  }
}

客户端的逻辑主要就是,建立连接,发送数据,数据发送完再接收读取返回的数据

测试

客户端在测试20m的数据时出现了阻塞,此时服务端也阻塞当中

[wang@localhost tpc]$ ./echo_client ip 2048000
connecting to 192.168.1.104:3007
connected, sending 2048000 bytes
# ... 客户端阻塞
[wang@localhost tpc]$ ./echo
Accepting... Ctrl-C to exit
accepted no. 1 client
thread for no. 1 client started.
# ... 服务端阻塞
排查问题

查看Recv-Q和Send-Q

服务端

[wang@localhost ~]$ netstat -tpn | grep '3007\|^[AP]'
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp   536080 260640 192.168.1.104:3007      192.168.1.105:56412     ESTABLISHED 5985/./ech

客户端

[wang@localhost ~]$ netstat -tpn | grep '3007\|^[AP]'
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
Active Internet connections (w/o servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name    
tcp   261264 307992 192.168.1.105:56412     192.168.1.104:3007      ESTABLISHED 12526/./echo_client 

可以看见不管客户端还是服务端,Send-Q上都有大量的缓存数据,可以看出send发生阻塞。

分析原因

服务端:由于服务端在每次recv4K字节后需要send数据给客户端,但此时客户端正在send数据的过程中,没有recv数据,导致服务端send阻塞
客户端:由于服务端阻塞在send中,没有继续recv数据,导致客户端也陷入send阻塞当中

解决方案

我们在设计协议的时候,可以先让客户端发一个header,告诉服务端数据的长度,然后服务端在接收到header后,准备好一个符合大小的buffer,将数据全部读取到buffer当中,之后再返回数据给客户端。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值