深入了解linux网络—— TCP网络通信(下)

前言

学习了TCP通信相关的接口使用,实现了基本的通信,现在来基于TCP通信实现翻译功能、远程SHELL

翻译功能

对于翻译功能,实现起来还是非常容易的,直接复用之前实现好的Dict类;

TcpServer中新增一个成员变量_func,表示未来信息的处理方法。

这样在server接收到信息时,只需回调_func即可。

cpp 复制代码
//tcpserver.hpp
using func_t = std::function<std::string(std::string)>;
class TcpServer
{
    void Server(int rwfd, InetAddr &addr)
    {
        while (true)
        {
            char english[256];
            int rn = read(rwfd, english, sizeof(english) - 1);
            if (rn < 0)
            {
                // read出错
                LOG(Level::ERROR) << "read error";
                break;
            }
            else if (rn == 0)
            {
                // write端退出
                LOG(Level::INFO) << "writer is exit";
                break;
            }
            // 读取成功
            english[rn] = '\0';
            std::string chinese = _func(english);
            std::cout << addr.ToString() << " : " << english << " -> " << chinese << std::endl;
            int wn = write(rwfd, chinese.c_str(), chinese.size());
            if (wn < 0)
            {
                LOG(Level::ERROR) << "write error";
                break;
            }
        }
    }
public:
    TcpServer(uint16_t port, func_t func) : _sockfd(-1), _port(port), _func(func)
    {
    }
    ~TcpServer() {}
    void Init()
    {
        // 1. socket
        _sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_sockfd < 0)
        {
            LOG(Level::FATAL) << "socket error";
            exit(1);
        }
        LOG(Level::DEBUG) << "socket success";
        // 2. bind
        InetAddr addr(_port);
        int b = bind(_sockfd, addr.GetInetAddr(), addr.GetLen());
        if (b < 0)
        {
            LOG(Level::FATAL) << "bind error";
            exit(2);
        }
        LOG(Level::DEBUG) << "bind success";
        int l = listen(_sockfd, 5);
        if (l < 0)
        {
            LOG(Level::FATAL) << "listen error";
            exit(3);
        }
        LOG(Level::DEBUG) << "listen success";
    }
    class ThreadData
    {
    public:
        ThreadData(int fd, TcpServer *tsvr, InetAddr addr)
            : _fd(fd), _tsvr(tsvr), _addr(addr)
        {
        }
        int _fd;
        TcpServer *_tsvr;
        InetAddr _addr;
    };
    static void *Routinue(void *argv)
    {
        pthread_detach(pthread_self());
        ThreadData *td = static_cast<ThreadData *>(argv);
        td->_tsvr->Server(td->_fd, td->_addr);
        return nullptr;
    }
    void Start()
    {
        while (true)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            bzero(&peer, len);
            int rwfd = accept(_sockfd, (struct sockaddr *)&peer, &len);
            if (rwfd < 0)
            {
                LOG(Level::FATAL) << "accept error";
                exit(4);
            }
            LOG(Level::DEBUG) << "accept success";
            // 多线程
            pthread_t tid;

            ThreadData *td = new ThreadData(rwfd, this, peer);
            pthread_create(&tid, nullptr, Routinue, td);
        }
    
private:
    int _sockfd;
    uint16_t _port;
    func_t _func;
};

这样在tcpserver启动时,先加载字典,然后创建TcpServer对象,启动服务端即可。

cpp 复制代码
//tcpserver.cc
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        std::cout << "usage : " << argv[0] << " port" << std::endl;
        exit(1);
    }
    uint16_t port = std::stoi(argv[1]);
    Dict d;
    d.Load();
    TcpServer tsvr(port, [&d](std::string english) -> std::string
                   { return d.Translate(english); });
    tsvr.Init();
    tsvr.Start();
    return 0;
}

远程shell

这里简单实现远程shell,实现原理很简单:

简单设计一个InetShell类,其中存储着可以远程执行的命令(防止恶意操作)

再由InetShell通过一个方式去执行远端命令;

然后通过回调,在server接受到信息后,回调InetShell中的执行命令的方法;

最后,将执行的结果,通过返回值传给serverserver再将返回值发送给远端。

1. 设计InetShell

首先,在InetShell中要保存可以执行的命令(防止恶意操作),这里就直接使用unordered_set来存储。

cpp 复制代码
class InetShell
{
    InetShell()
    {
        _whitelist.insert("ls");
        _whitelist.insert("ll");
        _whitelist.insert("pwd");
        _whitelist.insert("who");
        _whitelist.insert("whoami");
        _whitelist.insert("touch test.txt");
        _whitelist.insert("mkdir test");
    }

private:
    std::unordered_set<std::string> _whitelist;
};

2. 执行命令

InetShell中,要实现一个执行命令的方法Execute

在执行该命令之前,就要先判断当前命令是否安全(是否在_whitelist中);

在确认安全后,就要执行命令:

这里执行命令,要命令行解析、进行程序替换等等;

这里就不做这些操作了,可以直接使用popenpopen:将传递进来的字符当中命令行信息,进行命令行解析,执行命令;最后执行结果以文件的形式返回一个FILE*类型的指针)

所以,这里在确认命令安全之后,就可以直接调用popen,将命令行信息传递进去,以读方式打开(r)。

然后就可以使用C语言文件读取的方法将执行结果读取出来(fgets);

将读取的内容拼接成字符串;读取完毕之后关闭文件即可。

cpp 复制代码
    std::string Execute(const std::string &com)
    {
        if (IsWhite(com))
            return "unsafe";
        FILE *fp = popen(com.c_str(), "r");
        if (fp == NULL)
            return std::string();
        char buff[1024];
        std::string result;
        while (fgets(buff, sizeof(buff), fp))
        {
            result += buff;
        }
        pclose(fp);
        return result;
    }

这样,在server中通过回调,执行该方法,获取执行结果,然后再将结果发送给远端。

这里就通过简单的服务(翻译、远程shell),来熟悉TCP通信。

补充

这里Tcp通信,所实现的TcpServerUDP通信所实现的UdpServer都是不希望被拷贝的;我们可以通过删除拷贝构造和拷贝赋值来保证不被拷贝;

但是这里就实现一个nocopy类,该类删除了拷贝构造和拷贝赋值,TcpServer继承nocopy类,从而TcpServer也就不能被拷贝了。

cpp 复制代码
class nocopy
{
public:
    nocopy() {}
    ~nocopy() {}
    nocopy(const nocopy &) = delete;
    const nocopy &operator=(const nocopy &) = delete;
};

此外,这里当程序出现问题,socket失败、bind失败调用exit退出,退出码都使用的是数字;这里就可以设计一个枚举类型,将退出码一个个列举出来,方便查错。

cpp 复制代码
enum ERR
{
    OK = 0,
    SOCKET_ERR,
    BIND_ERR,
    LISTEN_ERR,
    ACCEPT_ERR,
    CONNECT_ERR,
    FORK_ERR
};

到这里,本篇文章内容就结束了,感谢支持

我的博客即将同步至腾讯云开发者社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=2oul0hvapjsws

相关推荐
Bruce_Liuxiaowei4 小时前
MQTT协议在物联网环境中的安全风险与防范指南
运维·网络·物联网·安全·网络安全
Paul_09204 小时前
golang面经——内存相关模块
服务器·网络·golang
yenggd4 小时前
vxlan-bgp-evnp分布式网关配置案例
网络·分布式·华为
CiLerLinux9 小时前
第四十九章 ESP32S3 WiFi 路由实验
网络·人工智能·单片机·嵌入式硬件
Lu Zelin9 小时前
单片机为什么不能跑Linux
linux·单片机·嵌入式硬件
CS Beginner10 小时前
【Linux】 Ubuntu 开发环境极速搭建
linux·运维·ubuntu
ajassi200010 小时前
开源 C++ QT QML 开发(二)工程结构
linux·qt·qml
今天只学一颗糖10 小时前
Linux学习笔记--insmod 命令
linux·笔记·学习
摩羯座-1856903059411 小时前
爬坑 10 年!京东店铺全量商品接口实战开发:从分页优化、SKU 关联到数据完整性闭环
linux·网络·数据库·windows·爬虫·python