44. TCP -23Linux聊天室实现命令符功能

🔥个人主页: Milestone-里程碑

❄️个人专栏: <<力扣hot100>> <<C++>><<Linux>>

🌟心向往之行必能至

目录

[一 : Command.hpp :封装命令符的使用](#一 : Command.hpp :封装命令符的使用)

[1.1 相关接口](#1.1 相关接口)

二.TcpServer.hpp

三.TcpClient.cc

[四.TcpServer.cc 两种方式进行传参](#四.TcpServer.cc 两种方式进行传参)

五.总结


上一篇文章中,我们实现了TCP增加英文查找中文的功能,本篇文章,让我们实现多人聊天时,可以实现Linux命令符的使用

一 : Command.hpp :封装命令符的使用

实现命令符的使用,我们前面写过的自定义shell里也实现了,可以直接CV过来,但此处我们引入新的接口进行实现

先将允许的命令符加入白名单,再进行判断执行还是退出

bash 复制代码
#pragma once
#include<set>
#include"InetAddr.hpp"
#include "Log.hpp"
#include"Common.hpp"
using namespace LogModule;
class Command{
    public:
    Command(){
        _WhiteList.insert("ls");
        _WhiteList.insert("who");
        _WhiteList.insert("pwd");
        _WhiteList.insert("ll");
        _WhiteList.insert("whoami");
        _WhiteList.insert("ls -l");
    }

    bool IsWLC(const std::string &com)
    {
        auto iter =_WhiteList.find(com);
        return iter==_WhiteList.end();
    }
    std::string Excute(const std::string&cmd,InetAddr&addr)
    {
        if(IsWLC(cmd))
        {
            return std::string("坏人");
            // exit(com_err);
        }
        
        FILE*fp=popen(cmd.c_str(),"r");
        if(fp==nullptr)
        {
            return std::string("你要执行的命令没有: ")+cmd;
        }
        std::string who=addr.StringAddr();
        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:
    std::set<std::string> _WhiteList;
};

1.1 相关接口

popen:"创建管道并执行 shell 命令

bash 复制代码
POPEN(3)                                                                   Linux Programmer's Manual                                                                   POPEN(3)

NAME
       popen, pclose - pipe stream to or from a process

SYNOPSIS
       #include <stdio.h>

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

       int pclose(FILE *stream);

   Feature Test Macro Requirements for glibc (see feature_test_macros(7)):

       popen(), pclose():
           _POSIX_C_SOURCE >= 2
               || /* Glibc versions <= 2.19: */ _BSD_SOURCE || _SVID_SOURCE
bash 复制代码
RETURN VALUE
       popen(): on success, returns a pointer to an open stream that can be used to read or write to the pipe; if the fork(2) or pipe(2) calls fail, or if the function  cannot
       allocate memory, NULL is returned.

       pclose(): on success, returns the exit status of the command; if wait4(2) returns an error, or some other error is detected, -1 is returned.

       Both functions set errno to an appropriate value in the case of an error.

pclose :关闭由popen创建的管道流

bash 复制代码
POPEN(3)                                                                   Linux Programmer's Manual                                                                   POPEN(3)

NAME
       popen, pclose - pipe stream to or from a process

SYNOPSIS
       #include <stdio.h>

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

       int pclose(FILE *stream);

   Feature Test Macro Requirements for glibc (see feature_test_macros(7)):

       popen(), pclose():
           _POSIX_C_SOURCE >= 2
               || /* Glibc versions <= 2.19: */ _BSD_SOURCE || _SVID_SOURCE
bash 复制代码
RETURN VALUE
       popen(): on success, returns a pointer to an open stream that can be used to read or write to the pipe; if the fork(2) or pipe(2) calls fail, or if the function  cannot
       allocate memory, NULL is returned.

       pclose(): on success, returns the exit status of the command; if wait4(2) returns an error, or some other error is detected, -1 is returned.

       Both functions set errno to an appropriate value in the case of an error.

下面文件的代码基本与前面写的一样,直接CV,小幅改动即可

二.TcpServer.hpp

bash 复制代码
#pragma once
#include <sys/types.h>
#include <unistd.h>
#include <wait.h>
#include <signal.h>
#include <pthread.h>
#include<functional>
#include "InetAddr.hpp"
#include "Common.hpp"
#include "Log.hpp"
#include"ThreadPool.hpp"
using namespace LogModule;
using namespace ThreadPoolModule;
const int defaultsocket = -1;
using task_t=std::function<void()>;
using func_t=std::function<std::string(const std::string &word,InetAddr &addr)>;
class TcpServer : NoCopy // 服务器不可拷贝
{
public:
    TcpServer(uint16_t &port,func_t func)
        : _listensocket(defaultsocket), _port(port), _isrunning(false),_func(func)
    {
    }
    void Init()
    {
        // signal(SIGCHLD, SIG_IGN); // 忽略SIG_IGN信号
        // 1.创建套接字
        _listensocket = socket(AF_INET, SOCK_STREAM, 0);
        if (_listensocket < 0)
        {
            LOG(LogLevel::ERROR) << "socket fail";
            exit(socket_err);
        }
        LOG(LogLevel::INFO) << "socket success" << _listensocket;
        // 2. bind
        InetAddr local(_port);
        int n = bind(_listensocket, local.NetAddrPtr(), local.NetAddrLen());
        if (n < 0)
        {
            LOG(LogLevel::ERROR) << "bind fail" << _listensocket;
            exit(bind_err);
        }
        LOG(LogLevel::INFO) << "bind success" << _listensocket;
        // 进行监听
        int backlog = 5;
        n = listen(_listensocket, backlog);
        if (n < 0)
        {
            LOG(LogLevel::ERROR) << "listen fail" << _listensocket;
            exit(listen_err);
        }
        LOG(LogLevel::INFO) << "listen success" << _listensocket;
    }
    class ThreadData
    {
    public:
        ThreadData(int fd, InetAddr &ar, TcpServer *s) : sockfd(fd), addr(ar), tsvr(s) {}

    public:
        int sockfd;
        InetAddr addr;
        TcpServer *tsvr;
    };
    static void *Rotinue(void *argv)
    {
        ThreadData *td = static_cast<ThreadData *>(argv);
        // 脱离主线程
        pthread_detach(pthread_self());
        td->tsvr->Service(td->sockfd, td->addr);
        delete td;
        return nullptr;
    }
    // 短服务
    // 长服务: 多进程多线程比较合适
    void Service(int sockfd, InetAddr &peer)
    {
        // 1. 先读取数据
        // a. n>0: 读取成功
        // b. n<0: 读取失败
        // c. n==0: 对端把链接关闭了,读到了文件的结尾 --- pipe
        char buf[1024];

        while (true)
        {
            ssize_t n = read(sockfd, buf, sizeof(buf) -  1);
            if (n < 0)
            {
                LOG(LogLevel::ERROR) << "read fail";
                close(sockfd);
                exit(read_err);
            }
            else if (n == 0)
            {
                LOG(LogLevel::DEBUG) << peer.StringAddr() << "exit ";
                close(sockfd);
                break;
            }
            else
            {
                //1.读到单词
                //2. 读到命令
                buf[n] = 0;
                
                std::string echo_string = _func(std::string(buf),peer);
                LOG(LogLevel::INFO) << peer.StringAddr() << " #" << buf<<"->"<<echo_string;

                write(sockfd, echo_string.c_str(), echo_string.size());
            }
        }
    }
    void Start()
    {
        _isrunning = true;
        while (_isrunning)
        {
            // a. 获取链接
            struct sockaddr_in peer;
            socklen_t len = sizeof(sockaddr_in);
            // 如果没有连接,accept就会阻塞
            int sockfd = accept(_listensocket, CONV(peer), &len);
            if (sockfd < 0)
            {
                LOG(LogLevel::WARNING) << "accept error";
                continue;
            }
            InetAddr addr(peer);
            LOG(LogLevel::INFO) << "accept success, peer addr : " << addr.StringAddr();
            // version0 -- text version 单线程版本
            //  Service(sockfd, addr);
            // version1 -- 多线程版本
            // pid_t id = fork();
            // if (id < 0)
            // {
            //     LOG(LogLevel::ERROR) << "fork fail";
            //     exit(fork_err);
            // }
            // // 父与子都关闭不需要的端口
            // else if (id == 0)
            // {
            //     close(_listensocket);
            //     if(fork()>0)
            //         exit(OK);
            //         //孙子进程
            //     Service(sockfd, addr);
            //     exit(OK);
            // }
            // else
            // {
            //     close(sockfd);
            //     pid_t rid = waitpid(id, nullptr, 0);
            //     (void)rid;
            //     //父进程要等待子进程,要不然僵尸,但等待与之前的一样,等于没处理
            //     //解决方法:
            //     //1. 设置为非阻塞状态,不推荐
            //     //2. 自定义信号(不够优雅)
            //     //3.  利用子进程创建一个孙进程,让子进程退出,孙进程就成了孤儿进程,由系统领养
            // }
            // version 2 ---多线程版本
            ThreadData *td = new ThreadData(sockfd, addr, this);
            pthread_t tid;
            pthread_create(&tid, nullptr, Rotinue, td);
            //version 3 --线程池版本,一般适合处理短服务
            // // 将新链接和客户端构建一个新任务,push线程池中  
            // ThreadPool<task_t>::GetInstance()->Enqueue([this,sockfd,&addr]
            // (){
            //     this->Service(sockfd,addr);
            // });
        }
        _isrunning = false;
    }
    ~TcpServer() {}

private:
    int _listensocket; // 监听socket
    uint16_t _port;
    bool _isrunning;
    func_t _func;
};

三.TcpClient.cc

bash 复制代码
#include <iostream>
#include "Common.hpp"
#include "Log.hpp"
#include "InetAddr.hpp"
using namespace LogModule;
int main(int args, char *argv[])
{
    if (args != 3)
    {
        LOG(LogLevel::ERROR) << "USAG" << argv[0] << " ip port";
        exit(usgv_err);
    }
    std::string ip = argv[1];
    uint16_t port = std::stoi(argv[2]);
    // 1.创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        LOG(LogLevel::ERROR) << "sockfd error";
        exit(socket_err);
    }
    LOG(LogLevel::DEBUG) << "sockfd success";
    // 无需显示绑定,系统随随机绑定端口号
    //  2. 我应该做什么呢?listen?accept?都不需要!!
    //  2. 直接向目标服务器发起建立连接的请求
    InetAddr serveraddr(ip, port);
    int n = connect(sockfd, serveraddr.NetAddrPtr(), serveraddr.NetAddrLen());
    if (n < 0)
    {
        LOG(LogLevel::ERROR) << "connect fail";
        exit(connect_err);
    }
    LOG(LogLevel::DEBUG) << "connect success";

    while (true)
    {
        // 1.写P
        LOG(LogLevel::DEBUG) << "Please input#";
        std::string line;
        std::getline(std::cin, line);
        ssize_t n = write(sockfd, line.c_str(), line.size());

        // 2.读
        char buf[1024];
        n = read(sockfd, buf, sizeof(buf) - 1);
        if (n > 0)
        {
            buf[n] = 0;
            LOG(LogLevel::INFO) << "server echo#" << buf;
        }
    }
    return 0;
}

四.TcpServer.cc 两种方式进行传参

bash 复制代码
#include"TcpServer.hpp"
#include"Commad.hpp"
#include<iostream>
int main(int args,char*argv[])
{
    if(args!=2)
    {
        std::cout<<"USGR: "<<argv[0]<<"port";
        exit(usgv_err);
    }
    uint16_t port=std::stoi(argv[1]);
    Command cmd;
    //两种绑定 1. lambda 2.bind
    // std::unique_ptr<TcpServer> tcp=std::make_unique<TcpServer>(port,[&cmd]( const std::string&word,InetAddr &addr){return cmd.Excute(word,addr);});
    // 2.
    func_t f=std::bind(&Command::Excute,&cmd,std::placeholders::_1,std::placeholders::_2);
     std::unique_ptr<TcpServer> tcp=std::make_unique<TcpServer>(port,f);
    tcp->Init();
    tcp->Start();
     return 0;
}   

五.总结

  1. 有了前面代码的基础,此处最大的变动就是封装一个Commad的类
  2. 在学习中,如果有新的知识,应该去学习,而非CV之前的
  3. 需要熟练使用bind和lambda
相关推荐
丶小鱼丶2 小时前
数据结构和算法之【二叉树】
java·数据结构·算法
2301_793804692 小时前
模板代码安全性增强
开发语言·c++·算法
SimonKing2 小时前
OpenClaw,再见!
java·后端·程序员
softbangong2 小时前
829-批量提取各子文件夹下文件到一级目录
java·服务器·前端·自动化工具·批量文件处理·文件提取工具·文件夹整理
干啥啥不行,秃头第一名2 小时前
C++中的观察者模式
开发语言·c++·算法
SuperEugene2 小时前
Vue3 + Vue Router + Pinia 路由守卫规范:beforeEach 应做 / 不应做,避死循环、防重复请求|状态管理与路由规范篇
开发语言·前端·javascript·vue.js·前端框架
魑-魅-魍-魉2 小时前
Maven 构建报错:无法连接私有仓库及依赖传输失败
java·maven
Thomas.Sir2 小时前
精通 MySQL 面试题
数据结构·数据库·mysql
小王不爱笑1322 小时前
Java 泛型详解
java·windows·python