【计算机网络】基于TCP进行socket编程——实现客户端到服务端远程命令行操作

发布于:2025-05-26 ⋅ 阅读:(23) ⋅ 点赞:(0)

🔥个人主页🔥:孤寂大仙V
🌈收录专栏🌈:计算机网络
🌹往期回顾🌹: 【计算机网络】基于UDP进行socket编程——实现服务端与客户端业务
🔖流水不争,争的是滔滔不息


一、TCPsocket编程

TCP Socket 编程是一种基于传输控制协议(TCP)的网络通信方式,用于在客户端和服务器之间建立可靠的双向连接。TCP 提供面向连接、可靠的数据传输,适用于需要确保数据完整性和顺序的应用场景,如网页浏览、文件传输等。

1.1基本接口

1.1.1通用接口

创建套接字

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

参数,IPV4:AF_INET,TCP:SOCK_STREAM,标志位一般为0。失败返回-1。

关闭套接字

 int close(int fd);

发送数据

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

返回值是返回发送的字节数,第一个参数套接字,第二个参数buffer,第三个参数是buffer大小,第四个参数是标志位。

接收数据

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

返回值是返回接收到的字节数,第一个参数套接字,第二个参数buffer,第三个参数是buffer大小,第四个参数是标志位。

TCP中发送和接收,用write和read也行。


1.1.2服务器主要流程

绑定端口号和ip

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

绑定本地ip和端口

监听

int listen(int sockfd, int backlog);

监听端口,backlog表示等待连接的队列的最大长度。

accept,返回新的socket描述符用于通信

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

接收客户端的请求,返回一个新的socket描述符,原来的socket描述符用于监听,新的用来和客户端通信。

1.1.3客户端主要流程

connect

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

主动连接服务器端的socket。

1.2 接口理解

服务端

  1. bind是绑定本地主机的ip和端口号。
  2. listen是开始监听,把绑定好的socket变成监听状态,等待客户端来连接。打个比喻也就是说listen相当于“开门营业”让内核知道这个socket是服务端的,准备接电话了。backlog表示等待连接的队列的最大长度,“最多有多少个客户拍队”。
  3. accept是接收连接,从监听 socket 中拿出一个连接请求,生成一个新的 socket,用于和客户端通信。你可以理解成服务员开门迎客,把客户带到一个新的包间聊天。返回的新 socket 是专用于与这个客户端通信的,原来的 socket 继续监听下一个连接。

客户端
connect是客户端主动给服务端发送连接请求,去找服务端,可以理解为客户端给客户端打电话。

二、TCP实现客户端到服务器远程网络命令行操作

Tcpserver.hpp

#include "Common.hpp"

using namespace std;
using namespace LogModule;

using func_t =function<string(const string&,InetAddr&)>;

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

class Tcpserver : public NoCopy
{
public:
    Tcpserver(uint16_t port,func_t func)
    :_port(port)
    ,_listensockfd(defaultsockfd)
    ,_sockfd(defaultsockfd)
    ,_isrunning(false)
    ,_func(func)
    {

    }
    
    void Init()
    {
        _listensockfd=socket(AF_INET,SOCK_STREAM,0);    //创建套接字
        if(_listensockfd<0)
        {
            LOG(LogLevel::FATAL)<<"socket error";
            exit(SOCKET_ERR);
        }
        LOG(LogLevel::INFO)<<"socket success";

        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;

        int n=bind(_listensockfd,(struct sockaddr*)&local,sizeof(local));   //绑定
        if(n<0)
        {
            LOG(LogLevel::FATAL)<<"bind error";
            exit(BIND_ERR);
        }
        LOG(LogLevel::INFO)<<"bind sucess";

        n=listen(_listensockfd,backlog);    //监听
        if(n<0)
        {
            LOG(LogLevel::FATAL)<<"listen error";
            exit(LISTEN_ERR);
        }
        LOG(LogLevel::INFO)<<"listen sucess";
    }

    void Server(int _sockfd,InetAddr& peer)
    {
        while(true)
        {
            char buffer[1024];
            ssize_t n=read(_sockfd,buffer,sizeof(buffer)-1);
            if(n>0)
            {
                buffer[n]=0;
                LOG(LogLevel::DEBUG) << peer.StringAddr() << " #" << buffer;
                string echo_string=_func(buffer,peer);
                write(_sockfd,echo_string.c_str(),echo_string.size());
            }
            else if(n==0)
            {
                LOG(LogLevel::DEBUG)<<"退出";
                close(_sockfd);
                break;
            }
            else
            {
                LOG(LogLevel::FATAL)<<"异常";
                close(_sockfd);
                break;
            }
        }
    }

    class ThreadDate
    {
    public:
        ThreadDate(int fd,InetAddr& ad,Tcpserver* th)
        :sockfd(fd)
        ,addr(ad)
        ,tsvr(th)
        {

        }
        ~ThreadDate()
        {

        }
        int sockfd;
        InetAddr addr;
        Tcpserver* tsvr;
    };

    static void* Routine(void* args)    //线程处理任务
    {
        pthread_detach(pthread_self());
        ThreadDate* td=static_cast<ThreadDate*>(args);
        td->tsvr->Server(td->sockfd,td->addr);
        delete td;
        return nullptr;
    }

    void Run()
    {
        _isrunning=true;
        while(_isrunning)
        {
            struct sockaddr_in peer; //客户端(网络)传来
            socklen_t len=sizeof(peer);
            _sockfd=accept(_listensockfd,(struct sockaddr*)&peer,&len);     //接收连接
            if(_sockfd<0)
            {
                LOG(LogLevel::WARNING)<<"accept error";
                continue;
            }
            LOG(LogLevel::INFO)<<"accept sucess";

            InetAddr addr(peer);    //转为主机序列

            pthread_t tid;                      //每当一个客户端连接,就创建线程去服务客户端想要干的事
            ThreadDate* td=new ThreadDate(_sockfd,addr,this);
            pthread_create(&tid,nullptr,Routine,td);    //真正创建线程的,把td就是那些信息传进去到Routine
        }
        _isrunning=false;
    }

    ~Tcpserver()
    {

    }
private:
    bool _isrunning;
    int _listensockfd;
    int _sockfd;
    uint16_t _port;
    func_t _func;
};


服务器初始化

 void Init()
    {
        _listensockfd=socket(AF_INET,SOCK_STREAM,0);    //创建套接字
        if(_listensockfd<0)
        {
            LOG(LogLevel::FATAL)<<"socket error";
            exit(SOCKET_ERR);
        }
        LOG(LogLevel::INFO)<<"socket success";

        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;

        int n=bind(_listensockfd,(struct sockaddr*)&local,sizeof(local));   //绑定
        if(n<0)
        {
            LOG(LogLevel::FATAL)<<"bind error";
            exit(BIND_ERR);
        }
        LOG(LogLevel::INFO)<<"bind sucess";

        n=listen(_listensockfd,backlog);    //监听
        if(n<0)
        {
            LOG(LogLevel::FATAL)<<"listen error";
            exit(LISTEN_ERR);
        }
        LOG(LogLevel::INFO)<<"listen sucess";
    }

创建套接字,绑定本地ip和端口号,把socket搞成监听状态。


有客户端connect给服务端发送连接请求,开始执行

 void Run()
    {
        _isrunning=true;
        while(_isrunning)
        {
            struct sockaddr_in peer; //客户端(网络)传来
            socklen_t len=sizeof(peer);
            _sockfd=accept(_listensockfd,(struct sockaddr*)&peer,&len);     //接收连接
            if(_sockfd<0)
            {
                LOG(LogLevel::WARNING)<<"accept error";
                continue;
            }
            LOG(LogLevel::INFO)<<"accept sucess";

            InetAddr addr(peer);    //转为主机序列

            pthread_t tid;                      //每当一个客户端连接,就创建线程去服务客户端想要干的事
            ThreadDate* td=new ThreadDate(_sockfd,addr,this);
            pthread_create(&tid,nullptr,Routine,td);    //真正创建线程的,把td就是那些信息传进去到Routine
        }
        _isrunning=false;
    }

accept,服务端接收客户端的连接,创建新的套接字。这里采用来一个客户端信息创建一个线程的方式ThreadDate* td=new ThreadDate(_sockfd,addr,this);把连接信息打包成 ThreadDate 对象(this是当前类的对象)。为啥封装成结构体?pthread_create 的第四个参数只能传 void*,所以你封装成 ThreadDate*,传给线程函数后可以解包用。下面是ThreadDate的构造的代码

    class ThreadDate
    {
    public:
        ThreadDate(int fd,InetAddr& ad,Tcpserver* th)
        :sockfd(fd)
        ,addr(ad)
        ,tsvr(th)
        {

        }
        ~ThreadDate()
        {

        }
        int sockfd;
        InetAddr addr;
        Tcpserver* tsvr;
    };

    static void* Routine(void* args)    //线程处理任务
    {
        pthread_detach(pthread_self());
        ThreadDate* td=static_cast<ThreadDate*>(args);
        td->tsvr->Server(td->sockfd,td->addr);
        delete td;
        return nullptr;
    }

Routine 是线程处理函数;传进去的参数是 ThreadDate* 类型的 td。在Routine中线程分离,解包然后通过td调用server进入回调函数。


void Server(int _sockfd,InetAddr& peer)
    {
        while(true)
        {
            char buffer[1024];
            ssize_t n=read(_sockfd,buffer,sizeof(buffer)-1);
            if(n>0)
            {
                buffer[n]=0;
                LOG(LogLevel::DEBUG) << peer.StringAddr() << " #" << buffer;
                string echo_string=_func(buffer,peer);
                write(_sockfd,echo_string.c_str(),echo_string.size());
            }
            else if(n==0)
            {
                LOG(LogLevel::DEBUG)<<"退出";
                close(_sockfd);
                break;
            }
            else
            {
                LOG(LogLevel::FATAL)<<"异常";
                close(_sockfd);
                break;
            }
        }
    }

从缓冲区中读数据,然后回调函数对客户端传来的信息进行解析,然后在返回去。
在这里插入图片描述

Tcpclient.cc

#include "Common.hpp"

using namespace std;
using namespace LogModule;

int main(int argc, char *argv[])
{
    string server_ip = argv[1];
    uint16_t server_port = stoi(argv[2]);

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        LOG(LogLevel::FATAL) << "socket error";
        exit(SOCKET_ERR);
    }
    LOG(LogLevel::INFO) << "socket success";

    struct sockaddr_in server;
    server.sin_family = AF_INET;
    server.sin_port = htons(server_port);
    server.sin_addr.s_addr = inet_addr(server_ip.c_str());

    int n=connect(sockfd,(struct sockaddr*)&server,sizeof(server));
    if(n<0)
    {
        LOG(LogLevel::FATAL)<<"connect error";
        exit(CONNECT_ERR);
    }
    LOG(LogLevel::INFO)<<"connect sucess";

    while(true)
    {
        string line;
        cout<<"请输入"<<endl;
        getline(cin,line);
        write(sockfd,line.c_str(),line.size());

        char buffer[1024];
        int n=read(sockfd,buffer,sizeof(buffer)-1);
        if(n>0)
        {
            buffer[n]=0;
            cout<<buffer<<endl;
        }
    }

    close(sockfd);
    return 0;
}

创建套接字,connect去连接服务器,连接上服务器,给服务器发信息,读服务器的应答。

Command.hpp

#pragma once

#include <iostream>
#include <string>
#include <cstdio>
#include <set>
#include "Command.hpp"
#include "InetAddr.hpp"
#include "Log.hpp"

using namespace LogModule;

class Command
{
public:

    std::string Execute(const std::string &cmd, InetAddr &addr)
    {
        std::string who = addr.StringAddr();

        // 2. 执行命令
        FILE *fp = popen(cmd.c_str(), "r");
        if(nullptr == fp)
        {
            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:
};

执行命令的方法

Tcpserver.cc

#include "Tcpserver.hpp"
using namespace std;
using namespace LogModule;

int main(int argc,char* argv[])
{
    uint16_t port=stoi(argv[1]);
    Enable_Console_Log_Strategy(); // 启用控制台输出
    Command cmd;
    unique_ptr<Tcpserver> u=make_unique<Tcpserver>(port,
        [&cmd](const string& buffer,InetAddr& peer){
            return cmd.Execute(buffer,peer);
    });
    u->Init();
    u->Run();
    return 0;
}

lambda表达式,对服务端的回调函数进行处理,让客户端发来去的信息去执行命令行操作。

在这里插入图片描述
客户端到服务端远程命令行操作:源码


网站公告

今日签到

点亮在社区的每一天
去签到