基于多线程阻塞 IO 实现的 netcat
-
Thread-per-connection 适用于连接数目不太多,或者线程非常廉价的情况。
-
使用多线程的方式来实现 netcat, 一个连接需要对应两个线程去处理,每个线程负责连接上的一个方向,即读 或 写。
代码
-
下面程序中,主线程负责 从标准输入,写到 TCP Socket,另一个线程负责 从 TCP Socket 读, 写到标准输出。
-
netcat
#include "Acceptor.h" #include "InetAddress.h" #include "TcpStream.h" #include <thread> #include <string.h> #include <unistd.h> 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) { // Caution: a bad example for closing connection // 读socket,写stdout std::thread thr([&stream] () { char buf[8192]; int nr = 0; // 接收数据,recv返回0,则代表对端关闭。因此跳出循环,关闭连接 while ( (nr = stream->receiveSome(buf, sizeof(buf))) > 0) { int nw = write_n(STDOUT_FILENO, buf, nr); if (nw < nr) { break; } } /* 这里采用exit的方式暴力的结束程序。因为,检测到对端半关闭连接时, * 本端主线程可能正处于read(stdin) 的阻塞状态,为了让主线程这边 * 也能够退出,因此采用了exit 的方案。 */ ::exit(0); // should somehow notify main thread instead }); // 主线程:读stdin,写socket char buf[8192]; int nr = 0; // 阻塞IO,这里read阻塞,直至读取到数据。 // 退出条件为,读stdin时,返回0.则主线程关闭套接字写段,发送FIN while ( (nr = ::read(STDIN_FILENO, buf, sizeof(buf))) > 0) { // 读取到数据后,马上发送到socket上 int nw = stream->sendAll(buf, nr); if (nw < nr) { break; } } // 关闭写段,发送FIN 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]); 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); } } }