【Linux网络】Socket 编程TCP

news2025/4/19 23:59:15

 🌈个人主页:秦jh__https://blog.csdn.net/qinjh_?spm=1010.2135.3001.5343
🔥 系列专栏:https://blog.csdn.net/qinjh_/category_12891150.html

9efbcbc3d25747719da38c01b3fa9b4f.gif

目录

TCP socket API 详解

socket():

 bind():

 listen():

accept(): 

 connect

V0 - Echo Server 

 TcpClientMain.cc

 TcpServer.hpp

 TcpServerMain.cc

V1 - Echo Server 多进程版本 

 TcpServer.hpp

V2 -  多线程版本

TcpServer.hpp 

V3 -- 线程池版本

TcpServer.hpp

多线程远程命令执行

 Command.hpp

TcpServer.hpp


前言

    💬 hello! 各位铁子们大家好哇。

             今日更新了Linux网络编程的内容
    🎉 欢迎大家关注🔍点赞👍收藏⭐️留言📝

TCP socket API 详解

socket():

  • socket()打开一个网络通讯端口,如果成功的话,就像 open()一样返回一个文件描述符;
  • 应用程序可以像读写文件一样用 read/write 在网络上收发数据;
  • 如果 socket()调用出错则返回-1;
  • 对于 IPv4, family 参数指定为 AF_INET;
  • 对于 TCP 协议,type 参数指定为 SOCK_STREAM, 表示面向流的传输协议
  • protocol 参数的介绍从略,指定为 0 即可。 

 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. 端口号 我们定义为 8888 

 listen():

  • listen()声明 sockfd 处于监听状态, 并且最多允许有 backlog 个客户端处于连接等待状态, 如果接收到更多的连接请求就忽略, 这里设置不会太大(一般是5), 具体细节同学们课后深入研究;
  • listen()成功返回 0,失败返回-1 

accept(): 

  •  如果服务器调用 accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来;
  • addr 是一个传出参数,accept()返回时传出客户端的地址和端口号;
  • 如果给 addr 参数传 NULL,表示不关心客户端的地址;
  • addrlen 参数是一个传入传出参数(value-result argument), 传入的是调用者提供的, 缓冲区 addr 的长度以避免缓冲区溢出问题, 传出的是客户端地址结构体的实际长度(有可能没有占满调用者提供的缓冲区);

理解 accept 的返回值:

”拉客“例子:有一个饭店,张三的工作是在店门口拉客,顾客a路过,最终被张三说服进来吃饭,张三就向店内喊一声,来客人了,里面就会有服务员出来接客提供服务。而张三就继续在门口拉客。张三只负责拉客。这个饭店就相当于服务器,一个个的顾客就是新的连接,张三就是类里面的_listensockfd(监听套接字),而里面的服务员就相当于accept的返回值。 _listensockfd的作用是用来获取新连接,accept的返回值fd是用来给顾客提供服务的。 

 connect

  • 客户端需要调用 connect()连接服务器;
  • connect 和 bind 的参数形式一致, 区别在于 bind 的参数是自己的地址, 而connect 的参数是对方的地址;
  • connect()成功返回 0,出错返回-1;

V0 - Echo Server 

 TcpClientMain.cc

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

// ./tcpclient server-ip server-port
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]);

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

    //2.不需要显示的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);
    ::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;
}

 TcpServer.hpp

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

#include"Log.hpp"
#include"InetAddr.hpp"

using namespace log_ns;

enum 
{
    SOCKET_ERROR=1,
    BIND_ERROR,
    LISTEN_ERR
};


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()
    {
        //1.创建socket
        _listensockfd=::socket(AF_INET,SOCK_STREAM,0);
        if(_listensockfd<0)
        {
            LOG(FATAL,"socket create error\n");
            exit(SOCKET_ERROR);
        }
        LOG(INFO,"socket create success,sockfd:%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;

        //2.bind sockfd 和 socket addr
        if(::bind(_listensockfd,(struct sockaddr*)&local,sizeof(local))<0)
        {
            LOG(FATAL,"bind error\n");
            exit(BIND_ERROR);
        }
        LOG(INFO,"bind success\n");

        //3.因为tcp是面向连接的,tcp需要未来不断的能够做到获取连接
        if(::listen(_listensockfd,gblcklog)<0)
        {
            LOG(FATAL,"listen error\n");
            exit(BIND_ERROR);
        }
        LOG(INFO,"listen success\n");
    }

    void Loop()
    {
        _isrunning=true;
        while(_isrunning)
        {
            struct sockaddr_in client;
            socklen_t len=sizeof(client);
            //4.获取新连接
            int sockfd=::accept(_listensockfd,(struct sockaddr*)&client,&len);
            if(sockfd<0)
            {
                LOG(WARNING,"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;
    }

    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 ="[server 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;
};

 TcpServerMain.cc

#include"TcpServer.hpp"

#include<memory>

// ./tcpserver 8888
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]);
    std::unique_ptr<TcpServer> tsvr=std::make_unique<TcpServer>(port);
    tsvr->InitServer();
    tsvr->Loop();

    return 0;
}

 上面代码,只能处理一个连接。如果再启动一个客户端, 尝试连接服务器, 发现第二个客户端, 不能正确的和服务器进行通信. 因为我们 accecpt 了一个请求之后, 就在一直 while 循环尝试read, 没有继续调用到 accecpt, 导致不能接受新的请求

V1 - Echo Server 多进程版本 

父子进程都会有独立的文件描述符表,但子进程会拷贝父进程的文件描述符表,因此他们最终都会指向同样的文件。为了防止误操作,需要把父子进程各自不需要的文件描述符关掉。如果父进程不需要的文件描述符一直不关,后面再有新的连接来,就会导致父进程的文件描述符一直被打开而没有关闭,文件描述符本质就是数组的下标,也就说明可用的文件描述符会越来越少,最终就会导致文件描述符泄漏。

 TcpServer.hpp

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

#include"Log.hpp"
#include"InetAddr.hpp"

using namespace log_ns;

enum 
{
    SOCKET_ERROR=1,
    BIND_ERROR,
    LISTEN_ERR
};


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()
    {
        //1.创建socket
        _listensockfd=::socket(AF_INET,SOCK_STREAM,0);
        if(_listensockfd<0)
        {
            LOG(FATAL,"socket create error\n");
            exit(SOCKET_ERROR);
        }
        LOG(INFO,"socket create success,sockfd:%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;

        //2.bind sockfd 和 socket addr
        if(::bind(_listensockfd,(struct sockaddr*)&local,sizeof(local))<0)
        {
            LOG(FATAL,"bind error\n");
            exit(BIND_ERROR);
        }
        LOG(INFO,"bind success\n");

        //3.因为tcp是面向连接的,tcp需要未来不断的能够做到获取连接
        if(::listen(_listensockfd,gblcklog)<0)
        {
            LOG(FATAL,"listen error\n");
            exit(BIND_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);
            //4.获取新连接
            int sockfd=::accept(_listensockfd,(struct sockaddr*)&client,&len);
            if(sockfd<0)
            {
                LOG(WARNING,"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);//此时有父子孙三个进程,子进程退出,孙子进程就变成孤儿进程,就被系统领养。父进程的wait就可以立即返回。 

                Service(sockfd,addr);
                exit(0);
            }
            //father
            ::close(sockfd); 
            int n=waitpid(id,nullptr,0);
            if(n>0)
            {
                LOG(INFO,"wait child success\n");
                //父子进程都会有独立的文件描述符表,但子进程会拷贝父进程的文件描述符表,因此他们最终都会指向同样的文件
                
            }
            
        }
        _isrunning=false;
    }

    void Service(int sockfd,InetAddr addr)
    {
        //长服务
        while(true)
        {
            char inbuffer[1024];//当作字符串
            ssize_t n=::read(sockfd,inbuffer,sizeof(inbuffer)-1);
            if(n>0)
            {
                inbuffer[n]=0;
                LOG(INFO,"get message from client %s, message:%s\n",addr.AddrStr().c_str(),inbuffer);

                std::string echo_string ="[server 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;
};

V2 -  多线程版本

多进程版本效率太低,因为要创建进程,就要创建进程pcb,构建页表,还要把父进程数据拷贝给子进程,所以可以用多线程。

 

多线程中,大部分资源都是共享的。所以新老线程的文件描述符表也是共享的。而进程则是拷贝一个给新线程。所以在多线程中就不能关闭fd了。 

TcpServer.hpp 

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

#include"Log.hpp"
#include"InetAddr.hpp"

using namespace log_ns;

enum 
{
    SOCKET_ERROR=1,
    BIND_ERROR,
    LISTEN_ERR
};


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()
    {
        //1.创建socket
        _listensockfd=::socket(AF_INET,SOCK_STREAM,0);
        if(_listensockfd<0)
        {
            LOG(FATAL,"socket create error\n");
            exit(SOCKET_ERROR);
        }
        LOG(INFO,"socket create success,sockfd:%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;

        //2.bind sockfd 和 socket addr
        if(::bind(_listensockfd,(struct sockaddr*)&local,sizeof(local))<0)
        {
            LOG(FATAL,"bind error\n");
            exit(BIND_ERROR);
        }
        LOG(INFO,"bind success\n");

        //3.因为tcp是面向连接的,tcp需要未来不断的能够做到获取连接
        if(::listen(_listensockfd,gblcklog)<0)
        {
            LOG(FATAL,"listen error\n");
            exit(BIND_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);
            //4.获取新连接
            int sockfd=::accept(_listensockfd,(struct sockaddr*)&client,&len);
            if(sockfd<0)
            {
                LOG(WARNING,"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);//此时有父子孙三个进程,子进程退出,孙子进程就变成孤儿进程,就被系统领养。父进程的wait就可以立即返回。 

            //     Service(sockfd,addr);
            //     exit(0);
            // }
            // //father
            // ::close(sockfd); 
            // int n=waitpid(id,nullptr,0);
            // if(n>0)
            // {
            //     LOG(INFO,"wait child success\n");
            //     //父子进程都会有独立的文件描述符表,但子进程会拷贝父进程的文件描述符表,因此他们最终都会指向同样的文件
                
            // }

            //version 2 --多线程版本
            pthread_t tid;
            ThreadData* td=new ThreadData(sockfd,this,addr);
            pthread_create(&tid,nullptr,Execute,td);//新线程进行分离

            
        }
        _isrunning=false;
    }

    static void* Execute(void* args)
    {
        pthread_detach(pthread_self());
        ThreadData* td=static_cast<ThreadData*>(args);
        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[n]=0;
                LOG(INFO,"get message from client %s, message:%s\n",addr.AddrStr().c_str(),inbuffer);

                std::string echo_string ="[server 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;
};

V3 -- 线程池版本

TcpServer.hpp

#pragma once
#include<iostream>
#include<cstring>
#include<functional>
#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"
#include"ThreadPool.hpp"

using namespace log_ns;

enum 
{
    SOCKET_ERROR=1,
    BIND_ERROR,
    LISTEN_ERR
};


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()
    {
        //1.创建socket
        _listensockfd=::socket(AF_INET,SOCK_STREAM,0);
        if(_listensockfd<0)
        {
            LOG(FATAL,"socket create error\n");
            exit(SOCKET_ERROR);
        }
        LOG(INFO,"socket create success,sockfd:%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;

        //2.bind sockfd 和 socket addr
        if(::bind(_listensockfd,(struct sockaddr*)&local,sizeof(local))<0)
        {
            LOG(FATAL,"bind error\n");
            exit(BIND_ERROR);
        }
        LOG(INFO,"bind success\n");

        //3.因为tcp是面向连接的,tcp需要未来不断的能够做到获取连接
        if(::listen(_listensockfd,gblcklog)<0)
        {
            LOG(FATAL,"listen error\n");
            exit(BIND_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);
            //4.获取新连接
            int sockfd=::accept(_listensockfd,(struct sockaddr*)&client,&len);
            if(sockfd<0)
            {
                LOG(WARNING,"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);//此时有父子孙三个进程,子进程退出,孙子进程就变成孤儿进程,就被系统领养。父进程的wait就可以立即返回。 

            //     Service(sockfd,addr);
            //     exit(0);
            // }
            // //father
            // ::close(sockfd); 
            // int n=waitpid(id,nullptr,0);
            // if(n>0)
            // {
            //     LOG(INFO,"wait child success\n");
            //     //父子进程都会有独立的文件描述符表,但子进程会拷贝父进程的文件描述符表,因此他们最终都会指向同样的文件
                
            // }

            //version 2 --多线程版本
            // pthread_t tid;
            // ThreadData* td=new ThreadData(sockfd,this,addr);
            // pthread_create(&tid,nullptr,Execute,td);//新线程进行分离

            //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* Execute(void* args)
    {
        pthread_detach(pthread_self());
        ThreadData* td=static_cast<ThreadData*>(args);
        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[n]=0;
                LOG(INFO,"get message from client %s, message:%s\n",addr.AddrStr().c_str(),inbuffer);

                std::string echo_string ="[server 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;
};

多线程远程命令执行

recv的前三个参数及返回值和read的使用一样。flag里面有阻塞读和非阻塞读,该选项其实我们也不用。 

send的前三个参数及返回值也跟write一样,也只是多了个参数flag。

popen内部会先建立一个管道文件,然后帮我们创建子进程,之后执行exec*,执行对应的command,内部会帮我们进行命令解析。

过程如下图:子进程执行command,所有的执行结果会写到管道里,父进程就可以在管道里读,为了让我们从管道里读,就把管道的文件描述符包装成了FILE*。type表示打开命令的方式,

 Command.hpp

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include<cstdio>
#include<set>

#include "Log.hpp"
#include "InetAddr.hpp"

class Command
{
public:
    Command()
    {
        _safe_command.insert("ls");
        _safe_command.insert("touch");//touch filename
        _safe_command.insert("pwd");
        _safe_command.insert("whoami");
        _safe_command.insert("which");//which pwd
    }
    ~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.empty()?"success":result;
        }
        return "execute error";
    }
    void HandlerCommand(int sockfd, InetAddr addr)
    {
        //我们把它当作长服务
        while (true)
        {
            char commandbuf[1024]; // 当作字符串
            ssize_t n = ::recv(sockfd, commandbuf, sizeof(commandbuf) - 1,0);//设为0,默认阻塞读取
            if (n > 0)
            {
                commandbuf[n] = 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;
};

TcpServer.hpp

#pragma once
#include<iostream>
#include<cstring>
#include<functional>
#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,
    BIND_ERROR,
    LISTEN_ERR
};


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

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

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

    }
    void InitServer()
    {
        //1.创建socket
        _listensockfd=::socket(AF_INET,SOCK_STREAM,0);
        if(_listensockfd<0)
        {
            LOG(FATAL,"socket create error\n");
            exit(SOCKET_ERROR);
        }
        LOG(INFO,"socket create success,sockfd:%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;

        //2.bind sockfd 和 socket addr
        if(::bind(_listensockfd,(struct sockaddr*)&local,sizeof(local))<0)
        {
            LOG(FATAL,"bind error\n");
            exit(BIND_ERROR);
        }
        LOG(INFO,"bind success\n");

        //3.因为tcp是面向连接的,tcp需要未来不断的能够做到获取连接
        if(::listen(_listensockfd,gblcklog)<0)
        {
            LOG(FATAL,"listen error\n");
            exit(BIND_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);
            //4.获取新连接
            int sockfd=::accept(_listensockfd,(struct sockaddr*)&client,&len);
            if(sockfd<0)
            {
                LOG(WARNING,"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 2 --多线程版本
            pthread_t tid;
            ThreadData* td=new ThreadData(sockfd,this,addr);
            pthread_create(&tid,nullptr,Execute,td);//新线程进行分离
        }
        _isrunning=false;
    }

    static void* Execute(void* args)
    {
        pthread_detach(pthread_self());
        ThreadData* td=static_cast<ThreadData*>(args);
        td->_self->_service(td->_sockfd,td->_addr);
        ::close(td->_sockfd);
        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[n]=0;
    //             LOG(INFO,"get message from client %s, message:%s\n",addr.AddrStr().c_str(),inbuffer);

    //             std::string echo_string ="[server 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;
    //         }
    //     }
    // }

    ~TcpServer(){}

private:
    uint16_t _port;
    int _listensockfd;
    bool _isrunning;

    command_service_t _service;
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2338345.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ESP32-idf学习(二)esp32C3作服务端与电脑蓝牙数据交互

一、当前需求 目前是想利用蓝牙来传输命令&#xff0c;或者一些数据&#xff0c;包括电脑、手机与板子的数据传输&#xff0c;板子与板子之间的数据传输。构思是一个板子是数据接收终端&#xff0c;在电脑或手机下发指令后&#xff0c;再给其他板子相应指令&#xff0c;也需要…

NHANES指标推荐:CMI

文章题目&#xff1a;Association between cardiometabolic index and biological ageing among adults: a population-based study DOI&#xff1a;10.1186/s12889-025-22053-3 中文标题&#xff1a;成年人心脏代谢指数与生物衰老之间的关系&#xff1a;一项基于人群的研究 发…

前端单元测试实战:如何开始?

实战&#xff1a;如何开始单元测试 1.安装依赖 npm install --save-dev jest2.简单的例子 首先&#xff0c;创建一个 sum.js 文件 ./sum.js function sum(a, b) {return a b; }module.exports sum;创建一个名为 sum.test.js 的文件&#xff0c;这个文件包含了实际测试内…

react-native搭建开发环境过程记录

主要参考&#xff1a;官网的教程 https://reactnative.cn/docs/environment-setup 环境介绍&#xff1a;macos ios npm - 已装node18 - 已装&#xff0c;通过nvm进行版本控制Homebrew- 已装yarn - 已装ruby - macos系统自带的2.2版本。watchman - 正常安装Xcode - 正常安装和…

观察者模式详解与C++实现

1. 模式定义 观察者模式&#xff08;Observer Pattern&#xff09;是一种行为型设计模式&#xff0c;定义了对象间的一对多依赖关系。当一个对象&#xff08;被观察者/主题&#xff09;状态改变时&#xff0c;所有依赖它的对象&#xff08;观察者&#xff09;都会自动收到通知…

UE5 关卡序列

文章目录 介绍创建一个关卡序列编辑动画添加一个物体编辑动画时间轴显示秒而不是帧时间轴跳转到一个确定的时间时间轴的显示范围更改关键帧的动画插值方式操作多个关键帧 播放动画 介绍 类似于Unity的Animation动画&#xff0c;可以用来录制场景中物体的动画 创建一个关卡序列…

AI测试用例生成平台

AI测试用例生成平台 项目背景技术栈业务描述项目展示项目重难点 项目背景 针对传统接口测试用例设计高度依赖人工经验、重复工作量大、覆盖场景有限等行业痛点&#xff0c;基于大语言模型技术实现接口测试用例智能生成系统。 技术栈 LangChain框架GLM-4模型Prompt Engineeri…

C#中扩展方法和钩子机制使用

1.扩展方法&#xff1a; 扩展方法允许向现有类型 “添加” 方法&#xff0c;而无需创建新的派生类型、重新编译或以其他方式修改原始类型。扩展方法是一种特殊的静态方法&#xff0c;但可以像实例方法一样进行调用。 使用场景&#xff1a; 1.当无法修改某个类的源代码&#…

YOLOv5、YOLOv6、YOLOv7、YOLOv8、YOLOv9、YOLOv10、YOLOv11、YOLOv12的网络结构图

文章目录 一、YOLOv5二、YOLOv6三、YOLOv7四、YOLOv8五、YOLOv9六、YOLOv10七、YOLOv11八、YOLOv12九、目标检测系列文章 本文将给出YOLO各版本&#xff08;YOLOv5、YOLOv6、YOLOv7、YOLOv8、YOLOv9、YOLOv10、YOLOv11、YOLOv12&#xff09;网络结构图的绘制方法及图。本文所展…

03 UV

04 Display工具栏_哔哩哔哩_bilibili 讲的很棒 ctrlMMB 移动点 s 打针 ss 批量打针

AIGC-几款本地生活服务智能体完整指令直接用(DeepSeek,豆包,千问,Kimi,GPT)

Unity3D特效百例案例项目实战源码Android-Unity实战问题汇总游戏脚本-辅助自动化Android控件全解手册再战Android系列Scratch编程案例软考全系列Unity3D学习专栏蓝桥系列AIGC(GPT、DeepSeek、豆包、千问、Kimi)👉关于作者 专注于Android/Unity和各种游戏开发技巧,以及各种资…

C#/.NET/.NET Core拾遗补漏合集(25年4月更新)

前言 在这个快速发展的技术世界中&#xff0c;时常会有一些重要的知识点、信息或细节被忽略或遗漏。《C#/.NET/.NET Core拾遗补漏》专栏我们将探讨一些可能被忽略或遗漏的重要知识点、信息或细节&#xff0c;以帮助大家更全面地了解这些技术栈的特性和发展方向。 ✍C#/.NET/.N…

MySQL性能调优(三):MySQL中的系统库(简介、performance_schema)

文章目录 MySQL性能调优数据库设计优化查询优化配置参数调整硬件优化 1.MySQL中的系统库1.1.系统库简介1.2.performance_schema1.2.1.什么是performance_schema1.2.2.performance_schema使用1.2.3.检查当前数据库版本是否支持1.2.4.performance_schema表的分类1.2.5.performanc…

印度zj游戏出海代投本土网盟广告核心优势

印度游戏出海代投本土网盟广告的核心优势包括&#xff1a; 本土化广告策略&#xff1a;针对印度市场的特点&#xff0c;定制本土化的广告策略&#xff0c;吸引更多印度用户的关注和参与。 深度了解印度市场&#xff1a;对印度文化、消费习惯、网络使用习惯等有深入了解&#x…

NO.97十六届蓝桥杯备战|数论板块-最大公约数和最小公倍数|欧几里得算法|秦九韶算法|小红的gcd(C++)

约数和倍数 如果a 除以b 没有余数&#xff0c;那么a 就是b 的倍数&#xff0c;b 就是a 的约数&#xff0c;记作b ∣ a 。 约数&#xff0c;也称因数。 最⼤公约数和最⼩公倍数 最⼤公约数Greatest Common Divisor&#xff0c;常缩写为gcd。 ⼀组整数的公约数&#xff0c;是…

《软件设计师》复习笔记(11.6)——系统转换、系统维护、系统评价

目录 一、遗留系统&#xff08;Legacy System&#xff09; 定义&#xff1a; 特点&#xff1a; 演化策略&#xff08;基于价值与技术评估&#xff09;&#xff1a; 高水平 - 低价值&#xff1a; 高水平 - 高价值&#xff1a; 低水平 - 低价值&#xff1a; 低水平 - 高价…

人像面部关键点检测

此工作为本人近期做人脸情绪识别&#xff0c;CBAM模块前是否能加人脸关键点检测而做的尝试。由于创新点不是在于检测点的标注&#xff0c;而是CBAM的改进&#xff0c;因此&#xff0c;只是借用了现成库Dilb与cv2进行。 首先&#xff0c;下载人脸关键点预测模型:Index of /file…

EDID结构

EDID DDC通讯中传输显示设备数据 VGA , DVI 的EDID由128字节组成&#xff0c;hdmi的EDID增加扩展块128字节。扩展快的内容主要是和音频属性相关的&#xff0c;DVI和vga没有音频&#xff0c;hdmi自带音频&#xff0c;扩展快数据规范按照cea-861x标准。 Edid为了让pc或其他的图像…

文件包含(详解)

文件包含漏洞是一种常见的Web安全漏洞&#xff0c;其核心在于应用程序未对用户控制的文件路径或文件名进行严格过滤&#xff0c;导致攻击者能够包含并执行任意文件&#xff08;包括本地或远程恶意文件&#xff09;。 1. 文件包含原理 动态文件包含机制 开发者使用动态包含函数…

《SpringBoot中@Scheduled和Quartz的区别是什么?分布式定时任务框架选型实战》​

&#x1f31f; ​大家好&#xff0c;我是摘星&#xff01;​ &#x1f31f; 今天为大家带来的是Scheduled和Quartz对比分析&#xff1a; 新手常见困惑&#xff1a; 刚学SpringBoot时&#xff0c;我发现用Scheduled写定时任务特别简单。但当我看到同事在项目里用Quartz时&…