Bootstrap

【Linux网络编程】Socket编程--TCP:echo server | 多线程远程命令执行

在这里插入图片描述

🌈个人主页: 南桥几晴秋
🌈C++专栏: 南桥谈C++
🌈C语言专栏: C语言学习系列
🌈Linux学习专栏: 南桥谈Linux
🌈数据结构学习专栏: 数据结构杂谈
🌈数据库学习专栏: 南桥谈MySQL
🌈Qt学习专栏: 南桥谈Qt
🌈菜鸡代码练习: 练习随想记录
🌈git学习: 南桥谈Git

🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈
本科在读菜鸡一枚,指出问题及时改正

前言

在学习本章之前,先看【Linux网络编程】Socket编程–UDP:实现服务器接收客服端的消息 | DictServer简单的英译汉的网络字典 | 简单聊天室】,里面详细介绍函数的使用方法,小编在这篇文章不再具体介绍。

TCP echo server

服务端

创建套接字 | 绑定套接字

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int socket(int domain, int type, int protocol);

在TCP中,第二个参数,指定套接字类型应该为SOCK_STREAM,其余的和UDP中一样。
在这里插入图片描述


绑定套接字:

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

将创建的套接字sockfdstruct addr绑定。


//创建
_listensockfd=::socket(AF_INET,SOCK_STREAM,0);
if(_listensockfd<0)
{
    exit(SOCKET_ERROR);
    LOG(FATAL,"socket create error\n");
}
LOG(INFO,"socket create sussedd,listensockfd: %d\n",_listensockfd); //3

struct sockaddr_in local;
memset(&local,0,sizeof(local));

local.sin_family=AF_INET;
local.sin_port=htons(_port);  //网络序列
local.sin_addr.s_addr=INADDR_ANY;
//绑定:sockfd 和 socket addr
if(::bind(_listensockfd,(struct sockaddr*)&local,sizeof(local))<0)
{
    LOG(FATAL,"bind error\n");
    exit(BAND_ERROR);
}

listen–设置监听状态

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int listen(int sockfd, int backlog);
  • int sockfd创建的套接字
  • int backlog是连接队列的长度

由于TCP是面向连接的,因此TCP需要不断地能够做到获取连接,所以设置成监听状态,让套接字准备好,随时准备等待别人来连网。

const static int gblcklog=8;
if(::listen(_sockfd,gblcklog)<0)
{
    LOG(FATAL,"listen error\n");
    exit(LISTEN_ERROR);
}

在这里插入图片描述

accetp–获取连接

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

  • int sockfd设置为listen状态的套接字
  • 后面俩输出型参数用来获取 client 端的套接字信息
  • 返回值:返回值是一个文件描述符
    在这里插入图片描述

如何理解这里的文件描述符_sockfd

  • _sockfd套接字是用来获取新的连接,accept返回的文件描述符套接字是用来给客户提供服务的,随这_sockfd套接字获取的连接增多,accept返回的文件描述符套接字会越来越多。

  • _sockfd称之为监听套接字

while(_isrunning)
{
    struct sockaddr_in client;
    socklen_t len=sizeof(client);
    // 获取新连接
    int sockfd=::accept(_listensockfd,(struct sockaddr*)&client,&len);
    if(sockfd<0)
    {
        //获取连接失败
        LOG(WARING,"accept error\n");
        continue;
    }
    InetAddr addr(client);
    LOG(INFO,"get a new link,client info: %s\n",addr.AddrStr().c_str());
	//提供服务
	Service(sockfd,addr);

read–读取数据 | write–读取数据

TCP是面向字节流的,符合流式的特性,在Linux以及C++中,学过文件流等流式特性,这些都属于文件。UDP无法直接做文件读取,需要使用sentorecvdrom

#include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);

#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t count);

服务端提供信息version–0(单进程版)

#pragma once
#include<iostream>
#include<cstring>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include"Log.hpp"
#include"InetAddr.hpp"

using namespace log_ns;

enum
{
    SOCKET_ERROR=1,
    BAND_ERROR,
    LISTEN_ERROR
};

const static int gport=8888;
const static int gsock=-1;
const static int gblcklog=8;

class TcpServer
{
public:
    TcpServer(uint16_t port=gport)
        :_port(port)
        ,_listensockfd(gsock)
        ,_isrunning(false)
    {}

    void InitServer()
    {
        //创建
        _listensockfd=::socket(AF_INET,SOCK_STREAM,0);
        if(_listensockfd<0)
        {
            exit(SOCKET_ERROR);
            LOG(FATAL,"socket create error\n");
        }
        LOG(INFO,"socket create sussess,listensockfd: %d\n",_listensockfd); //3

        struct sockaddr_in local;
        memset(&local,0,sizeof(local));

        local.sin_family=AF_INET;
        local.sin_port=htons(_port);  //网络序列
        local.sin_addr.s_addr=INADDR_ANY;
        //绑定:sockfd 和 socket addr
        if(::bind(_listensockfd,(struct sockaddr*)&local,sizeof(local))<0)
        {
            LOG(FATAL,"bind error\n");
            exit(BAND_ERROR);
        }
        LOG(INFO,"bind success\n");

        // 设置监听状态 listen
        if(::listen(_listensockfd,gblcklog)<0)
        {
            LOG(FATAL,"listen error\n");
            exit(LISTEN_ERROR);
        }
        LOG(INFO,"listen success\n");
    }

    void Loop()
    {
        _isrunning=true;
        while(_isrunning)
        {
            struct sockaddr_in client;
            socklen_t len=sizeof(client);
            // 获取新连接
            int sockfd=::accept(_listensockfd,(struct sockaddr*)&client,&len);
            if(sockfd<0)
            {
                //获取连接失败
                LOG(WARING,"accept error\n");
                continue;
            }
            InetAddr addr(client);
            LOG(INFO,"get a new link,client info: %s\n",addr.AddrStr().c_str());
            //提供服务--version 0
            Service(sockfd,addr);
        }
        _isrunning=false;
    }

    //提供服务--version 0
    void Service(int sockfd,InetAddr addr)
    {
        //长服务
        while(true)
        {
            char inbuffer[1024];
            ssize_t n=::read(sockfd,inbuffer,sizeof(inbuffer)-1);
            if(n>0)
            {
                std::string echo_string="[sever echo] # ";
                echo_string+=inbuffer;
                write(sockfd,echo_string.c_str(),echo_string.size());
            }
            else if(n==0)
            {
                LOG(INFO,"client %s quit\n",addr.AddrStr().c_str());
                break;
            }
            else
            {
                LOG(ERROR,"read error: %s\n",addr.AddrStr().c_str());
                break;
            }
        }
        ::close(sockfd);
    }

    ~TcpServer()
    {}

private:
    uint16_t _port;
    int _listensockfd;
    bool _isrunning;
};

服务端提供信息version–1(多进程版)

在上一个版本中,服务器只能接受一个客户端发来的连接,无法接受多个客户端发来的请求,因此称之为单进程版。在这个版本中,使用多进程,实现多个客户端都可以向服务器发起连接。

在这里插入图片描述

上述父进程在创建子进程后,将父进程的数据结构以及文件描述符表都拷贝给了子进程,此时父子进程都指向同样的文件。

如果想让子进程单独处理新获取的连接,对于子进程来说,不关心之前创建的其他连接,只关心子进程本身继承下来的文件描述符,比如4号fd。对于曾经已经打开的文件建议关闭。如果不关,万一将来子进程误对3号fd进行操作。父进程对新获取的连接进行关闭,父进程不需要这个文件,新连接已经交给了子进程,让子进程进行处理。

对于不使用的文件描述符,需要及时关闭,避免文件描述符泄漏

在这里插入图片描述

因此,在多进程版本中,父进程在完成任务后,继续返回到获取连接那里,子进程完成获取到的新连接的任务。这样服务器实现了多进程并发式获取连接。

在这里插入图片描述

子进程创建后未来会结束,一旦结束就会先进入僵尸状态,等待父进程进行wait读取,一旦被读取,僵尸进程才会被释放掉,如果不读取,子进程将会一直处于僵尸状态。当前父进程采用的是阻塞式wait,当子进程被创建,子进程在处理任务时,父进程会一直阻塞等待,如果子进程一直不死,父进程就一直在阻塞等待。此时依旧是等子进程结束,父进程才开始工作,简单来说这不是并发,依然还是一次只能处理一个连接。
解决方案:

  1. 使用信号量,这是最推荐的做法,但是过于简单signal(SIGCHLD,SIG_IGN)
  2. 在子进程中再fork一次,让子进程直接退出,父进程的wait可以立即返回,僵尸状态就可以立即被处理。此时孙子进程的父进程退出了,变成了孤儿进程,孤儿进程被系统领养,退出后,OS会对他进程回收,爷爷进程就不用关心了。
    在这里插入图片描述

完整代码

#pragma once
#include<iostream>
#include<cstring>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/wait.h>
#include"Log.hpp"
#include"InetAddr.hpp"

using namespace log_ns;

enum
{
    SOCKET_ERROR=1,
    BAND_ERROR,
    LISTEN_ERROR
};

const static int gport=8888;
const static int gsock=-1;
const static int gblcklog=8;

class TcpServer
{
public:
    TcpServer(uint16_t port=gport)
        :_port(port)
        ,_listensockfd(gsock)
        ,_isrunning(false)
    {}

    void InitServer()
    {
        //创建
        _listensockfd=::socket(AF_INET,SOCK_STREAM,0);
        if(_listensockfd<0)
        {
            exit(SOCKET_ERROR);
            LOG(FATAL,"socket create error\n");
        }
        LOG(INFO,"socket create sussess,listensockfd: %d\n",_listensockfd); //3

        struct sockaddr_in local;
        memset(&local,0,sizeof(local));

        local.sin_family=AF_INET;
        local.sin_port=htons(_port);  //网络序列
        local.sin_addr.s_addr=INADDR_ANY;
        //绑定:sockfd 和 socket addr
        if(::bind(_listensockfd,(struct sockaddr*)&local,sizeof(local))<0)
        {
            LOG(FATAL,"bind error\n");
            exit(BAND_ERROR);
        }
        LOG(INFO,"bind success\n");

        // 设置监听状态 listen
        if(::listen(_listensockfd,gblcklog)<0)
        {
            LOG(FATAL,"listen error\n");
            exit(LISTEN_ERROR);
        }
        LOG(INFO,"listen success\n");
    }

    void Loop()
    {
        //signal(SIGCHLD,SIG_IGN);
        _isrunning=true;
        while(_isrunning)
        {
            struct sockaddr_in client;
            socklen_t len=sizeof(client);
            // 获取新连接
            int sockfd=::accept(_listensockfd,(struct sockaddr*)&client,&len);
            if(sockfd<0)
            {
                //获取连接失败
                LOG(WARING,"accept error\n");
                continue;
            }
            InetAddr addr(client);
            LOG(INFO,"get a new link,client info: %s, sockfd is: %d\n",addr.AddrStr().c_str(),sockfd);
            //提供服务--version 0
            //Service(sockfd,addr);

            //提供服务--version 1(多进程版)
            pid_t id=fork();
            if(id==0)
            {
                //child
                ::close(_listensockfd);
                
                if(fork()>0) exit(0);

                Service(sockfd,addr);

                exit(0);
            }
            //fathre
            ::close(sockfd);
            int n=waitpid(id,nullptr,0);
            if(n>0)
            {
                LOG(INFO,"wait child success\n");
            }
        }
        _isrunning=false;
    }

    //提供服务--version 0
    void Service(int sockfd,InetAddr addr)
    {
        //长服务
        while(true)
        {
            char inbuffer[1024];
            ssize_t n=::read(sockfd,inbuffer,sizeof(inbuffer)-1);
            if(n>0)
            {
                std::string echo_string="[sever echo] # ";
                echo_string+=inbuffer;
                write(sockfd,echo_string.c_str(),echo_string.size());
            }
            else if(n==0)
            {
                LOG(INFO,"client %s quit\n",addr.AddrStr().c_str());
                break;
            }
            else
            {
                LOG(ERROR,"read error: %s\n",addr.AddrStr().c_str());
                break;
            }
        }
        ::close(sockfd);
    }

    ~TcpServer()
    {}

private:
    uint16_t _port;
    int _listensockfd;
    bool _isrunning;
};

在这里插入图片描述

为什么两次都是4号fd?
父进程获取第一次连接后,将4号fd关闭了,此时4号fd是空的,第二次进程连接的时候,依然用这个4号fd。

服务端提供信息version–2(多线程版)

需要让新线程分离,如果在创建一个新线程后,让主线程等待,这不是并发式。
这里需要让新线程分离,这样新线程执行完毕后,会直接被回收。

在多线程中,所有的文件描述符表都是共享的,因此不能对不需要的文件描述符进行关闭。

#pragma once
#include<iostream>
#include<cstring>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/wait.h>    
#include<pthread.h>
#include"Log.hpp"
#include"InetAddr.hpp"

using namespace log_ns;

enum
{
    SOCKET_ERROR=1,
    BAND_ERROR,
    LISTEN_ERROR
};

const static int gport=8888;
const static int gsock=-1;
const static int gblcklog=8;

class TcpServer
{
public:
    TcpServer(uint16_t port=gport)
        :_port(port)
        ,_listensockfd(gsock)
        ,_isrunning(false)
    {}

    void InitServer()
    {
        //创建
        _listensockfd=::socket(AF_INET,SOCK_STREAM,0);
        if(_listensockfd<0)
        {
            exit(SOCKET_ERROR);
            LOG(FATAL,"socket create error\n");
        }
        LOG(INFO,"socket create sussess,listensockfd: %d\n",_listensockfd); //3

        struct sockaddr_in local;
        memset(&local,0,sizeof(local));

        local.sin_family=AF_INET;
        local.sin_port=htons(_port);  //网络序列
        local.sin_addr.s_addr=INADDR_ANY;
        //绑定:sockfd 和 socket addr
        if(::bind(_listensockfd,(struct sockaddr*)&local,sizeof(local))<0)
        {
            LOG(FATAL,"bind error\n");
            exit(BAND_ERROR);
        }
        LOG(INFO,"bind success\n");

        // 设置监听状态 listen
        if(::listen(_listensockfd,gblcklog)<0)
        {
            LOG(FATAL,"listen error\n");
            exit(LISTEN_ERROR);
        }
        LOG(INFO,"listen success\n");
    }

    class ThreadData
    {
    public:
        int _sockfd;
        TcpServer *_self;
        InetAddr _addr;

    public:
        ThreadData(int sockfd,TcpServer *self,const InetAddr &addr):_sockfd(sockfd),_self(self),_addr(addr)
        {}
    };

    void Loop()
    {
        //signal(SIGCHLD,SIG_IGN);
        _isrunning=true;
        while(_isrunning)
        {
            struct sockaddr_in client;
            socklen_t len=sizeof(client);
            // 获取新连接
            int sockfd=::accept(_listensockfd,(struct sockaddr*)&client,&len);
            if(sockfd<0)
            {
                //获取连接失败
                LOG(WARING,"accept error\n");
                continue;
            }
            InetAddr addr(client);
            LOG(INFO,"get a new link,client info: %s, sockfd is: %d\n",addr.AddrStr().c_str(),sockfd); //4
            //提供服务--version 2(多线程版)
            pthread_t tid;
            ThreadData *td=new ThreadData(sockfd,this,addr);
            pthread_create(&tid,nullptr,Excute,td); //新线程分离

        }

        _isrunning=false;
    }

    static void *Excute(void *argc)
    {
        ThreadData *td=static_cast<ThreadData *>(argc);
        pthread_detach(pthread_self());
        td->_self->Service(td->_sockfd,td->_addr);
        delete td;
        return nullptr;
    }

    //提供服务
    void Service(int sockfd,InetAddr addr)
    {
        //长服务
        while(true)
        {
            char inbuffer[1024];
            ssize_t n=::read(sockfd,inbuffer,sizeof(inbuffer)-1);
            if(n>0)
            {
                inbuffer[1024]=0;
                LOG(INFO,"get from client %s, message: %s\n",addr.AddrStr().c_str(),inbuffer);
                std::string echo_string="[sever echo] # ";
                echo_string+=inbuffer;
                write(sockfd,echo_string.c_str(),echo_string.size());
            }
            else if(n==0)
            {
                LOG(INFO,"client %s quit\n",addr.AddrStr().c_str());
                break;
            }
            else
            {
                LOG(ERROR,"read error: %s\n",addr.AddrStr().c_str());
                break;
            }
        }
        ::close(sockfd);
    }

    ~TcpServer()
    {}

private:
    uint16_t _port;
    int _listensockfd;
    bool _isrunning;
};

服务端提供信息version–3(线程池版)

线程池中的线程数量是有限的,因此需要对现有的长时间运行的服务进行修改,以避免某个服务长时间占用线程池中的线程,这样可能导致资源浪费和性能问题。

在这里,依然使用的是长服务,在未来可以使用短的服务,避免这种问题

#pragma once
#include<iostream>
#include<cstring>
#include<unistd.h>
#include<functional>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/wait.h>    
#include<pthread.h>
#include"Log.hpp"
#include"InetAddr.hpp"
#include"Thread.hpp"
#include"ThreadPool.hpp"

using namespace log_ns;

enum
{
    SOCKET_ERROR=1,
    BAND_ERROR,
    LISTEN_ERROR
};

const static int gport=8888;
const static int gsock=-1;
const static int gblcklog=8;

using task_t=std::function<void()>;

class TcpServer
{
public:
    TcpServer(uint16_t port=gport)
        :_port(port)
        ,_listensockfd(gsock)
        ,_isrunning(false)
    {}

    void InitServer()
    {
        //创建
        _listensockfd=::socket(AF_INET,SOCK_STREAM,0);
        if(_listensockfd<0)
        {
            exit(SOCKET_ERROR);
            LOG(FATAL,"socket create error\n");
        }
        LOG(INFO,"socket create sussess,listensockfd: %d\n",_listensockfd); //3

        struct sockaddr_in local;
        memset(&local,0,sizeof(local));

        local.sin_family=AF_INET;
        local.sin_port=htons(_port);  //网络序列
        local.sin_addr.s_addr=INADDR_ANY;
        //绑定:sockfd 和 socket addr
        if(::bind(_listensockfd,(struct sockaddr*)&local,sizeof(local))<0)
        {
            LOG(FATAL,"bind error\n");
            exit(BAND_ERROR);
        }
        LOG(INFO,"bind success\n");

        // 设置监听状态 listen
        if(::listen(_listensockfd,gblcklog)<0)
        {
            LOG(FATAL,"listen error\n");
            exit(LISTEN_ERROR);
        }
        LOG(INFO,"listen success\n");
    }

    class ThreadData
    {
    public:
        int _sockfd;
        TcpServer *_self;
        InetAddr _addr;

    public:
        ThreadData(int sockfd,TcpServer *self,const InetAddr &addr):_sockfd(sockfd),_self(self),_addr(addr)
        {}
    };

    void Loop()
    {
        //signal(SIGCHLD,SIG_IGN);
        _isrunning=true;
        while(_isrunning)
        {
            struct sockaddr_in client;
            socklen_t len=sizeof(client);
            // 获取新连接
            int sockfd=::accept(_listensockfd,(struct sockaddr*)&client,&len);
            if(sockfd<0)
            {
                //获取连接失败
                LOG(WARING,"accept error\n");
                continue;
            }
            InetAddr addr(client);
            LOG(INFO,"get a new link,client info: %s, sockfd is: %d\n",addr.AddrStr().c_str(),sockfd); //4

            //提供服务--version 3(线程池版) int sockfd,InetAddr addr
           task_t t=std::bind(&TcpServer::Service,this,sockfd,addr);
           ThreadPool<task_t>::GetInstance()->Equeue(t);

        }

        _isrunning=false;
    }

    static void *Excute(void *argc)
    {
        ThreadData *td=static_cast<ThreadData *>(argc);
        pthread_detach(pthread_self());
        td->_self->Service(td->_sockfd,td->_addr);
        delete td;
        return nullptr;
    }

    //提供服务
    void Service(int sockfd,InetAddr addr)
    {
        //长服务
        while(true)
        {
            char inbuffer[1024];
            ssize_t n=::read(sockfd,inbuffer,sizeof(inbuffer)-1);
            if(n>0)
            {
                inbuffer[1024]=0;
                LOG(INFO,"get from client %s, message: %s\n",addr.AddrStr().c_str(),inbuffer);
                std::string echo_string="[sever echo] # ";
                echo_string+=inbuffer;
                write(sockfd,echo_string.c_str(),echo_string.size());
            }
            else if(n==0)
            {
                LOG(INFO,"client %s quit\n",addr.AddrStr().c_str());
                break;
            }
            else
            {
                LOG(ERROR,"read error: %s\n",addr.AddrStr().c_str());
                break;
            }
        }
        ::close(sockfd);
    }

    ~TcpServer()
    {}

private:
    uint16_t _port;
    int _listensockfd;
    bool _isrunning;
};

客户端

connect–连接

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

返回值:成功为1,失败为0

connect 函数用于客户端与服务器之间的连接建立。客户端使用它向指定的服务器地址和端口发起连接请求。如果目标地址和端口的服务器正在监听并接受连接,connect 会成功,建立一个套接字连接。如果服务器拒绝连接,或者发生其他网络错误,则 connect 会失败,并返回错误代码。

通过命令行参数指定服务器地址和端口,连接到服务器后,可以输入并发送消息,接收服务器返回的回显消息。程序使用基本的套接字操作(如 socket(), connect(), write(), read(), 和close())进行通信。

客户端完整代码

/*
在使用的时候必须遵循的格式为:./tcpclient server_ip server_port
例如:./tcpclient 127.0.0.1
*/

#include<iostream>
#include<cstring>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include"Log.hpp"
#include"InetAddr.hpp"

using namespace log_ns;



int main(int argc,char *argv[])
{
    if (argc != 3)
    {
        std::cerr << "Usage: " << argv[0] << " server-ip server-port" << std::endl;
        exit(0);
    }

    std::string serverip=argv[1];
    uint16_t serverport=std::stoi(argv[2]);
    //创建socket
    int sockfd=::socket(AF_INET,SOCK_STREAM,0);
    if(sockfd<0)
    {
        std::cerr<<"create socket error"<<std::endl;
        exit(1);
    }
    //注意:客户端不需要显示的bind,但是一定要有自己的ip和port,所以需要隐式的bind,OS会自动bind sockfd,用自己的IP和随机端口号
    struct sockaddr_in server;
    memset(&server,0,sizeof(server));
    server.sin_family=AF_INET;
    server.sin_port=htons(serverport);
    //server.sin_addr.s_addr
    ::inet_pton(AF_INET,serverip.c_str(),&server.sin_addr);

    int n=::connect(sockfd,(struct sockaddr*)&server,sizeof(server));
    if(n<0)
    {
        std::cerr<<"connect socket error"<<std::endl;
        exit(2);
    }

    while(true)
    {
        std::string message;
        std::cout<<"Enter # ";
        std::getline(std::cin,message);

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

        char echo_buffer[1024];
        n=read(sockfd,echo_buffer,sizeof(echo_buffer));
        if(n>0)
        {
            echo_buffer[n]=0;
            std::cout<<echo_buffer<<std::endl;
        }
        else
        {
            break;
        }
    }

    ::close(sockfd);

    return 0;
}

多线程远程命令执行

popen–命令交互

#include <stdio.h>
FILE *popen(const char *command, const char *type);

参数:

  • command:要执行的外部命令(通常是一个 shell 命令)。这个命令将被传递给系统的命令解释器(比如 /bin/sh 或类似的 shell)。

  • type:指定文件流的打开方式。它决定了你如何与命令的标准输入、标准输出进行交互。

返回值:

  • 如果调用成功,popen 返回一个 FILE * 类型的指针,该指针指向一个文件流,这个流可以用来与执行的命令交互。
  • 如果调用失败,返回 NULL,并且可以通过 errno 获取错误原因。
    在这里插入图片描述

完整代码

Command.hpp

#pragma once 
#include<iostream>
#include<string>
#include<set>
#include<cstdio>
#include<cstring>
#include"Log.hpp"
#include"InetAddr.hpp"

class Command
{
public:
    Command() 
    {
        _safe_command.insert("ls");
        _safe_command.insert("touch");
        _safe_command.insert("pwd");
        _safe_command.insert("whoami");
        _safe_command.insert("which");
    }
    ~Command() {}
    bool SafeCheck(const std::string &cmdstr)
    {
        for(auto &cmd:_safe_command)
        {
            if(strncmp(cmd.c_str(),cmdstr.c_str(),cmd.size())==0)
            {
                return true;
            }
        }
        return false;
    }
    std::string Excute(const std::string &cmdstr)
    {
        if(!SafeCheck(cmdstr)) return "unsafe";
        std::string result;
        FILE *fp=popen(cmdstr.c_str(),"r");
        if(fp)
        {
            char line[1024];
            while(fgets(line,sizeof(line),fp))
            {
                result+=line;
            }
            return result;
        }
        else
        {
            return "execute error";
        }
    }
    void HandlerCommand(int sockfd,InetAddr addr)
    {
        //长服务
        while(true)
        {
            char commandbuf[1024];
            ssize_t n=::recv(sockfd,commandbuf,sizeof(commandbuf)-1,0);
            if(n>0)
            {
                commandbuf[1024]=0;
                LOG(INFO,"get command from client %s, command: %s\n",addr.AddrStr().c_str(),commandbuf);
                std::string result=Excute(commandbuf);
                send(sockfd,result.c_str(),result.size(),0);
            }
            else if(n==0)
            {
                LOG(INFO,"client %s quit\n",addr.AddrStr().c_str());
                break;
            }
            else
            {
                LOG(ERROR,"read error: %s\n",addr.AddrStr().c_str());
                break;
            }
        }
    }
private:
std::set<std::string> _safe_command;
};

TcpServerMain.cc

#include "TcpServer.hpp"
#include "Command.hpp"
#include <memory>

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        std::cerr << "Usage: " << argv[0] << " local-port" << std::endl;
        exit(0);
    }
    uint16_t port = std::stoi(argv[1]);

    Command cmdservice;
    std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(
        std::bind(&Command::HandlerCommand,
                  &cmdservice, std::placeholders::_1,
                  std::placeholders::_2),
        port);
    tsvr->InitServer();
    tsvr->Loop();

    return 0;
}

TcpServer.hpp

#pragma once
#include<iostream>
#include<cstring>
#include<unistd.h>
#include<functional>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/wait.h>    
#include<pthread.h>
#include"Log.hpp"
#include"InetAddr.hpp"

using namespace log_ns;

using command_service_t=std::function<void(int sockfd,InetAddr addr)>;

enum
{
    SOCKET_ERROR=1,
    BAND_ERROR,
    LISTEN_ERROR
};

const static int gport=8888;
const static int gsock=-1;
const static int gblcklog=8;

class TcpServer
{
public:
    TcpServer(command_service_t service,uint16_t port=gport)
        :_port(port)
        ,_listensockfd(gsock)
        ,_isrunning(false)
        ,_service(service)
    {}

    void InitServer()
    {
        //创建
        _listensockfd=::socket(AF_INET,SOCK_STREAM,0);
        if(_listensockfd<0)
        {
            exit(SOCKET_ERROR);
            LOG(FATAL,"socket create error\n");
        }
        LOG(INFO,"socket create sussess,listensockfd: %d\n",_listensockfd); //3

        struct sockaddr_in local;
        memset(&local,0,sizeof(local));

        local.sin_family=AF_INET;
        local.sin_port=htons(_port);  //网络序列
        local.sin_addr.s_addr=INADDR_ANY;
        //绑定:sockfd 和 socket addr
        if(::bind(_listensockfd,(struct sockaddr*)&local,sizeof(local))<0)
        {
            LOG(FATAL,"bind error\n");
            exit(BAND_ERROR);
        }
        LOG(INFO,"bind success\n");

        // 设置监听状态 listen
        if(::listen(_listensockfd,gblcklog)<0)
        {
            LOG(FATAL,"listen error\n");
            exit(LISTEN_ERROR);
        }
        LOG(INFO,"listen success\n");
    }

    class ThreadData
    {
    public:
        int _sockfd;
        TcpServer *_self;
        InetAddr _addr;

    public:
        ThreadData(int sockfd,TcpServer *self,const InetAddr &addr):_sockfd(sockfd),_self(self),_addr(addr)
        {}
    };

    void Loop()
    {
        //signal(SIGCHLD,SIG_IGN);
        _isrunning=true;
        while(_isrunning)
        {
            struct sockaddr_in client;
            socklen_t len=sizeof(client);
            // 获取新连接
            int sockfd=::accept(_listensockfd,(struct sockaddr*)&client,&len);
            if(sockfd<0)
            {
                //获取连接失败
                LOG(WARING,"accept error\n");
                continue;
            }
            InetAddr addr(client);
            LOG(INFO,"get a new link,client info: %s, sockfd is: %d\n",addr.AddrStr().c_str(),sockfd); //4

            //提供服务--version 2(多线程版)
            pthread_t tid;
            ThreadData *td=new ThreadData(sockfd,this,addr);
            pthread_create(&tid,nullptr,Excute,td); //新线程分离

        }

        _isrunning=false;
    }

    static void *Excute(void *argc)
    {
        ThreadData *td=static_cast<ThreadData *>(argc);
        pthread_detach(pthread_self());
        td->_self->_service(td->_sockfd,td->_addr);
        ::close(td->_sockfd);
        delete td;
        return nullptr;
    }

    ~TcpServer()
    {}

private:
    uint16_t _port;
    int _listensockfd;
    bool _isrunning;

    command_service_t _service;
};

测试

在这里插入图片描述

在这里插入图片描述

;