基于tcp服务器实现远程命令操作功能

1.简单了解Tcp和Udp的区别

TCP是面向连接的,也就是需要连接的,使用Tcp协议的客户端进行通信的时候必须要先和服务器建立连接,这就要求服务器随时都要处于等待连接的状态,这也就是所谓的监听,才能进行通信,而Udp一旦绑定就自动建立连接,可以直接通信;

backlog是指服务器中全链接的个数,其实也就是排队个数;

还有就是Tcp是面向字节流进行通信的,Udp是面向数据报通信,两者的区别是面向数据报是不可能会发生读不完这种情况的,但是面向字节流可能会发生,所以我们使用recv和sendto来解决这个问题;

2.Tcp接收数据的方法

因为tcp接收数据前需要先建立连接,并且进行通信的socket和建立连接的监听套接字并不相同,所以在accept这里我们就可以直接设置多线程模式了,或者是直接引入线程池;

3.TCP服务器的实现

server.hpp

cpp 复制代码
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <string>
#include <cstdlib>
#include <cstring>
#include <sys/wait.h>
#include <pthread.h>
#include <functional>
#include "log.hpp"
#include "conmmen.hpp"
#include "InetAddr.hpp"
#include "ThreadPool.hpp"

using namespace LogModule;
using namespace ThreadPoolModule; 

const int gloalsockfd = -1;
static const uint16_t gport = 8080;
#define BACKLOG 8
using handler_t =std::function<std::string (std::string)>;
class Tcpserver
{
  using task_t =std::function<void()>;
  struct ThreadData
  {
      int sockfd;
      Tcpserver * self;
  };

public:
  Tcpserver(handler_t handler, uint16_t port = gport)
      : _handler(handler),_listensockfd(gloalsockfd), _port(port), _isrunning(false)

  {
  }

  void InitServer()
  {
    // 1.创建套接字
    _listensockfd = ::socket(AF_INET, SOCK_STREAM, 0);
    if (_listensockfd < 0)
    {
      LOG(loglevel::FATAL) << "socket:" << strerror(errno);
      Die(SOCKET_ERR);
    }
    LOG(loglevel::INFO) << "create socket success";

    // 2.绑定套接字到网络
    struct sockaddr_in local;
    memset(&local,0,sizeof(local));
    local.sin_family=AF_INET;
    local.sin_port=htons(gport);
    local.sin_addr.s_addr=INADDR_ANY;
    int n = ::bind(_listensockfd, cast(&local),sizeof(local));
    if (n < 0)
    {
      LOG(loglevel::FATAL) << "bind:" << strerror(errno);
      Die(BIND_ERR);
    }
    LOG(loglevel::INFO) << "bind success";

    // 3.监听
    n = ::listen(_listensockfd, BACKLOG);
    if (n < 0)
    {
      LOG(loglevel::FATAL) << "listen:" << strerror(errno);
      Die(LISTEN_ERR);
    }
    LOG(loglevel::INFO) << "listen success";
    signal(SIGCHLD, SIG_IGN); //忽略子进程退出信号,os自动回收不再不要wait了
  }

  void HandlerRequest(int sockfd)
  {
     LOG(loglevel::INFO)<<"HandlerRequest ,sockfd is:"<<sockfd;
     char inbuffer[4096];
     while(true)
     {
       //ssize_t n = ::read(sockfd,inbuffer,sizeof(inbuffer)-1);//读取不完善
       ssize_t n = ::recv(sockfd,inbuffer,sizeof(inbuffer)-1,0);  
       if(n>0)
       {
          inbuffer[n]=0;
          // std::string echo_str="server echo #";
          // echo_str+=inbuffer;

          std::string result_str = _handler(inbuffer);    //回调方法出去调用完还是会回来的
         // ::write(sockfd,echo_str.c_str(),echo_str.size()); 写入也是不完善的
           ::sendto(sockfd,result_str.c_str(),result_str.size(),0,nullptr,0 );
       }
       else if(n==0)
       {
            LOG(loglevel::INFO)<<"client close the connection"<< sockfd;
            break;  
       }
       else
       {
          break;
       }
     }

     ::close(sockfd);
  }

  static void * threadEntry(void * args)
  {
      pthread_detach(pthread_self());
      ThreadData * data =(ThreadData*)args;
      data->self->HandlerRequest(data->sockfd); 
      return nullptr;
  }

  void Start()
  {
    _isrunning = true;
    while (_isrunning)
    {
      struct sockaddr_in peer;
      socklen_t peerlen = sizeof(peer);  //对端地址的长度必须初始化好,不然会有随机值,参数就会出错 
      int sockfd = ::accept(_listensockfd, cast(&peer), &peerlen);
      if (sockfd < 0)
      {
        LOG(loglevel::WARNING) << "accept:" << strerror(errno);
        continue;
      }
      LOG(loglevel::INFO) << "accecpt success,sockfd:" << sockfd;

      //version 1 多进程模式
      // pid_t id=fork();  //创建子进程
      // if(id == 0)
      // {
      //   //关闭不需要的文件描述符,防止子进程误触发
      //   ::close(_listensockfd); 
      //   if(fork() >0) exit(0);  //子进程退出,孙子进程执行,因为子进程退出了,所以孙子进程成为了孤儿进程,会被系统收养,所以孙子进程也要退出,不然会造成僵尸进程
      //   //子进程处理请求  
      //   HandlerRequest(sockfd); 
      //   exit(0);  //子进程退出,必须退出否则会造成内存泄露,因为子进程会拷贝父进程得文件描述符表
      // }
      // ::close(sockfd);  //sockfd已经交给子进程了,父进程关闭自己不关心的文件描述符,防止误触发
      // //子进程已经退出,父进程不再阻塞
      // int rid =::waitpid(id,nullptr,0);
      // if(id < 0) 
      // {
      //    LOG(loglevel::FATAL)<<"create fork error";
      // }

      //version 2 多线程模式
      //新线程和主线程共享一张文件描述符表
      // pthread_t tid ;
      // ThreadData * data= new ThreadData;  //sockfd是在栈上开辟的空间声明周期很短,int(sockfd)的意思是用sockfd初始化int类型的地址sockfdp;
      // data->sockfd=sockfd;
      // data->self=this;
      // pthread_create(&tid,nullptr,threadEntry,data);

      //version 3 线程池模式
      task_t f=std::bind(&Tcpserver::HandlerRequest,this,sockfd);
      ThreadPool<task_t>::getInstance()->Equeue([this,sockfd](){
        this->HandlerRequest(sockfd);
      });
      
    }
  }

  void Stop()
  {
    _isrunning = false;
  }
  ~Tcpserver()
  {
  }

private:
  int _listensockfd;
  bool _isrunning;
  uint16_t _port;
  handler_t _handler;


};

clientMain.cc

cpp 复制代码
#include <iostream>
#include <string>
#include <cstring>   
#include <sys/socket.h> 
#include <arpa/inet.h>  
#include <netinet/in.h>     
#include <unistd.h>
#include <sys/types.h> 

int main(int argc,char * argv[])
{
    if(argc !=3)
    {
        std::cout<<"Usage:./client_tcp server_ip server_port"<<std::endl;
        return 1;
    }
    std::string server_ip = argv[1];
    int server_port = std::stoi(argv[2]); 
    int sockfd=::socket(AF_INET,SOCK_STREAM,0);
    if(sockfd<0)
    {
        std::cout<<"create sockfd error"<<std::endl;
        return 2;
    }

    struct sockaddr_in server_addr; 
    memset(&server_addr,0,sizeof(server_addr));
    server_addr.sin_family=AF_INET; 
    server_addr.sin_port=::htons(server_port); 
    server_addr.sin_addr.s_addr=::inet_addr(server_ip.c_str());   

    int n = ::connect(sockfd,(struct sockaddr*)&server_addr,sizeof(server_addr));  //connect之后会自动bind
    if(n<0)
    {
        std::cout<<"connect error"<<std::endl;
        return 3;
    }
    std::string message;
    while(true)
    {
        char inbuffer[1024];
        std::cout<<"input message:"; 
        std::getline(std::cin,message);
        n= ::write(sockfd,message.c_str(),message.size());
        if(n>0)
        {
            //读的是服务器发回来的数据
           int m = ::read(sockfd,inbuffer,sizeof(inbuffer));
           if(m>0)
           {
              inbuffer[m]=0;
              std::cout<<"receive message from server:"<<inbuffer<<std::endl;
           }
           else
                 break;
        }
        else 
              break;    
    }   
    ::close(sockfd);
    return 0;
}

serverMain.cc

cpp 复制代码
#include "server.hpp"
#include "commendExcu.hpp"
#include <memory>

int main()
{
    ENABLE_CONSOLE_LOG();
    commendExcu commend;   //实例化命令执行类   
    std::unique_ptr<Tcpserver> tsvr=std::make_unique<Tcpserver>([&commend](std::string cmdstr){
        return commend.execute(cmdstr);
    });   //用智能指针初始化服务器
    tsvr -> InitServer();   //初始化服务器
    tsvr -> Start();        //启动服务器
    return 0;
}

commentExct.hpp ->实现远程执行命令功能

cpp 复制代码
#pragma once
#include <iostream>
#include <cstdio>
#include <string>   

class commendExcu
{
  public:
    std::string execute(std::string cmdstr)
    {
      FILE * fp = ::popen(cmdstr.c_str(), "r");
      if(fp == nullptr)
      {
         return std::string("Failed to execute command");
      }
      char buffer[1024];
      std::string result;
      while(true)
      {
         char * ret = ::fgets(buffer, sizeof(buffer), fp);
         if(!ret) break;
         result += ret;
      }
      pclose(fp);
      return result.empty()?std::string("done"):result;
     }
};

本篇博客可以搭配"每日遗忘"专栏中的二三篇文章来看;

相关推荐
xhbh6661 小时前
Windows 如何实现 IP 转发?从注册表到 netsh 命令的完整指南
网络
混迹中的咸鱼1 小时前
Unreal Engine 5 联机网络架构技术手册
网络·架构·ue5
pengyi8710151 小时前
HTTP与HTTPS代理基础区别,协议原理通俗解析
网络·爬虫·网络协议·tcp/ip·智能路由器
call me by ur name1 小时前
多模态大模型轻量化
前端·网络·人工智能
专注VB编程开发20年1 小时前
轻量级多进程消息收发模型WEBSOCKET,MQTT
网络·websocket·网络协议
Tim风声(网络工程师)9 小时前
排查内网互联网访问流程
运维·服务器·网络
科技牛牛12 小时前
AI爬虫引爆代理IP产业:一场正在发生的数据粮草争夺战
人工智能·爬虫·tcp/ip·数据安全·ip地址查询
一袋米扛几楼9812 小时前
【网络】网络规划与底层通信:自顶向下方法论 (Top-Down Methodology) 全解析
网络·工程
liulilittle12 小时前
TCP BBR 拥塞控制模块编译
网络·网络协议·tcp/ip