首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >【Linux 网络】TCP Socket 编程实战:手把手实现远程命令执行(附实操要点 + 完整代码)

【Linux 网络】TCP Socket 编程实战:手把手实现远程命令执行(附实操要点 + 完整代码)

作者头像
Yuzuriha
发布2026-01-14 19:25:57
发布2026-01-14 19:25:57
1590
举报
文章被收录于专栏:Linux网络Linux网络

前言: 上文我们讲到了UDP的Scket编程【Linux网络】基于UDP的Socket编程,实现简单聊天室-CSDN博客 本文我们来讲基于TCP的Socket编程,TCP与UDP大致上差不多,细节上注意区别。

TCP接口介绍

通用接口 (创建与销毁)

socket()

代码语言:javascript
复制
int socket(int domain, int type, int protocol);

作用:创建一个套接字相当于买了一部手机,但还没插卡)

参数:
domain: 协议域,常用 AF_INET (IPv4)
type: 套接字类型,TCP 必选 SOCK_STREAM (面向字节流)
protocol: 协议,通常填 0 (自动选择 TCP)
返回值:成功返回文件描述符 (sockfd),失败返回 -1

close()

代码语言:javascript
复制
int close(int fd);

作用:关闭连接,释放资源(相当于挂断电话)

注意:TCP 是全双工的,close 会引发四次挥手断开连接
服务器端专用接口 (被动连接)

bind()

代码语言:javascript
复制
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

作用:将套接字与具体的 IP地址 和 端口号 绑定(相当于给手机插上 SIM 卡,固定号码)

参数:需要传入一个 sockaddr_in 结构体(强转为 sockaddr*),包含 IP 和 Port

注意:服务器通常绑定 INADDR_ANY,表示接收本机所有网卡的连接

listen()

代码语言:javascript
复制
int listen(int sockfd, int backlog);

作用:将套接字设置为 监听状态(相当于打开手机铃声,准备接电话)

参数:backlog 指的是全连接队列的长度(即已经完成三次握手但还没被 accept 取走的连接数)

注意:调用后,TCP 状态机进入 LISTEN 状态

accept()

代码语言:javascript
复制
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

作用:从已完成连接队列中获取一个连接(相当于接起电话)

返回值:非常重要!!!
        成功返回一个新的文件描述符(connfd),专门用于和这个特定的客户端通信



区别:
    socket() 返回的 fd 是“门迎”,只负责揽客(监听)
    accept() 返回的 fd 是“服务员”,只负责服务这一个客人(通信)
客户端专用接口 (主动连接)

connect()

代码语言:javascript
复制
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

作用:向服务器发起连接请求(相当于拨打电话)

流程:调用该函数会触发 TCP 的 三次握手

返回值:0 表示连接成功,-1 表示失败
数据传输接口 (通用)

read() / recv()

代码语言:javascript
复制
ssize_t read(int fd, void *buf, size_t count);

作用:从套接字读取数据

返回值:
> 0: 实际读取到的字节数
= 0: 对端关闭了连接 (读到了 EOF,非常重要的判断条件)
< 0: 出错 (需检查 errno,如 EAGAIN 表示非阻塞模式下暂无数据)

write() / send()

代码语言:javascript
复制
ssize_t write(int fd, const void *buf, size_t count);

作用:向套接字写入数据

注意:TCP 是流式协议,发送的数据可能会被拆包或粘包,需要应用层协议(如添加包头长度)来解决。

使用接口实现简单的客户端与服务端

TcpClient.cc

代码语言:javascript
复制
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include "Log.hpp"
#include "Comment.hpp"
#include "InetAddr.hpp"
using namespace LogModule;

void Usage(std::string s)
{
    std::cerr << "Please use: " << s << " ip prot" << std::endl;
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(TCPCLIENT_ERR);
    }

    // 创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        LOG(LogLevel::ERROR) << "socket error";
        exit(SOCK_ERR);
    }

    // 绑定信息,但由OS自动绑定,无需我们自己绑定

    // 申请建立连接请求
    uint16_t prot = stoi(argv[2]);
    InetAddr addr(argv[1], prot);
    int n = connect(sockfd, addr.Getaddr(), addr.Len());
    if (n < 0)
    {
        LOG(LogLevel::ERROR) << "connect error";
        exit(CONN_ERR);
    }

    while (true)
    {
        cout << "Please Enter@ ";
        string str;
        cin >> str;
        // 向服务器发送信息
        write(sockfd, str.c_str(), str.size());

        // 接收服务器的信息
        char buff[1024];
        int n = read(sockfd, buff, sizeof(buff));
        if (n > 0)
        {
            buff[n] = 0;
            cout << "Eoch: " << buff << endl;
        }
    }
}

TcpServer.cc

代码语言:javascript
复制
#include "TcpServer.hpp"

void usage(string str)
{
    cerr << "Please use: " << str << " prot" << endl;
}

int main(int agrc, char *argv[])
{
    if (agrc != 2)
    {
        usage(argv[0]);
        exit(TCPSERVER_ERR);
    }

    uint16_t prot = stoi(argv[1]);
    TcpServer tcp(prot);
    tcp.Init();
    tcp.Run();
}

TcpServer.hpp

代码语言:javascript
复制
#pragma once

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <functional>
#include <unistd.h>
#include "Log.hpp"
#include "Comment.hpp"
#include "InetAddr.hpp"
using namespace LogModule;

const static int backlog = 8;

string Func(string str, struct sockaddr_in addr)
{
    string s = "hello";
    return s + str;
}

class TcpServer : public NoCopy
{
private:
    using func_t = std::function<string(string, struct sockaddr_in)>;

public:
    TcpServer(uint16_t prot, func_t func = Func)
        : _func(func),
          _prot(prot)
    {
    }

    void Init()
    {
        // 创建套接字:数据类似为流式
        _listensockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_listensockfd < 0)
        {
            LOG(LogLevel::ERROR) << "socket error";
            exit(SOCK_ERR);
        }
        LOG(LogLevel::INFO) << "socket success: " << _listensockfd; // 3

        // 绑定
        InetAddr inetaddr(_prot);
        int n = bind(_listensockfd, inetaddr.Getaddr(), inetaddr.Len());
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "bind error";
            exit(BIND_ERR);
        }
        LOG(LogLevel::INFO) << "bind success: " << _listensockfd; // 3

        // 将套接字设置为listen状态:TCP服务器处于listen状态下,才可以被连接
        int m = listen(_listensockfd, backlog);
        if (m < 0)
        {
            LOG(LogLevel::FATAL) << "listen error";
            exit(LISTEN_ERR);
        }
        LOG(LogLevel::INFO) << "listen success: " << _listensockfd; // 3
    }

    void Run()
    {
        while (true)
        {
            // 获取连接,建立通信(若获取连接失败,则再次进行获取)
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            // accept返回一个新的fd,由新的套接字提供服务
            _sockfd = accept(_listensockfd, (struct sockaddr *)&peer, &len);
            if (_sockfd < 0)
            {
                LOG(LogLevel::ERROR) << "accept error";
                continue;
            }

            // 连接获取成功,执行对应方法
            Service(peer);
            // 任务执行完毕
            close(_sockfd);
            break;
        }
    }

    void Service(struct sockaddr_in &peer)
    {
        while (true)
        {
            // 接收客户端信息
            // a. n>0: 读取成功
            // b. n<0: 读取失败
            // c. n==0: 对端把链接关闭了,读到了文件的结尾 --- pipe
            char buff[1024];
            ssize_t n = read(_sockfd, buff, sizeof(buff) - 1);
            if (n > 0)
            {
                buff[n] = 0;
                cout << "tcpclient say: " << buff << endl;

                // 调用方法
                string ret = _func(string(buff), peer);

                // 发送信息
                write(_sockfd, ret.c_str(), ret.size());
            }
            else if (n == 0)
            {
                LOG(LogLevel::INFO) << "读取到了文件末尾~";
                return;
            }
            else
            {
                LOG(LogLevel::ERROR) << "读取失败!";
                return;
            }
        }
    }

private:
    int _listensockfd;
    int _sockfd;
    uint16_t _prot;
    func_t _func;
};

完善服务器代码

将服务器的代码逻辑由单进程,改进为多线程,让其可以同时服务于多个用户

代码语言:javascript
复制
#pragma once

#include <pthread.h>
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <functional>
#include <unistd.h>
#include "Log.hpp"
#include "Comment.hpp"
#include "InetAddr.hpp"
using namespace LogModule;

const static int backlog = 8;

string Func(string str)
{
    string s = "hello";
    return s + str;
}

class TcpServer : public NoCopy
{
private:
    using func_t = std::function<string(string)>;

public:
    TcpServer(uint16_t prot, func_t func = Func)
        : _func(func),
          _prot(prot)
    {
    }

    void Init()
    {
        // 创建套接字:数据类似为流式
        _listensockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_listensockfd < 0)
        {
            LOG(LogLevel::ERROR) << "socket error";
            exit(SOCK_ERR);
        }
        LOG(LogLevel::INFO) << "socket success: " << _listensockfd; // 3

        // 绑定
        InetAddr inetaddr(_prot);
        int n = bind(_listensockfd, inetaddr.Getaddr(), inetaddr.Len());
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "bind error";
            exit(BIND_ERR);
        }
        LOG(LogLevel::INFO) << "bind success: " << _listensockfd; // 3

        // 将套接字设置为listen状态:TCP服务器处于listen状态下,才可以被连接
        int m = listen(_listensockfd, backlog);
        if (m < 0)
        {
            LOG(LogLevel::FATAL) << "listen error";
            exit(LISTEN_ERR);
        }
        LOG(LogLevel::INFO) << "listen success: " << _listensockfd; // 3
    }

    // 作为参数传入线程执行函数中
    class ThreadDate
    {
    public:
        ThreadDate(sockaddr_in &addr, int sockfd, TcpServer *th)
            : _addr(addr),
              _th(th),
              _sockfd(sockfd)
        {
        }

        sockaddr_in _addr;
        TcpServer *_th;
        int _sockfd;
    };

    static void *route(void *args)
    {
        // 进行线程分离,避免当其他线程之间的互相影响
        pthread_detach(pthread_self());

        ThreadDate *date = static_cast<ThreadDate *>(args);
        date->_th->Service(date->_addr, date->_sockfd);
        return nullptr;
    }

    void Run()
    {
        while (true)
        {
            // 获取连接,建立通信(若获取连接失败,则再次进行获取)
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            // accept返回一个新的fd,由新的套接字提供服务
            _sockfd = accept(_listensockfd, (struct sockaddr *)&peer, &len);
            if (_sockfd < 0)
            {
                LOG(LogLevel::ERROR) << "accept error";
                continue;
            }

            // 多线程版本
            pthread_t tid;
            ThreadDate date(peer, _sockfd, this);
            pthread_create(&tid, nullptr, route, &date);
        }
    }

    // 实现翻译功能!
    void Service(struct sockaddr_in &peer, int sockfd)
    {
        while (true)
        {
            // 接收客户端信息
            // a. n>0: 读取成功
            // b. n<0: 读取失败
            // c. n==0: 对端把链接关闭了,读到了文件的结尾 --- pipe
            char buff[1024];
            ssize_t n = read(sockfd, buff, sizeof(buff) - 1);
            if (n > 0)
            {
                buff[n] = 0;
                cout << "tcpclient say: " << buff << endl;

                // 调用方法
                string ret = _func(string(buff));

                // 发送信息
                write(sockfd, ret.c_str(), ret.size());
            }
            else if (n == 0)
            {
                LOG(LogLevel::INFO) << "读取到了文件末尾~";
                return;
            }
            else
            {
                LOG(LogLevel::ERROR) << "读取失败!";
                return;
            }
        }
    }

private:
    int _listensockfd;
    int _sockfd;
    uint16_t _prot;
    func_t _func;
};

其次客户端的读取、发送,作为两个单独的进程(线程)运行,有效避免读取于发送串行阻塞的问题(既没有输入的情况下,就不能接收服务器发送的信息),但这个我们暂时不做处理!

实现远程命令执行模块

popen()

代码语言:javascript
复制
#include <stdio.h>

FILE *popen(const char *command, const char *type);

作用:将字符串转化为命令,并将命令的执行结果返回


command:要执行的 Shell 命令字符串(如 "ls -l", "grep error")。它支持所有 Shell 语法(通配符、管道符等)

type:打开模式(二选一):
"r" (读模式):命令的标准输出 -> 你的程序(你可以读取命令打印的结果)
"w" (写模式):你的程序 -> 命令的标准输入(你可以把数据喂给命令)

返回值
成功:返回一个标准文件指针 FILE *。你可以直接用 fgets、fscanf 等文件操作函数来读写它
失败:返回 NULL

配套关闭:pclose

popen返回的指针,使用完必须调用 pclose(FILE *stream) 来关闭!它不仅关闭文件流,还会等待命令执行完毕,回收子进程资源(防止僵尸进程)

cmd.hpp

可以根据自己的需要执行任意指令,我这里为了安全起见,只开放个别指令用于执行!

代码语言:javascript
复制
#pragma once

#include <iostream>
#include <vector>
#include <stdio.h>
#include <string>
#include "Log.hpp"
using namespace LogModule;

class Cmd
{
public:
    // 初始化命令集
    Cmd()
    {
        arr.push_back("ls");
        arr.push_back("pwd");
        arr.push_back("whoami");
        arr.push_back("who");
        arr.push_back("tree .");
    }

    bool Isin(std::string command)
    {
        for (auto &e : arr)
        {
            if (e == command)
                return true;
        }
        return false;
    }

    string Run(std::string command)
    {
        // 判断是否在规则命令中
        if (!Isin(command))
        {
            return "非法命令!";
        }

        // popen函数,将字符串解析为命令,并执行后返回结果
        FILE *file = popen(command.c_str(), "r");
        if (file == nullptr)
        {
            LOG(LogLevel::ERROR) << "该命令不存在";
        }

        // 获取执行后的结果
        char ret[1024];
        string str;
        while (fgets(ret, sizeof(ret), file))
        {
            str += ret;
        }

        // 不再使用
        pclose(file);
        return "the result is: \n" + str;
    }

private:
    std::vector<std::string> arr;
};

完整代码实现

TcpServer.hpp
代码语言:javascript
复制
#pragma once

#include <pthread.h>
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <functional>
#include <unistd.h>
#include "Log.hpp"
#include "Comment.hpp"
#include "InetAddr.hpp"
using namespace LogModule;

const static int backlog = 8;

string Func(string str)
{
    string s = "hello";
    return s + str;
}

class TcpServer : public NoCopy
{
private:
    using func_t = std::function<string(string)>;

public:
    TcpServer(uint16_t prot, func_t func = Func)
        : _func(func),
          _prot(prot)
    {
    }

    void Init()
    {
        // 创建套接字:数据类似为流式
        _listensockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_listensockfd < 0)
        {
            LOG(LogLevel::ERROR) << "socket error";
            exit(SOCK_ERR);
        }
        LOG(LogLevel::INFO) << "socket success: " << _listensockfd; // 3

        // 绑定
        InetAddr inetaddr(_prot);
        int n = bind(_listensockfd, inetaddr.Getaddr(), inetaddr.Len());
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "bind error";
            exit(BIND_ERR);
        }
        LOG(LogLevel::INFO) << "bind success: " << _listensockfd; // 3

        // 将套接字设置为listen状态:TCP服务器处于listen状态下,才可以被连接
        int m = listen(_listensockfd, backlog);
        if (m < 0)
        {
            LOG(LogLevel::FATAL) << "listen error";
            exit(LISTEN_ERR);
        }
        LOG(LogLevel::INFO) << "listen success: " << _listensockfd; // 3
    }

    // 作为参数传入线程执行函数中
    class ThreadDate
    {
    public:
        ThreadDate(sockaddr_in &addr, int sockfd, TcpServer *th)
            : _addr(addr),
              _th(th),
              _sockfd(sockfd)
        {
        }

        sockaddr_in _addr;
        TcpServer *_th;
        int _sockfd;
    };

    static void *route(void *args)
    {
        // 进行线程分离,避免当其他线程之间的互相影响
        pthread_detach(pthread_self());

        ThreadDate *date = static_cast<ThreadDate *>(args);
        date->_th->Service(date->_addr, date->_sockfd);
        return nullptr;
    }

    void Run()
    {
        while (true)
        {
            // 获取连接,建立通信(若获取连接失败,则再次进行获取)
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            // accept返回一个新的fd,由新的套接字提供服务
            _sockfd = accept(_listensockfd, (struct sockaddr *)&peer, &len);
            if (_sockfd < 0)
            {
                LOG(LogLevel::ERROR) << "accept error";
                continue;
            }

            // 多线程版本
            pthread_t tid;
            ThreadDate date(peer, _sockfd, this);
            pthread_create(&tid, nullptr, route, &date);
        }
    }

    // 实现翻译功能!
    void Service(struct sockaddr_in &peer, int sockfd)
    {
        while (true)
        {
            // 接收客户端信息
            // a. n>0: 读取成功
            // b. n<0: 读取失败
            // c. n==0: 对端把链接关闭了,读到了文件的结尾 --- pipe
            char buff[1024];
            ssize_t n = read(sockfd, buff, sizeof(buff) - 1);
            if (n > 0)
            {
                buff[n] = 0;
                cout << "tcpclient say: " << buff << endl;

                // 调用方法
                string ret = _func(string(buff));

                // 发送信息
                write(sockfd, ret.c_str(), ret.size());
            }
            else if (n == 0)
            {
                LOG(LogLevel::INFO) << "读取到了文件末尾~";
                return;
            }
            else
            {
                LOG(LogLevel::ERROR) << "读取失败!";
                return;
            }
        }
    }

private:
    int _listensockfd;
    int _sockfd;
    uint16_t _prot;
    func_t _func;
};
TcpServer.cc
代码语言:javascript
复制
#include "TcpServer.hpp"
#include "cmd.hpp"

void usage(string str)
{
    cerr << "Please use: " << str << " prot" << endl;
}

int main(int agrc, char *argv[])
{
    if (agrc != 2)
    {
        usage(argv[0]);
        exit(TCPSERVER_ERR);
    }

    uint16_t prot = stoi(argv[1]);
    Cmd cmd;

    TcpServer tcp(prot, [&cmd](string command)
                  { return cmd.Run(command); });
    tcp.Init();
    tcp.Run();
}
Log.hpp
代码语言:javascript
复制
// 实现日志模块

#pragma once
#include <iostream>
#include <sstream>    // 包含stringstream类
#include <filesystem> //C++17文件操作接口库
#include <fstream>
#include <sys/types.h>
#include <unistd.h>
#include "Mutex.hpp"
using namespace std;
using namespace MutexModule;

// 补充:外部类只能通过内部类的实例化对象,来访问内部类中的方法与成员,且受修饰符限制
//       内部类可以直接访问外部类的方法以及成员,没有限制

namespace LogModule
{
    const string end = "\r\n";

    // 实现刷新策略:a.向显示器刷新 b.向指定文件刷新

    // 利用多态机制实现
    // 包含至少一个纯虚函数的类称为抽象类,不能实例化,只能被继承
    class LogStrategy // 基类
    {
    public:
        //"=0"声明为纯虚函数。纯虚函数强制派生类必须重写该函数
        virtual void SyncLog(const string &message) = 0;
    };

    // 向显示器刷新:子类
    class ConsoleLogStrategy : public LogStrategy
    {
    public:
        void SyncLog(const string &message) override
        {
            // 加锁,访问显示器,显示器也是临界资源
            LockGuard lockguard(_mutex);
            cout << message << end;
        }

    private:
        Mutex _mutex;
    };

    // 向指定文件刷新:子类
    const string defaultpath = "./log";
    const string defaultfile = "my.log";
    class FileLogStrategy : public LogStrategy
    {
    public:
        FileLogStrategy(const string &path = defaultpath, const string &file = defaultfile)
            : _path(path), _file(file)
        {
            LockGuard lockguard(_mutex);
            // 判断路径是否存在,如果不存在就创建对应的路径
            if (!(filesystem::exists(_path)))
                filesystem::create_directories(_path);
        }

        void SyncLog(const string &message) override
        {
            // 合成最后路径
            string Path = _path + (_path.back() == '/' ? "" : "/") + _file;
            // 打开文件
            ofstream out(Path, ios::app);
            out << message << end;
        }

    private:
        string _path;
        string _file;
        Mutex _mutex;
    };

    //

    // 日志等级
    // enum class:强类型枚举。1.必须通过域名访问枚举值 2.枚举值不能隐式类型转化为整型
    enum class LogLevel
    {
        DEBUG,   // 调试级
        INFO,    // 信息级
        WARNING, // 警告级
        ERROR,   // 错误级
        FATAL    // 致命级
    };

    //

    // 将等级转化为字符串
    string LevelToStr(LogLevel level)
    {
        switch (level)
        {
        case LogLevel::DEBUG:
            return "DEBUG";
        case LogLevel::INFO:
            return "DEBUG";
        case LogLevel::WARNING:
            return "WARNING";
        case LogLevel::ERROR:
            return "ERROR";
        case LogLevel::FATAL:
            return "FATAL";
        default:
            return "UNKOWN";
        }
    }

    // 获取时间
    string GetTime()
    {
        // time函数:获取当前系统的时间戳
        // localtime_r函数:将时间戳转化为本地时间(可重入函数,localtime则是不可重入函数)
        // struct tm结构体,会将转化之后的本地时间存储在结构体中
        time_t curr = time(nullptr);
        struct tm curr_time;
        localtime_r(&curr, &curr_time);
        char buffer[128];
        snprintf(buffer, sizeof(buffer), "%04d-%02d-%02d %02d:%02d:%02d",
                 curr_time.tm_year + 1900, // 年份是从1900开始计算的,需要加上1900才能得到正确的年份
                 curr_time.tm_mon + 1,     // 月份了0~11,需要加上1才能得到正确的月份
                 curr_time.tm_mday,        // 日
                 curr_time.tm_hour,        // 时
                 curr_time.tm_min,         // 分
                 curr_time.tm_sec);        // 秒

        return buffer;
    }

    //

    // 实现日志信息,并选择刷新策略
    class Logger
    {
    public:
        Logger()
        {
            // 默认选择显示器刷新
            Strategy = make_unique<ConsoleLogStrategy>();
        }

        void EnableConsoleLogStrategy()
        {
            Strategy = make_unique<ConsoleLogStrategy>();
        }

        void EnableFileLogStrategy()
        {
            Strategy = make_unique<FileLogStrategy>();
        }

        // 日志信息
        class LogMessage
        {
        public:
            LogMessage(const LogLevel &level, const string &name, const int &line, Logger &logger)
                : _level(level),
                  _name(name),
                  _logger(logger),
                  _line_member(line)
            {
                _pid = getpid();
                _time = GetTime();
                // 合并:日志信息的左半部分

                stringstream ss; // 创建输出流对象,stringstream可以将输入的所有数据全部转为为字符串
                ss << "[" << _time << "] "
                   << "[" << LevelToStr(_level) << "] "
                   << "[" << _pid << "] "
                   << "[" << _name << "] "
                   << "[" << _line_member << "] "
                   << " - ";

                // 返回ss中的字符串
                _loginfo = ss.str();
            }

            // 日志文件的右半部分:可变参数,重载运算符<<

            // e.g. <<"huang"<<123<<"dasd"<<24
            template <class T>
            LogMessage &operator<<(const T &message) // 引用返回可以让后续内容不断追加
            {
                stringstream ss;
                ss << message;
                _loginfo += ss.str();

                // 返回对象!
                return *this;
            }

            // 销毁时,将信息刷新
            ~LogMessage()
            {
                // 日志文件
                _logger.Strategy->SyncLog(_loginfo);
            }

        private:
            string _time;
            LogLevel _level;
            pid_t _pid;
            string _name;
            int _line_member;
            string _loginfo; // 合并之后的一条完整信息

            // 日志对象
            Logger &_logger;
        };

        // 重载运算符(),便于创建LogMessage对象
        // 这里返回临时对象:当临时对象销毁时,调用对应的析构函数,自动对象中创建好的日志信息进行刷新!
        // 其次局部对象也不能传引用返回!
        LogMessage operator()(const LogLevel &level, const string &name, const int &line)
        {
            return LogMessage(level, name, line, *this);
        }

    private:
        unique_ptr<LogStrategy> Strategy;
    };

    // 为了用户使用更方便,我们使用宏封装一下
    Logger logger;

// 切换刷新策略
#define Enable_Console_LogStrategy() logger.EnableConsoleLogStrategy();
#define Enable_File_LogStrategy() logger.EnableFileLogStrategy();
// 创建日志,并刷新
//__FILE__ 和 __LINE__ 是编译器预定义的宏,作用是获取当前代码所在的文件名、行号
#define LOG(level) logger(level, __FILE__, __LINE__) // 细节:不加;
};
cmd.hpp
代码语言:javascript
复制
#pragma once

#include <iostream>
#include <vector>
#include <stdio.h>
#include <string>
#include "Log.hpp"
using namespace LogModule;

class Cmd
{
public:
    // 初始化命令集
    Cmd()
    {
        arr.push_back("ls");
        arr.push_back("pwd");
        arr.push_back("whoami");
        arr.push_back("who");
        arr.push_back("tree .");
    }

    bool Isin(std::string command)
    {
        for (auto &e : arr)
        {
            if (e == command)
                return true;
        }
        return false;
    }

    string Run(std::string command)
    {
        // 判断是否在规则命令中
        if (!Isin(command))
        {
            return "非法命令!";
        }

        // popen函数,将字符串解析为命令,并执行后返回结果
        FILE *file = popen(command.c_str(), "r");
        if (file == nullptr)
        {
            LOG(LogLevel::ERROR) << "该命令不存在";
        }

        // 获取执行后的结果
        char ret[1024];
        string str;
        while (fgets(ret, sizeof(ret), file))
        {
            str += ret;
        }

        // 不再使用
        pclose(file);
        return "the result is: \n" + str;
    }

private:
    std::vector<std::string> arr;
};
TcpClient.cc
代码语言:javascript
复制
#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include "Log.hpp"
#include "Comment.hpp"
#include "InetAddr.hpp"
using namespace LogModule;

void Usage(std::string s)
{
    std::cerr << "Please use: " << s << " ip prot" << std::endl;
}

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(TCPCLIENT_ERR);
    }

    // 创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        LOG(LogLevel::ERROR) << "socket error";
        exit(SOCK_ERR);
    }

    // 绑定信息,但由OS自动绑定,无需我们自己绑定

    // 申请建立连接请求
    uint16_t prot = stoi(argv[2]);
    InetAddr addr(argv[1], prot);
    int n = connect(sockfd, addr.Getaddr(), addr.Len());
    if (n < 0)
    {
        LOG(LogLevel::ERROR) << "connect error";
        exit(CONN_ERR);
    }

    while (true)
    {
        cout << "Please Enter@ ";
        string str;
        cin >> str;
        // 向服务器发送信息
        write(sockfd, str.c_str(), str.size());

        // 接收服务器的信息
        char buff[1024];
        int n = read(sockfd, buff, sizeof(buff));
        if (n > 0)
        {
            buff[n] = 0;
            cout << "Eoch: " << buff << endl;
        }
    }
}
Comment.hpp
代码语言:javascript
复制
#pragma once

// 禁止拷贝类
class NoCopy
{
public:
    NoCopy() {};
    // 拷贝构造
    NoCopy(const NoCopy &nocopy) = delete;
    // 赋值重载
    const NoCopy &operator=(const NoCopy &nocopy) = delete;
};

enum EXIT
{
    OK = 0,
    SOCK_ERR,
    BIND_ERR,
    LISTEN_ERR,
    ACCP_ERR,
    TCPSERVER_ERR,
    TCPCLIENT_ERR,
    CONN_ERR
};
InetAddr.hpp
代码语言:javascript
复制
#pragma once
#include <iostream>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string>
#include <cstring>

// 实现网络地址与主机地址的转换

class InetAddr
{
public:
    // 网络转主机
    InetAddr(struct sockaddr_in &addr)
        : _addr(addr)
    {
        _prot = ntohs(_addr.sin_port); // 网络地址转主机地址
        char buff[1024];
        inet_ntop(AF_INET, &addr.sin_addr, buff, sizeof(buff)); // 将4字节网络风格的IP -> 点分十进制的字符串风格的IP
        _ip = std::string(buff);
    }

    // 主机转网络(客户端)
    InetAddr(std::string ip, uint16_t prot)
        : _ip(ip),
          _prot(prot)
    {
        memset(&_addr, 0, sizeof(_addr));
        _addr.sin_family = AF_INET;
        //&addr.sin_addr 是一个指向 struct in_addr 的指针,其内存地址等价于 &(addr.sin_addr.s_addr)(因为结构体的起始地址就是第一个成员的起始地址)
        inet_pton(AF_INET, _ip.c_str(), &_addr.sin_addr);
        _addr.sin_port = htons(_prot);
    }

    // 主机转网络(服务端)
    InetAddr(uint16_t prot)
        : _prot(prot)
    {
        memset(&_addr, 0, sizeof(_addr));
        _addr.sin_family = AF_INET;
        _addr.sin_port = htons(prot);
        _addr.sin_addr.s_addr = INADDR_ANY; // 任意ip地址
    }

    // 直接获取sockaddr_in
    struct sockaddr *Getaddr()
    {
        return (struct sockaddr *)&_addr;
    }

    int Len()
    {
        return sizeof(_addr);
    }

    uint16_t prot()
    {
        return _prot;
    }

    std::string ip()
    {
        return _ip;
    }

    // 运算符重载
    bool operator==(InetAddr &addr)
    {
        return _prot == addr._prot && _ip == addr._ip;
    }

    std::string Getname()
    {
        return _ip + ':' + std::to_string(_prot);
    }

private:
    struct sockaddr_in _addr;
    uint16_t _prot;
    std::string _ip;
};
Mutex.hpp
代码语言:javascript
复制
// 封装锁接口
#pragma once
#include <pthread.h>

namespace MutexModule
{
    class Mutex
    {
    public:
        Mutex()
        {
            pthread_mutex_init(&mutex, nullptr);
        }

        ~Mutex()
        {
            pthread_mutex_destroy(&mutex);
        }

        void Lock()
        {
            pthread_mutex_lock(&mutex);
        }

        void Unlock()
        {
            pthread_mutex_unlock(&mutex);
        }

        pthread_mutex_t *Get()
        {
            return &mutex;
        }

    private:
        pthread_mutex_t mutex;
    };

    class LockGuard
    {
    public:
        LockGuard(Mutex &mutex)
            : _Mutex(mutex)
        {
            _Mutex.Lock();
        }

        ~LockGuard()
        {
            _Mutex.Unlock();
        }

    private:
        Mutex &_Mutex;
    };
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2026-01-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • TCP接口介绍
    • 通用接口 (创建与销毁)
    • 服务器端专用接口 (被动连接)
    • 客户端专用接口 (主动连接)
    • 数据传输接口 (通用)
  • 使用接口实现简单的客户端与服务端
  • 完善服务器代码
  • 实现远程命令执行模块
  • 完整代码实现
    • TcpServer.hpp
    • TcpServer.cc
    • Log.hpp
    • cmd.hpp
    • TcpClient.cc
    • Comment.hpp
    • InetAddr.hpp
    • Mutex.hpp
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档