20-第一个netcat的实现

代码

recipes/netcat.cc at master · chenshuo/recipes (github.com)

 #include "Acceptor.h"
 #include "InetAddress.h"
 #include "TcpStream.h"
 
 #include <thread>
 
 #include <string.h>
 #include <unistd.h>
 
 // 读n字节
 int write_n(int fd, const void* buf, int length)
 {
   int written = 0;
   while (written < length)
  {
     int nw = ::write(fd, static_cast<const char*>(buf) + written, length - written);
     if (nw > 0)
    {
       written += nw;
    }
     else if (nw == 0)
    {
       break;  // EOF
    }
     else if (errno != EINTR)
    {
       perror("write");
       break;
    }
  }
   return written;
 }
 
 void run(TcpStreamPtr stream)
 {
   // 子线程:负责从socket读数据,并将数据写到stdout
   // Caution: a bad example for closing connection
   std::thread thr([&stream] () {
     char buf[8192];
     int nr = 0;
     while ( (nr = stream->receiveSome(buf, sizeof(buf))) > 0)
    {
       int nw = write_n(STDOUT_FILENO, buf, nr);
       if (nw < nr)
      {
         break;
      }
    }
    ::exit(0);  // should somehow notify main thread instead
  });
 
   // 主线程:负责从stdin读数据,并将数据写到socket  
   char buf[8192];
   int nr = 0;
   while ( (nr = ::read(STDIN_FILENO, buf, sizeof(buf))) > 0)
  {
     int nw = stream->sendAll(buf, nr);
     if (nw < nr)
    {
       break;
    }
  }
   stream->shutdownWrite();
   thr.join();
 }
 
 int main(int argc, const char* argv[])
 {
   if (argc < 3)
  {
     printf("Usage:\n %s hostname port\n %s -l port\n", argv[0], argv[0]);
     return 0;
  }
 
   int port = atoi(argv[2]);
   // -l:代表监听某个端口,充当服务端  
   if (strcmp(argv[1], "-l") == 0)
  {
     std::unique_ptr<Acceptor> acceptor(new Acceptor(InetAddress(port)));
     TcpStreamPtr stream(acceptor->accept());
     if (stream)
    {
       acceptor.reset();  // stop listening
       run(std::move(stream));
    }
     else
    {
       perror("accept");
    }
  }
   // 充当客户端  
   else
  {
     InetAddress addr;
     const char* hostname = argv[1];
     if (InetAddress::resolve(hostname, port, &addr))
    {
       TcpStreamPtr stream(TcpStream::connect(addr));
       if (stream)
      {
         run(std::move(stream));
      }
       else
      {
         printf("Unable to connect %s\n", addr.toIpPort().c_str());
         perror("");
      }
    }
     else
    {
       printf("Unable to resolve %s\n", hostname);
    }
  }
 }

问题:如何线程如何通知另一个线程退出。

线程退出条件:

  • 主线程,读stdin返回0:

    • 55行的while循环退出,然后调用stream->shutdownWrite()。从而导致子线程中44行的读socket会返回0,从而子线程退出,主线程join返回,主线程也结束。

  • 子线程,读socket返回0:

    • 不能像上面一样shutdown write。因为就算子线程shutdown write,也不能阻止主线程从stdin读数据,如果stdin没有数据,主线程就一直阻塞在55行的read上。所以这里采用一种比较粗暴的办法:子线程调用exit(0),这里采用exit的方式暴力的结束程序。因为,检测到对端半关闭连接时, 本端主线程可能正处于read(stdin) 的阻塞状态,为了让主线程这边也能够退出,因此采用了exit 的方案。

 

阻塞IO有自动限速:比如写一个netcat 从/dev/zero读然后写到socket,当对端读socket的速度慢,netcat从/dev/zero读数据的速度也会慢

 
posted @ 2023-04-29 15:43  DavidJIAN  阅读(40)  评论(0)    收藏  举报