目录

TCP socket API

1>socket

2>bind

3>listen

4>accept

5>connect

6>细节点

V1 - Echo Server

1>代码实现

a.InetAddr.hpp

b.Common.hpp

c.TcpServer.hpp

d.TcpClient.cc

V2 - Echo Server多进程版本

TcpServer.hpp

V3 - Echo Server多线程版本

TcpServer.hpp

V4 - Echo Server 线程池版本

TcpServer.hpp

V5 - 多进程远程命令执行

1>代码实现

a.Command.hpp

b.TcpServer.hpp

c.TcpServer.cc

d.TcpClient.cc

2>运行结果


TCP socket API

下⾯介绍程序中⽤到的socket API,这些函数都在sys/socket.h中

1>socket

• socket()打开⼀个⽹络通讯端⼝,如果成功的话,就像open()⼀样返回⼀个⽂件描述符

• 应⽤程序可以像读写⽂件⼀样⽤read/write在⽹络上收发数据

• 如果socket()调⽤出错则返回-1

• 对于IPv4,family参数指定为AF_INET

• 对于TCP协议,type参数指定为SOCK_STREAM,表⽰⾯向流的传输协议

• protocol参数的介绍从略,指定为0即可

2>bind

• 服务器程序所监听的⽹络地址和端⼝号通常是固定不变的,客⼾端程序得知服务器程序的地址和端⼝号后就可以向服务器发起连接; 服务器需要调⽤bind绑定⼀个固定的⽹络地址和端⼝号

• bind()成功返回0,失败返回-1

• bind()的作⽤是将参数sockfd和myaddr绑定在⼀起,使sockfd这个⽤于⽹络通讯的⽂件描述符监听myaddr所描述的地址和端⼝号

• struct sockaddr *是⼀个通⽤指针类型,myaddr参数实际上可以接受多种协议的sockaddr结构体,⽽它们的⻓度各不相同,所以需要第三个参数addrlen指定结构体的⻓度

我们的程序中对myaddr参数是这样初始化的:

  1. 将整个结构体清零

  2. 设置地址类型为AF_INET

  3. ⽹络地址为INADDR_ANY,这个宏表⽰本地的任意IP地址,因为服务器可能有多个⽹卡,每个⽹卡也可能绑定多个IP 地址,这样设置可以在所有的IP地址上监听,直到与某个客⼾端建⽴了连接时才确定下来到底⽤哪个IP 地址

  4. 端⼝号为SERV_PORT,我们定义为9999

3>listen

• listen()声明sockfd处于监听状态,并且最多允许有backlog个客⼾端处于连接等待状态,如果接收到更多的连接请求就忽略,这⾥设置不会太⼤(⼀般是5)

• listen()成功返回0,失败返回-1

4>accept

• 三次握⼿完成后,服务器调⽤accept()接受连接

• 如果服务器调⽤accept()时还没有客⼾端的连接请求,就阻塞等待直到有客⼾端连接上来

• addr是⼀个传出型参数,accept()返回时传出客⼾端的地址和端⼝号

• 如果给addr参数传NULL,表⽰不关⼼客⼾端的地址

• addrlen参数是⼀个传⼊传出型参数(value-result argument),传⼊的是调⽤者提供的,缓冲区addr的⻓度以避免缓冲区溢出问题,传出的是客⼾端地址结构体的实际⻓度(有可能没有占满调⽤者提供的缓冲区)

我们的服务器程序结构是这样的:

5>connect

• 客⼾端需要调⽤connect()连接服务器

• connect和bind的参数形式⼀致,区别在于bind的参数是⾃⼰的地址,⽽connect的参数是目标服务器的地址

• connect()成功返回0,出错返回-1

6>细节点

① listen -> 只要TCP服务器处于listen状态,那么它就已经可以被连接了!

② accept -> 我们获取的连接,是从内核中直接获取的,建立连接的过程和accept无关!

③ accept -> 返回值是一个文件描述符,这个文件描述符是提供服务的(即IO服务),而我们之前socket的sockfd,它只负责获取新连接,所以它是一个listensockfd(监听套接字)

④ connect -> client会在connect成功之后,在底层自动进行bind!

V1 - Echo Server

1>代码实现

a.InetAddr.hpp

引⼊InetAddr.hpp,⽅便后⾯打印消息

 #pragma once
 ​
 #include "Common.hpp"
 ​
 // 网络地址和主机地址之间进行转换的类
 ​
 class InetAddr
 {
 public:
     InetAddr(struct sockaddr_in &addr) : _addr(addr)
     {
         // 网络转主机
         _port = ntohs(_addr.sin_port);  // 从网络中拿到的是网络序列
         // _ip = inet_ntoa(_addr.sin_addr); // 4字节网络风格的IP -> 点分十进制的字符串风格的IP
         char ipbuffer[64];
         inet_ntop(AF_INET, &_addr.sin_addr, ipbuffer, sizeof(_addr));
         _ip = ipbuffer;
     }
     InetAddr(const std::string &ip, u_int16_t port):_ip(ip), _port(port)
     {
         // 主机转网络
         memset(&_addr, 0, sizeof(_addr));
         _addr.sin_family = AF_INET;
         inet_pton(AF_INET, _ip.c_str(), &_addr.sin_addr);
         _addr.sin_port = htons(_port); //写错了哈
         // local.sin_addr.s_addr = inet_addr(ip.c_str());
     }
     InetAddr(u_int16_t port):_port(port), _ip("")
     {
         // 主机转网络
         memset(&_addr, 0, sizeof(_addr));
         _addr.sin_family = AF_INET;
         _addr.sin_addr.s_addr = INADDR_ANY;
         _addr.sin_port = htons(_port);
     }
     std::string Ip(){ return _ip; }
     uint16_t Port(){ return _port; }
     const struct sockaddr_in& NetAddr(){ return _addr; }
     const struct sockaddr *NetAddrPtr()
     {
         return CONV(_addr);
     }
     socklen_t NetAddrLen()
     {
         return sizeof(_addr);
     }
     bool operator==(const InetAddr &addr)
     {
         return _ip == addr._ip && _port == addr._port;
     }
     std::string StringAddr()
     {
         return _ip + ":" + std::to_string(_port);
     }
     ~InetAddr()
     {
     }
 private:
     struct sockaddr_in _addr;
     std::string _ip;
     uint16_t _port;
 };

b.Common.hpp

 #pragma once
 ​
 #include <iostream>
 #include <functional>
 #include <unistd.h>
 #include <string>
 #include <cstring>
 #include <memory>
 #include <sys/socket.h>
 #include <sys/types.h>
 #include <arpa/inet.h>
 #include <netinet/in.h>
 ​
 enum ExitCode
 {
     OK = 0,
     USAGE_ERR,
     SOCKET_ERR,
     BIND_ERR,
     LISTEN_ERR,
     CONNECT_ERR,
     FOCK_ERR
 };
 ​
 class NoCopy
 {
 public:
     NoCopy(){}
     NoCopy(const NoCopy&) = delete;
     const NoCopy& operator=(const NoCopy&) = delete;
     ~NoCopy(){}
 };
 ​
 #define CONV(addr) ((struct sockaddr*)&addr)

c.TcpServer.hpp

在构建TcpServer时,需要传入两个参数,分别是端口号和回调函数,所以在上层TcpServer.cc中要实现回调函数,具体可以看V5版本的TcpServer.cc

 #pragma once
 ​
 #include "Common.hpp"
 #include "Log.hpp"
 #include "InetAddr.hpp"
 #include "ThreadPool.hpp"
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <pthread.h>
 ​
 using namespace LogModule;
 using namespace ThreadPoolModule;
 ​
 using task_t = std::function<void()>;
 // using func_t = std::function<std::string(const std::string&, InetAddr&)>;
 ​
 const static int defaultsockfd = -1;
 const static int backlog = 8;
 ​
 class TcpServer : NoCopy
 {
 public:
     TcpServer(uint16_t port, func_t func)
         : _listensockfd(defaultsockfd),
           _port(port),
           _isrunning(false),
           _func(func)
     {
     }
     void Init()
     {
         // 1. 创建套接字文件
         _listensockfd = socket(AF_INET, SOCK_STREAM, 0);
         if (_listensockfd < 0)
         {
             LOG(LogLevel::FATAL) << "socket error";
             exit(SOCKET_ERR);
         }
         LOG(LogLevel::INFO) << "socket success: " << _listensockfd;
 ​
         // 2. bind众所周知的端口号
         InetAddr local(_port);
         int n = bind(_listensockfd, local.NetAddrPtr(), local.NetAddrLen());
         if (n < 0)
         {
             LOG(LogLevel::FATAL) << "bind error";
             exit(BIND_ERR);
         }
         LOG(LogLevel::INFO) << "bind success: " << _listensockfd;
 ​
         // 3. 设置socket状态为listen
         n = listen(_listensockfd, backlog);
         if (n < 0)
         {
             LOG(LogLevel::FATAL) << "listen error";
             exit(LISTEN_ERR);
         }
         LOG(LogLevel::INFO) << "listen success: " << _listensockfd;
     }
     class ThreadData
     {
     public:
         ThreadData(int fd, InetAddr &ar, TcpServer *s)
             : sockfd(fd),
               addr(ar),
               tsvr(s)
         {
         }
         int sockfd;
         InetAddr addr;
         TcpServer *tsvr;
     };
     void Service(int sockfd, InetAddr &peer)
     {
         char buffer[1024];
         while (true)
         {
             // 1. 先读取数据
             // a. n>0: 读取成功
             // b. n<0: 读取失败
             // c. n==0: 对端把链接关闭了,读到了文件的结尾 --- pipe
             ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);
             if (n > 0)
             {
                 // buffer是一个英文单词 or 是一个命令字符串
                 buffer[n] = 0; // 设置为C风格字符串, n <= sizeof(buffer)-1
                 LOG(LogLevel::DEBUG) << peer.StringAddr() << " # " << buffer;
 ​
                 // std::string echo_string = _func(buffer, peer);
 ​
                 std::string echo_string = "echo# ";
                 echo_string += buffer;
 ​
                 write(sockfd, echo_string.c_str(), echo_string.size());
             }
             else if (n == 0)
             {
                 LOG(LogLevel::DEBUG) << peer.StringAddr() << " 退出了...";
                 close(sockfd);
                 break;
             }
             else
             {
                 LOG(LogLevel::DEBUG) << peer.StringAddr() << " 退出了...";
                 close(sockfd);
                 break;
             }
         }
     }
     static void *Routine(void *args)
     {
         pthread_detach(pthread_self());
         ThreadData *td = static_cast<ThreadData *>(args);
         td->tsvr->Service(td->sockfd, td->addr);
         delete td;
         return nullptr;
     }
     void Run()
     {
         _isrunning = true;
         while (_isrunning)
         {
             struct sockaddr_in peer;
             socklen_t len = sizeof(peer);
             int sockfd = accept(_listensockfd, CONV(peer), &len);
             if (sockfd < 0)
             {
                 LOG(LogLevel::WARNING) << "accept error";
                 continue;
             }
             InetAddr addr(peer);
             LOG(LogLevel::INFO) << "accept success, peer addr : " << addr.StringAddr();
 ​
             // // version0 -- test version --- 单进程程序 --- 不会存在的!
             Service(sockfd, addr);
 ​
             // // version1 --- 多进程版本
             // pid_t id = fork();
             // if(id < 0)
             // {
             //     LOG(LogLevel::FATAL) << "fork error";
             //     exit(FOCK_ERR);
             // }
             // else if(id == 0)
             // {
             //     // 子进程,子进程除了看到sockfd,能看到listensockfd吗??
             //     // 我们不想让子进程访问listensock!
             //     close(_listensockfd);
             //     if(fork() > 0) // 再次fork,子进程退出
             //         exit(OK);
 ​
             //     Service(sockfd, addr); // 孙子进程,孤儿进程,1, 系统回收我
             //     exit(OK);
             // }
             // else
             // {
             //     //父进程
             //     close(sockfd);
 ​
             //     //父进程要等待子进程啊,不然子进程就成僵尸进程了
             //     pid_t rid = waitpid(id, nullptr, 0); // 阻塞的吗?不会,因为子进程立马退出了
             //     (void)rid;
             // }
 ​
             // // version2: 多线程版本
             // ThreadData* td = new ThreadData(sockfd, addr, this);
             // pthread_t tid;
             // pthread_create(&tid, nullptr, Routine, td);
 ​
             // // version3:线程池版本,线程池一般比较适合处理短服务
             // // 将新连接和客户端构建一个新的任务,push线程池中
             // ThreadPool<task_t>::GetInstance()->Enqueue([this, sockfd, &addr](){
             //     this->Service(sockfd, addr);
             // })
         }
         _isrunning = false;
     }
     ~TcpServer()
     {
     }
 ​
 private:
     int _listensockfd; // 监听socket
     uint16_t _port;
     bool _isrunning;
 ​
     func_t _func; // 设置回调处理
 };

d.TcpClient.cc

 #include "Common.hpp"
 #include "InetAddr.hpp"
 ​
 void Usage(std::string proc)
 {
     std::cerr << "Usage: " << proc << " server_ip server_port" << std::endl;
 }
 ​
 int main(int argc, char* argv[])
 {
     if(argc != 3)
     {
         Usage(argv[0]);
         exit(USAGE_ERR);
     }
     std::string serverip = argv[1];
     uint16_t serverport = std::stoi(argv[2]);
 ​
     // 1. 创建socket
     int sockfd = socket(AF_INET, SOCK_STREAM, 0);
     if(sockfd < 0)
     {
         std::cerr << "socket error" << std::endl;
         exit(SOCKET_ERR);
     }
 ​
     // 2. bind吗??需要。显式的bind?不需要!随机方式选择端口号
     // 我应该做什么呢?listen?accept?都不需要!!
 ​
     // 3. 直接向目标服务器发起建立连接的请求
     InetAddr serveraddr(serverip, serverport);
     int n = connect(sockfd, serveraddr.NetAddrPtr(), serveraddr.NetAddrLen());
     if(n < 0)
     {
         perror("connect");
         std::cout << n << std::endl;
         std::cerr << "connect error" << std::endl;
         exit(CONNECT_ERR);
     }
 ​
     // 4. echo client
     while(true)
     {
         std::string line;
         std::cout << "Please Enter@ ";
         std::getline(std::cin, line);
 ​
         write(sockfd, line.c_str(), line.size());
 ​
         char buffer[1024];
         ssize_t size = read(sockfd, buffer, sizeof(buffer)-1);
         if(size > 0)
         {
             buffer[size] = 0;
             std::cout << "server echo# " << buffer << std::endl;
         }
     }
     close(sockfd);
 ​
     return 0;
 }

由于客⼾端不需要固定的端⼝号,因此不必调⽤bind(),客⼾端的端⼝号由内核⾃动分配

• 客⼾端不是不允许调⽤bind(),只是没有必要显⽰的调⽤bind()固定⼀个端⼝号,否则如果在同⼀台机器上启动多个客⼾端,就会出现端⼝号被占⽤导致不能正确建⽴连接

• 服务器也不是必须调⽤bind(),但如果服务器不调⽤bind(),内核会⾃动给服务器分配监听端⼝,每次启动服务器时端⼝号都不⼀样,客⼾端要连接服务器就会遇到⿇烦

测试多个连接的情况

再启动⼀个客⼾端,尝试连接服务器,发现第⼆个客⼾端,不能正确的和服务器进⾏通信

分析原因,是因为我们accecpt了⼀个请求之后,就在⼀直while循环尝试read,没有继续调⽤到accecpt,导致不能接受新的请求

我们当前的这个TCP,只能处理⼀个连接,这是不科学的

V2 - Echo Server多进程版本

通过每个请求, 创建⼦进程的⽅式来⽀持多连接

TcpServer.hpp

这里只需要对V1版本修改一下即可,通过两次fork()就可以让孙子进程去执行回调函数,而父进程回收子进程后,又可以回来继续accept了

 void Run()
     {
         _isrunning = true;
         while (_isrunning)
         {
             struct sockaddr_in peer;
             socklen_t len = sizeof(peer);
             int sockfd = accept(_listensockfd, CONV(peer), &len);
             if (sockfd < 0)
             {
                 LOG(LogLevel::WARNING) << "accept error";
                 continue;
             }
             InetAddr addr(peer);
             LOG(LogLevel::INFO) << "accept success, peer addr : " << addr.StringAddr();
 ​
             // // version0 -- test version --- 单进程程序 --- 不会存在的!
             // Service(sockfd, addr);
 ​
             // version1 --- 多进程版本
             pid_t id = fork();
             if(id < 0)
             {
                  LOG(LogLevel::FATAL) << "fork error";
                  exit(FOCK_ERR);
              }
              else if(id == 0)
              {
                  // 子进程,子进程除了看到sockfd,能看到listensockfd吗??
                  // 我们不想让子进程访问listensock!
                  close(_listensockfd);
                  if(fork() > 0) // 再次fork,子进程退出
                      exit(OK);
 ​
                  Service(sockfd, addr); // 孙子进程,孤儿进程,1, 系统回收我
                  exit(OK);
              }
              else
              {
                  //父进程
                  close(sockfd);
 ​
                  //父进程要等待子进程啊,不然子进程就成僵尸进程了
                  pid_t rid = waitpid(id, nullptr, 0); // 阻塞的吗?不会,因为子进程立马退出了
                  (void)rid;
              }
 ​
             // version2: 多线程版本
             // ThreadData* td = new ThreadData(sockfd, addr, this);
             // pthread_t tid;
             // pthread_create(&tid, nullptr, Routine, td);
 ​
             // // version3:线程池版本,线程池一般比较适合处理短服务
             // // 将新连接和客户端构建一个新的任务,push线程池中
             // ThreadPool<task_t>::GetInstance()->Enqueue([this, sockfd, &addr](){
             //     this->Service(sockfd, addr);
             // })
         }
         _isrunning = false;
     }

V3 - Echo Server多线程版本

TcpServer.hpp

这里也是对V1版本稍微改动即可,并且使⽤最原始的接⼝,使⽤内部ThreadData类

 class ThreadData
 {
 public:
     ThreadData(int fd, InetAddr &ar, TcpServer *s)
         : sockfd(fd),
           addr(ar),
           tsvr(s)
     {
     }
     int sockfd;
     InetAddr addr;
     TcpServer *tsvr;
 };
 void Service(int sockfd, InetAddr &peer)
 {
     char buffer[1024];
     while (true)
     {
         // 1. 先读取数据
         // a. n>0: 读取成功
         // b. n<0: 读取失败
         // c. n==0: 对端把链接关闭了,读到了文件的结尾 --- pipe
         ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);
         if (n > 0)
         {
             // buffer是一个英文单词 or 是一个命令字符串
             buffer[n] = 0; // 设置为C风格字符串, n <= sizeof(buffer)-1
             LOG(LogLevel::DEBUG) << peer.StringAddr() << " # " << buffer;
 ​
             std::string echo_string = _func(buffer, peer);
 ​
             // std::string echo_string = "echo# ";
             // echo_string += buffer;
 ​
             write(sockfd, echo_string.c_str(), echo_string.size());
         }
         else if (n == 0)
         {
             LOG(LogLevel::DEBUG) << peer.StringAddr() << " 退出了...";
             close(sockfd);
             break;
         }
         else
         {
             LOG(LogLevel::DEBUG) << peer.StringAddr() << " 退出了...";
             close(sockfd);
             break;
         }
     }
 }
 static void *Routine(void *args)
 {
     pthread_detach(pthread_self());
     ThreadData *td = static_cast<ThreadData *>(args);
     td->tsvr->Service(td->sockfd, td->addr);
     delete td;
     return nullptr;
 }
 void Run()
 {
     _isrunning = true;
     while (_isrunning)
     {
         struct sockaddr_in peer;
         socklen_t len = sizeof(peer);
         int sockfd = accept(_listensockfd, CONV(peer), &len);
         if (sockfd < 0)
         {
             LOG(LogLevel::WARNING) << "accept error";
             continue;
         }
         InetAddr addr(peer);
         LOG(LogLevel::INFO) << "accept success, peer addr : " << addr.StringAddr();
 ​
         // // version0 -- test version --- 单进程程序 --- 不会存在的!
         // Service(sockfd, addr);
 ​
         // // version1 --- 多进程版本
         // pid_t id = fork();
         // if(id < 0)
         // {
         //     LOG(LogLevel::FATAL) << "fork error";
         //     exit(FOCK_ERR);
         // }
         // else if(id == 0)
         // {
         //     // 子进程,子进程除了看到sockfd,能看到listensockfd吗??
         //     // 我们不想让子进程访问listensock!
         //     close(_listensockfd);
         //     if(fork() > 0) // 再次fork,子进程退出
         //         exit(OK);
 ​
         //     Service(sockfd, addr); // 孙子进程,孤儿进程,1, 系统回收我
         //     exit(OK);
         // }
         // else
         // {
         //     //父进程
         //     close(sockfd);
 ​
         //     //父进程要等待子进程啊,不然子进程就成僵尸进程了
         //     pid_t rid = waitpid(id, nullptr, 0); // 阻塞的吗?不会,因为子进程立马退出了
         //     (void)rid;
         // }
 ​
         // version2: 多线程版本
         ThreadData* td = new ThreadData(sockfd, addr, this);
         pthread_t tid;
         pthread_create(&tid, nullptr, Routine, td);
 ​
         // // version3:线程池版本,线程池一般比较适合处理短服务
         // // 将新连接和客户端构建一个新的任务,push线程池中
         // ThreadPool<task_t>::GetInstance()->Enqueue([this, sockfd, &addr](){
         //     this->Service(sockfd, addr);
         // })
     }
     _isrunning = false;
 }

V4 - Echo Server 线程池版本

引⼊系统部分的线程池,进⾏简单的业务处理

TcpServer.hpp

同样这里也是对V1版本稍微改动即可

 void Run()
 {
     _isrunning = true;
     while (_isrunning)
     {
         struct sockaddr_in peer;
         socklen_t len = sizeof(peer);
         int sockfd = accept(_listensockfd, CONV(peer), &len);
         if (sockfd < 0)
         {
             LOG(LogLevel::WARNING) << "accept error";
             continue;
         }
         InetAddr addr(peer);
         LOG(LogLevel::INFO) << "accept success, peer addr : " << addr.StringAddr();
 ​
         // // version0 -- test version --- 单进程程序 --- 不会存在的!
         // Service(sockfd, addr);
 ​
         // // version1 --- 多进程版本
         // pid_t id = fork();
         // if(id < 0)
         // {
         //     LOG(LogLevel::FATAL) << "fork error";
         //     exit(FOCK_ERR);
         // }
         // else if(id == 0)
         // {
         //     // 子进程,子进程除了看到sockfd,能看到listensockfd吗??
         //     // 我们不想让子进程访问listensock!
         //     close(_listensockfd);
         //     if(fork() > 0) // 再次fork,子进程退出
         //         exit(OK);
 ​
         //     Service(sockfd, addr); // 孙子进程,孤儿进程,1, 系统回收我
         //     exit(OK);
         // }
         // else
         // {
         //     //父进程
         //     close(sockfd);
 ​
         //     //父进程要等待子进程啊,不然子进程就成僵尸进程了
         //     pid_t rid = waitpid(id, nullptr, 0); // 阻塞的吗?不会,因为子进程立马退出了
         //     (void)rid;
         // }
 ​
         // // version2: 多线程版本
         // ThreadData* td = new ThreadData(sockfd, addr, this);
         // pthread_t tid;
         // pthread_create(&tid, nullptr, Routine, td);
 ​
         // version3:线程池版本,线程池一般比较适合处理短服务
         // 将新连接和客户端构建一个新的任务,push线程池中
         ThreadPool<task_t>::GetInstance()->Enqueue([this, sockfd, &addr](){
              this->Service(sockfd, addr);
          })
     }
     _isrunning = false;
 }

V5 - 多进程远程命令执行

1>代码实现

a.Command.hpp

命令类,⽤来执⾏命令,并获取结果

 #pragma once
 ​
 #include <iostream>
 #include <string>
 #include <cstdio>
 #include <set>
 #include "Command.hpp"
 #include "InetAddr.hpp"
 #include "Log.hpp"
 ​
 class Command
 {
 public:
     Command()
     {
         // 严格匹配
         _WhiteListCommands.insert("ls");
         _WhiteListCommands.insert("pwd");
         _WhiteListCommands.insert("ls -l");
         _WhiteListCommands.insert("touch haha.txt");
         _WhiteListCommands.insert("who");
         _WhiteListCommands.insert("whoami");
     }
     bool IsSafeCommand(const std::string &cmd)
     {
         auto iter = _WhiteListCommands.find(cmd);
         return iter != _WhiteListCommands.end();
     }
     std::string Execute(const std::string &cmd, InetAddr &addr)
     {
         // 1. 属于白名单命令
         if(!IsSafeCommand(cmd))
         {
             return "坏人";
         }
 ​
         std::string who = addr.StringAddr();
 ​
         // 2. 执行命令
         FILE* fp = popen(cmd.c_str(), "r"); // 管道 && 创建子进程
         if(fp == nullptr)
         {
             return std::string("你要执行的命令不存在: ") + cmd;
         }
         std::string res;
         char line[1024];
         while(fgets(line, sizeof(line), fp))
         {
             res += line;
         }
         pclose(fp);
         std::string result = who + "execute done, result is: \n" + res;
         LOG(LogLevel::DEBUG) << result;
         return result;
     }
     ~Command(){}
 private:
     // 受限制的远程执行
     std::set<std::string> _WhiteListCommands;
 };

b.TcpServer.hpp

#pragma once

#include "Common.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"
#include "ThreadPool.hpp"
#include <sys/types.h>
#include <sys/wait.h>
#include <pthread.h>

using namespace LogModule;
using namespace ThreadPoolModule;

// using task_t = std::function<void()>;
using func_t = std::function<std::string(const std::string&, InetAddr&)>;

const static int defaultsockfd = -1;
const static int backlog = 8;

class TcpServer : NoCopy
{
public:
    TcpServer(uint16_t port, func_t func)
        : _listensockfd(defaultsockfd),
          _port(port),
          _isrunning(false),
          _func(func)
    {
    }
    void Init()
    {
        // 1. 创建套接字文件
        _listensockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_listensockfd < 0)
        {
            LOG(LogLevel::FATAL) << "socket error";
            exit(SOCKET_ERR);
        }
        LOG(LogLevel::INFO) << "socket success: " << _listensockfd;

        // 2. bind众所周知的端口号
        InetAddr local(_port);
        int n = bind(_listensockfd, local.NetAddrPtr(), local.NetAddrLen());
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "bind error";
            exit(BIND_ERR);
        }
        LOG(LogLevel::INFO) << "bind success: " << _listensockfd;

        // 3. 设置socket状态为listen
        n = listen(_listensockfd, backlog);
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "listen error";
            exit(LISTEN_ERR);
        }
        LOG(LogLevel::INFO) << "listen success: " << _listensockfd;
    }
    class ThreadData
    {
    public:
        ThreadData(int fd, InetAddr &ar, TcpServer *s)
            : sockfd(fd),
              addr(ar),
              tsvr(s)
        {
        }
        int sockfd;
        InetAddr addr;
        TcpServer *tsvr;
    };
    void Service(int sockfd, InetAddr &peer)
    {
        char buffer[1024];
        while (true)
        {
            // 1. 先读取数据
            // a. n>0: 读取成功
            // b. n<0: 读取失败
            // c. n==0: 对端把链接关闭了,读到了文件的结尾 --- pipe
            ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);
            if (n > 0)
            {
                // buffer是一个英文单词 or 是一个命令字符串
                buffer[n] = 0; // 设置为C风格字符串, n <= sizeof(buffer)-1
                LOG(LogLevel::DEBUG) << peer.StringAddr() << " # " << buffer;

                std::string echo_string = _func(buffer, peer);

                // std::string echo_string = "echo# ";
                // echo_string += buffer;

                write(sockfd, echo_string.c_str(), echo_string.size());
            }
            else if (n == 0)
            {
                LOG(LogLevel::DEBUG) << peer.StringAddr() << " 退出了...";
                close(sockfd);
                break;
            }
            else
            {
                LOG(LogLevel::DEBUG) << peer.StringAddr() << " 异常了...";
                close(sockfd);
                break;
            }
        }
    }
    static void *Routine(void *args)
    {
        pthread_detach(pthread_self());
        ThreadData *td = static_cast<ThreadData *>(args);
        td->tsvr->Service(td->sockfd, td->addr);
        delete td;
        return nullptr;
    }
    void Run()
    {
        _isrunning = true;
        while (_isrunning)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int sockfd = accept(_listensockfd, CONV(peer), &len);
            if (sockfd < 0)
            {
                LOG(LogLevel::WARNING) << "accept error";
                continue;
            }
            InetAddr addr(peer);
            LOG(LogLevel::INFO) << "accept success, peer addr : " << addr.StringAddr();

            // // version0 -- test version --- 单进程程序 --- 不会存在的!
            // Service(sockfd, addr);

            // // version1 --- 多进程版本
            // pid_t id = fork();
            // if(id < 0)
            // {
            //     LOG(LogLevel::FATAL) << "fork error";
            //     exit(FOCK_ERR);
            // }
            // else if(id == 0)
            // {
            //     // 子进程,子进程除了看到sockfd,能看到listensockfd吗??
            //     // 我们不想让子进程访问listensock!
            //     close(_listensockfd);
            //     if(fork() > 0) // 再次fork,子进程退出
            //         exit(OK);

            //     Service(sockfd, addr); // 孙子进程,孤儿进程,1, 系统回收我
            //     exit(OK);
            // }
            // else
            // {
            //     //父进程
            //     close(sockfd);

            //     //父进程要等待子进程啊,不然子进程就成僵尸进程了
            //     pid_t rid = waitpid(id, nullptr, 0); // 阻塞的吗?不会,因为子进程立马退出了
            //     (void)rid;
            // }

            // version2: 多线程版本
            ThreadData* td = new ThreadData(sockfd, addr, this);
            pthread_t tid;
            pthread_create(&tid, nullptr, Routine, td);

            // // version3:线程池版本,线程池一般比较适合处理短服务
            // // 将新连接和客户端构建一个新的任务,push线程池中
            // ThreadPool<task_t>::GetInstance()->Enqueue([this, sockfd, &addr](){
            //     this->Service(sockfd, addr);
            // })
        }
        _isrunning = false;
    }
    ~TcpServer()
    {
    }

private:
    int _listensockfd; // 监听socket
    uint16_t _port;
    bool _isrunning;

    func_t _func; // 设置回调处理
};

c.TcpServer.cc

#include "TcpServer.hpp"
#include "Dict.hpp"
#include "Command.hpp"

std::string defaulthandler(const std::string &word, InetAddr &addr)
{
    LOG(LogLevel::DEBUG) << "回调到了defaulthandler";
    std::string s = "haha, ";
    s += word;
    return s;
}

void Usage(std::string proc)
{
    std::cerr << "Usage: " << proc << " port" << std::endl;
}

int main(int argc, char* argv[])
{
    if(argc != 2)
    { 
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = std::stoi(argv[1]);

    Enable_Console_LogStrategy();

    // // 1. 翻译模块
    // Dict d;
    // d.LoadDict();

    // std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(port, [&d](const std::string &word, InetAddr &addr){
    //     return d.Translate(word, addr);
    // });

    // 2. 命令的执行模块
    Command cmd;
    
    std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(port, std::bind(&Command::Execute, &cmd, std::placeholders::_1, std::placeholders::_2));

    tsvr->Init();
    tsvr->Run();

    return 0;
}

d.TcpClient.cc

#include "Common.hpp"
#include "InetAddr.hpp"

void Usage(std::string proc)
{
    std::cerr << "Usage: " << proc << " server_ip server_port" << std::endl;
}

int main(int argc, char* argv[])
{
    if(argc != 3)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    // 1. 创建socket
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if(sockfd < 0)
    {
        std::cerr << "socket error" << std::endl;
        exit(SOCKET_ERR);
    }

    // 2. bind吗??需要。显式的bind?不需要!随机方式选择端口号
    // 我应该做什么呢?listen?accept?都不需要!!

    // 3. 直接向目标服务器发起建立连接的请求
    InetAddr serveraddr(serverip, serverport);
    int n = connect(sockfd, serveraddr.NetAddrPtr(), serveraddr.NetAddrLen());
    if(n < 0)
    {
        perror("connect");
        std::cout << n << std::endl;
        std::cerr << "connect error" << std::endl;
        exit(CONNECT_ERR);
    }

    // 4. echo client
    while(true)
    {
        std::string line;
        std::cout << "Please Enter@ ";
        std::getline(std::cin, line);

        write(sockfd, line.c_str(), line.size());

        char buffer[1024];
        ssize_t size = read(sockfd, buffer, sizeof(buffer)-1);
        if(size > 0)
        {
            buffer[size] = 0;
            std::cout << "server echo# " << buffer << std::endl;
        }
    }
    close(sockfd);

    return 0;
}

2>运行结果

本篇文章到这里就结束啦,希望这些内容对大家有所帮助!

下篇文章见,希望大家多多来支持一下!

感谢大家的三连支持!

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐