TCP套接字

目录

一、echo版本

二、Dict翻译版本

三、Command命令行解析版本


一、echo版本

单进程echo版本

定义一个Common.hpp其中定义多个文件共同使用的一些变量

Common.hpp

cpp 复制代码
#pragma once

enum ExitCode
{
    OK = 0,
    USAGE_ERR,
    SOCKET_ERR,
    BIND_ERR,
    LISTEN_ERR,
    CONNECT_ERR,
    FORK_ERR
};

#define CONV(addr) ((struct sockaddr*)&addr)


// 服务器往往是禁止拷贝的
// 设计一个类其中删除拷贝构造和赋值重载
// 之后让TcpServer继承这个类
// TcpServer想要拷贝构造和赋值重载就先要完成对基类的拷贝构造和赋值重载
// 而这个类又不能拷贝构造和赋值重载,这样就保证了服务器不能被拷贝
class NoCopy
{
public:
    NoCopy(){}
    ~NoCopy(){}
    NoCopy(const NoCopy &) = delete;
    const NoCopy &operator=(const NoCopy &) = delete;
};

InetAddr.hpp

cpp 复制代码
#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string>
#include "Common.hpp"
class InetAddr
{
private:
    struct sockaddr_in _sockaddr_in;
    std::string _ip;
    uint16_t _port;
public:
    // 构造时传入套接字信息 而得出ip端口(网络转主机)
    InetAddr(struct sockaddr_in& sockaddr_in):_sockaddr_in(sockaddr_in)
    {
        _ip = inet_ntoa(_sockaddr_in.sin_addr);
        _port = ntohs(_sockaddr_in.sin_port);
    }
    // 构造时传入端口得出网络套接字信息(主机转网络)
    InetAddr(uint64_t port):_ip(), _port(port)
    {
        bzero(&_sockaddr_in, sizeof(_sockaddr_in));
        _sockaddr_in.sin_family = AF_INET;
        _sockaddr_in.sin_port = htons(_port);
        _sockaddr_in.sin_addr.s_addr = INADDR_ANY;
    }
    // 构造时传入ip以及端口得出网络套接字信息(主机转网络)
    InetAddr(std::string ip, uint64_t port):_ip(ip), _port(port)
    {
        bzero(&_sockaddr_in, sizeof(_sockaddr_in));
        _sockaddr_in.sin_family = AF_INET;
        _sockaddr_in.sin_port = htons(_port);
        inet_pton(AF_INET, _ip.c_str(), &_sockaddr_in.sin_addr);
    }
    // 直接转为 struct sockaddr * 并且返回
    struct sockaddr * NetAddr()
    {
        return CONV(_sockaddr_in);
    }
    // 获取struct sockaddr_in长度
    socklen_t AddrLen()
    {
        return sizeof(_sockaddr_in);
    }

    struct sockaddr_in& SockAddrIn()
    {
        return _sockaddr_in;
    }
    uint16_t Port() {return _port;}
    std::string Ip() {return _ip;}
    bool operator==(const InetAddr &inetaddr)
    {
        return _ip == inetaddr._ip && _port == inetaddr._port;
    }
    std::string StringAddr()
    {
        std::string stringaddr = "[ " + _ip + " | " + std::to_string(_port) + " ]";  
        return stringaddr;
    }

    ~InetAddr()
    {

    }
};

Init中要设置socket状态为监听,因为TCP是面向连接的,服务器要一直监听,看有没有客户端想要建立连接

接口介绍

#include <sys/types.h> /* See NOTES */

#include <sys/socket.h>

int listen(int sockfd, int backlog);

第一个参数是监听的套接字id,后面是允许被监听的客户端队列的大小

RETURN VALUE

On success, zero is returned. On error, -1 is returned, and errno is set appropriately.

TcpServer->Init()

cpp 复制代码
    void Init()
    {
        // 申请套接字
        _listen_sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_listen_sockfd == -1)
        {
            logger(LogLevel::FATAL) << "socket error!";
            exit(SOCKET_ERR);
        }
        logger(LogLevel::INFO) << "socket success, _listen_sockfd : " << _listen_sockfd;
        
        // 绑定端口
        InetAddr addr(_port);
        int n = bind(_listen_sockfd, addr.NetAddr(), addr.AddrLen());
        if (n != 0)
        {
            logger(LogLevel::FATAL) << "bind error!";
            exit(BIND_ERR);            
        }
        logger(LogLevel::INFO) << "bind success, _listen_sockfd : " << _listen_sockfd;
        
        // 设置socket状态为监听
        // 因为TCP是面向连接的,服务器要一直监听,看有没有客户端想要建立连接
        n = listen(_listen_sockfd, backlog);
        if (n != 0)
        {
            logger(LogLevel::FATAL) << "listen error!";
            exit(LISTEN_ERR); 
        }
        logger(LogLevel::INFO) << "listen success";
    }

TcpServer->Run()

将_isrunning改为true,之后进入死循环,服务器本身就是死循环的

首先获取连接,Tcp是基于连接的,只要服务器处于监听状态那么就可以被连接。

接口介绍

#include <sys/types.h> /* See NOTES */

#include <sys/socket.h>

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

第二三个参数是一个输入型参数,获取连接对方,也就是客户端的套接字信息

RETURN VALUE

On success, these system calls return a file descriptor for the accepted socket (a nonnegative integer). On error, -1 is returned, errno is set appropriately, and addrlen is left un‐changed.

获取成功返回值是一个文件描述符,那和成员属性的_sockfd有什么区别?成员属性应该是进行监听或者说是获取链接专用的,而这里是获取连接之后的用来提供服务的sockfd,后序读写IO就用这个,因为是文件描述符,读写IO接口和文件那一块是一样的

accept只是获取连接,是从内核中获取的,而建立连接的过程和accept操作无关

若是暂时没有连接accept就会阻塞

cpp 复制代码
    void Service(int sockfd, InetAddr &peer)
    {
        char buffer[1024];
        while (true)
        {
            // 1. 先读取数据
            // a. n>0: 读取成功
            // b. n<0: 读取失败
            // c. n==0: 对端把链接关闭了,读到了文件的结尾 --- pipe
            ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);
            if (n > 0)
            {
                // buffer是一个英文单词 or 是一个命令字符串
                buffer[n] = 0;
                logger(LogLevel::DEBUG) << peer.StringAddr() << " #" << buffer;
                // 2. 写回数据
                std::string echo_string = "echo# ";
                echo_string += buffer;
                write(sockfd, echo_string.c_str(), echo_string.size());
            }
            else if (n == 0)
            {
                logger(LogLevel::DEBUG) << peer.StringAddr() << " 退出了...";
                close(sockfd);
                break;
            }
            else
            {
                logger(LogLevel::DEBUG) << peer.StringAddr() << " 异常...";
                close(sockfd);
                break;
            }
        }
    }
    void Run()
    {
        _isrunning = true;
        while (_isrunning)
        {
            // 获取连接
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int sockfd = accept(_listen_sockfd, CONV(peer), &len);
            if (sockfd < 0)
            {
                logger(LogLevel::WARNING) << "accept error";
                continue;
            }
            InetAddr addr(peer);
            logger(LogLevel::INFO) << "accept success, peer addr : " << addr.StringAddr();
            Service(sockfd, addr);
        }
        _isrunning = true;
    }

启动服务器,之后在客户端使用指令telnet ip 端口(本质上是tcp连接)就可以建立连接,而服务端会一直监听,发现存在对自己的连接就会获取连接然后读到客户端发来的消息,收到消息之后打印然后将消息写回

服务端测试完毕,现在写客户端

客户端

申请套接字

不需要显示绑定端口号;不需要设置监听,服务端设置监听是因为它要接收客户端的连接请求,而客户端要做的是主动向服务端发起连接

发起连接

接口connect介绍

第一个参数是自己的sockfd,第二个参数是对方的套接字信息,表明要向谁发起连接

#include <sys/types.h> /* See NOTES */

#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr,

socklen_t addrlen);

RETURN VALUE

If the connection or binding succeeds, zero is returned. On error, -1 is returned, and errno is set appropriately.

connect发起连接成功了,客户端会自动在底层bind自己被OS分配的端口号,之后通过sockfd进行通信

cpp 复制代码
#include "TcpServer.hpp"


// ./tcpclient serverip serverport
int main(int argc, char* argv[])
{
    if (argc != 3)
    {
        std::cerr << "Usage: " << argv[0] << "serverip serverport" << std::endl;
        exit(USAGE_ERR);
    }
    // 申请套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1)
    {
        logger(LogLevel::FATAL) << "socket error!";
        exit(SOCKET_ERR);
    }

    // 建立连接
    // 首先要定义出服务端的套接字信息结构体
    uint64_t serverport = std::stoi(argv[2]);
    std::string serverip = argv[1];
    InetAddr peer(serverip, serverport);
    int n = connect(sockfd, peer.NetAddr(), peer.AddrLen());
    if (n != 0)
    {
        logger(LogLevel::FATAL) << "connect error!";
        exit(CONNECT_ERR);        
    }

    // 开始通信
    while (true)
    {
        std::cout << "please enter# " << std::endl;
        std::string input;
        std::getline(std::cin, input);

        write(sockfd, input.c_str(), input.size());

        char buffer[128];
        ssize_t size = read(sockfd, buffer, sizeof(buffer) - 1);
        if (size)
        {
            buffer[size] = 0;
            std::cout << "server echo# " << buffer << std::endl;
        }
    }
    close(sockfd);
    return 0;
}

测试

查看网络状态,可以看到一个双向连接,是tcp的

改为多线程版本

结论一:如果打开的进程退出了,这个进程打开的文件会默认释放,文件描述符会自动关闭(这也是解释了测试的时候客户端退出了,服务端打印退出了的原因,因为文件已经释放,此时服务端发现要读取的文件已经关闭,那么就知道读不了了)

结论二:创建一个子进程,这个子进程可以拿到父进程的打开文件获得的文件描述符(回顾之前的系统部分管道通信)

那么现在改为多进程版本,在TcpServer中创建子进程,要求父进程只需要不断accept,而子进程不断创建去执行Service,那么父进程就要关掉socket这个专门用来读写的fd,而子进程继承了_listen_sockfd也要关掉这个专门用来监听的fd,子进程执行完Service就要退出,退出父进程需要等待避免其成为僵尸进程,因为要创建很多子进程,僵尸进程多了会造成大量的资源泄露。但是呢等待子进程是阻塞进行的,也就是说,子进程没有执行完之前,父进程不会再创建新的子进程,这样一来还是串行化的,并没有多进程的体现。为了解决这个问题,可以在子进程内部先创建一个孙子进程,让孙子进程执行Service,子进程直接退出即可。子进程肯定是先于孙子进程退出的,那么孙子进程就成为了孤儿进程,但是不需要担心,1号进程会自动管理这个孙子进程。这样一来父进程就可以立即等待成功然后继续accpet获取连接创建子进程,完成并行

cpp 复制代码
    void Run()
    {
        _isrunning = true;
        while (_isrunning)
        {
            // 获取连接
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int sockfd = accept(_listen_sockfd, CONV(peer), &len);
            if (sockfd < 0)
            {
                logger(LogLevel::WARNING) << "accept error";
                continue;
            }
            InetAddr addr(peer);
            logger(LogLevel::INFO) << "accept success, peer addr : " << addr.StringAddr();
            pid_t id = fork(); 
            if (id < 0)
            {
                logger(LogLevel::FATAL) << "fork error";
                exit(FORK_ERR);
            }
            else if (id == 0) // 子进程
            {
                close(_listen_sockfd);
                if (fork() > 0)
                    exit(OK); // 子进程退出,孙子进程继续运行
                Service(sockfd, addr);
                exit(OK);
            }
            else
            {
                close(sockfd);
                waitpid(id, nullptr, 0);
            }
        }
        _isrunning = true;
    }

此时退出客户端,父进程为1的进程也会退出,也就是孙子进程

这里存在一个问题就是客户端服务端都在运行的时候,服务端退出,之后服务端再运行,若是不更换端口号,会出现绑定失败,这一点后面解释

改为多线程的版本

创建线程需要提供入口函数,入口函数还要传参,入口函数定义在类内是一个静态成员函数,这个函数不能访问类内普通方法属性,因此想要调用方法Service需要传递参数TcpServer *tsvr ,Service需要参数sockfd以及InetAddr peer,所以可以定义一个类,这个类中有三个属性类型为TcpServer * 、int、InetAddr。在我们创建线程的时候就直接给入口函数传入这个类实例化出来的对象

cpp 复制代码
#pragma once

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>

#include "Log.hpp"
#include "mutexEncapsulation.hpp"
#include "Common.hpp"
#include "InetAddr.hpp"
using namespace LogModule;

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

class TcpServer : public NoCopy
{
private:
    int _listen_sockfd;
    uint16_t _port;
    bool _isrunning;
public:
    TcpServer(uint64_t port):_listen_sockfd(defaultsockfd), _port(port), _isrunning(false)
    {
        logger(LogLevel::INFO) << "TcpServer对象初始化成功";
    }
    ~TcpServer()
    {
        logger(LogLevel::INFO) << "TcpServer对象销毁成功";
    }
    class ThreadData
    {
    public: 
        ThreadData(int sockfd, InetAddr &addr, TcpServer *tsvr)
        :_sockfd(sockfd), _addr(addr), _tsvr(tsvr)
        {}
        int _sockfd;
        InetAddr _addr;
        TcpServer *_tsvr;
    };
    void Init()
    {
        // 申请套接字
        _listen_sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_listen_sockfd == -1)
        {
            logger(LogLevel::FATAL) << "socket error!";
            exit(SOCKET_ERR);
        }
        logger(LogLevel::INFO) << "socket success, _listen_sockfd : " << _listen_sockfd;
        
        // 绑定端口
        InetAddr addr(_port);
        int n = bind(_listen_sockfd, addr.NetAddr(), addr.AddrLen());
        if (n != 0)
        {
            logger(LogLevel::FATAL) << "bind error!";
            exit(BIND_ERR);            
        }
        logger(LogLevel::INFO) << "bind success, _listen_sockfd : " << _listen_sockfd;
        
        // 设置socket状态为监听
        // 因为TCP是面向连接的,服务器要一直监听,看有没有客户端想要建立连接
        n = listen(_listen_sockfd, backlog);
        if (n != 0)
        {
            logger(LogLevel::FATAL) << "listen error!";
            exit(LISTEN_ERR); 
        }
        logger(LogLevel::INFO) << "listen success";
    }
    void Service(int sockfd, InetAddr &peer)
    {
        char buffer[1024];
        while (true)
        {
            // 1. 先读取数据
            // a. n>0: 读取成功
            // b. n<0: 读取失败
            // c. n==0: 对端把链接关闭了,读到了文件的结尾 --- pipe
            ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);
            if (n > 0)
            {
                // buffer是一个英文单词 or 是一个命令字符串
                buffer[n] = 0;
                logger(LogLevel::DEBUG) << peer.StringAddr() << " #" << buffer;
                // 2. 写回数据
                std::string echo_string = "echo# ";
                echo_string += buffer;
                write(sockfd, echo_string.c_str(), echo_string.size());
            }
            else if (n == 0)
            {
                logger(LogLevel::DEBUG) << peer.StringAddr() << " 退出了...";
                close(sockfd);
                break;
            }
            else
            {
                logger(LogLevel::DEBUG) << peer.StringAddr() << " 异常...";
                close(sockfd);
                break;
            }
        }
    }
    static void* Route(void* args)
    {
        pthread_detach(pthread_self());
        ThreadData *td = static_cast<ThreadData*>(args);
        td->_tsvr->Service(td->_sockfd, td->_addr);
        delete td;
        return nullptr;
    }
    void Run()
    {
        _isrunning = true;
        while (_isrunning)
        {
            // 获取连接
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int sockfd = accept(_listen_sockfd, CONV(peer), &len);
            if (sockfd < 0)
            {
                logger(LogLevel::WARNING) << "accept error";
                continue;
            }
            InetAddr addr(peer);
            logger(LogLevel::INFO) << "accept success, peer addr : " << addr.StringAddr();
            // 多线程版本
            ThreadData *td = new ThreadData(sockfd, addr, this);
            pthread_t tid;
            pthread_create(&tid, nullptr, Route, td);
        }
        _isrunning = true;
    }
};

像这种多线程多进程的都是提供的长服务,也就是连接上了服务端,服务端会一直不断开地提供服务;若是短服务则是只有客户端请求了服务端才会建立一次连接,响应之后就断开

改为线程池版本,这就是一种短服务,因为线程池中线程的个数是有限的,不可能一直为那几个客户端提供服务,线程池一般比较适合处理短服务

cpp 复制代码
// 线程池版本
ThreadPool<task_t>::GetInstance()->Enqueue([this, sockfd, &addr](){
   this->Service(sockfd, addr);
 });

二、Dict翻译版本

和udp一样。将翻译功能放在上层去处理,网络服务器只负责接收客户端传来的消息,接收到回调Dict中的翻译函数获取返回值也就是翻译结果之后再由服务端发送给客户端

server.hpp

cpp 复制代码
#pragma once

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>
#include <functional>


#include "Log.hpp"
#include "mutexEncapsulation.hpp"
#include "Common.hpp"
#include "InetAddr.hpp"
#include "threadPool.hpp"

using namespace ThreadPoolModule;
using namespace LogModule;

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

using func_t = std::function<std::string(std::string&, InetAddr&)>;
using task_t = std::function<void()>;

class TcpServer : public NoCopy
{
private:
    int _listen_sockfd;
    uint16_t _port;
    bool _isrunning;
    func_t _func;
public:
    TcpServer(uint64_t port, func_t func)
    :_listen_sockfd(defaultsockfd), _port(port), _isrunning(false), _func(func)
    {
        logger(LogLevel::INFO) << "TcpServer对象初始化成功";
    }
    ~TcpServer()
    {
        logger(LogLevel::INFO) << "TcpServer对象销毁成功";
    }
    class ThreadData
    {
    public: 
        ThreadData(int sockfd, InetAddr &addr, TcpServer *tsvr)
        :_sockfd(sockfd), _addr(addr), _tsvr(tsvr)
        {}
        int _sockfd;
        InetAddr _addr;
        TcpServer *_tsvr;
    };
    void Init()
    {
        // 申请套接字
        _listen_sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_listen_sockfd == -1)
        {
            logger(LogLevel::FATAL) << "socket error!";
            exit(SOCKET_ERR);
        }
        logger(LogLevel::INFO) << "socket success, _listen_sockfd : " << _listen_sockfd;
        
        // 绑定端口
        InetAddr addr(_port);
        int n = bind(_listen_sockfd, addr.NetAddr(), addr.AddrLen());
        if (n != 0)
        {
            logger(LogLevel::FATAL) << "bind error!";
            exit(BIND_ERR);            
        }
        logger(LogLevel::INFO) << "bind success, _listen_sockfd : " << _listen_sockfd;
        
        // 设置socket状态为监听
        // 因为TCP是面向连接的,服务器要一直监听,看有没有客户端想要建立连接
        n = listen(_listen_sockfd, backlog);
        if (n != 0)
        {
            logger(LogLevel::FATAL) << "listen error!";
            exit(LISTEN_ERR); 
        }
        logger(LogLevel::INFO) << "listen success";
    }
    void Service(int sockfd, InetAddr &peer)
    {
        char buffer[1024];
        while (true)
        {
            // 1. 先读取数据
            // a. n>0: 读取成功
            // b. n<0: 读取失败
            // c. n==0: 对端把链接关闭了,读到了文件的结尾 --- pipe
            ssize_t n = read(sockfd, buffer, sizeof(buffer) - 1);
            if (n > 0)
            {
                buffer[n] = 0;
                logger(LogLevel::DEBUG) << peer.StringAddr() << " #" << buffer;
                // 翻译
                std::string echo_string = buffer;
                echo_string = _func(echo_string, peer);
                write(sockfd, echo_string.c_str(), echo_string.size());
            }
            else if (n == 0)
            {
                logger(LogLevel::DEBUG) << peer.StringAddr() << " 退出了...";
                close(sockfd);
                break;
            }
            else
            {
                logger(LogLevel::DEBUG) << peer.StringAddr() << " 异常...";
                close(sockfd);
                break;
            }
        }
    }
    static void* Route(void* args)
    {
        pthread_detach(pthread_self());
        ThreadData *td = static_cast<ThreadData*>(args);
        td->_tsvr->Service(td->_sockfd, td->_addr);
        delete td;
        return nullptr;
    }
    void Run()
    {
        _isrunning = true;
        while (_isrunning)
        {
            // 获取连接
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int sockfd = accept(_listen_sockfd, CONV(peer), &len);
            if (sockfd < 0)
            {
                logger(LogLevel::WARNING) << "accept error";
                continue;
            }
            InetAddr addr(peer);
            logger(LogLevel::INFO) << "accept success, peer addr : " << addr.StringAddr();
            // 多进程版本
            // pid_t id = fork(); 
            // if (id < 0)
            // {
            //     logger(LogLevel::FATAL) << "fork error";
            //     exit(FORK_ERR);
            // }
            // else if (id == 0) // 子进程
            // {
            //     close(_listen_sockfd);
            //     if (fork() > 0)
            //         exit(OK); // 子进程退出,孙子进程继续运行
            //     Service(sockfd, addr);
            //     exit(OK);
            // }
            // else
            // {
            //     close(sockfd);
            //     waitpid(id, nullptr, 0);
            // }
            // 多线程版本
            // ThreadData *td = new ThreadData(sockfd, addr, this);
            // pthread_t tid;
            // pthread_create(&tid, nullptr, Route, td);
            // 线程池版本
            ThreadPool<task_t>::GetInstance()->Enqueue([this, sockfd, &addr](){
                this->Service(sockfd, addr);
            });
        }
        _isrunning = true;
    }
};

server.cc

cpp 复制代码
#include "TcpServer.hpp"
#include "Common.hpp"
#include "Dict.hpp"
#include <memory>

int main(int argc, char* argv[])
{
    if (argc != 2)
    {
        std::cerr << "Usage: " << argv[0] << " port" << std::endl;
        exit(USAGE_ERR);
    }
    uint64_t port = std::stoi(argv[1]);

    Dict d;
    d.DictLoad();

    std::unique_ptr<TcpServer> tsevr = std::make_unique<TcpServer>(port, 
        [&d](std::string& word, InetAddr& addr){
            return d.Translate(word, addr);
    });
    tsevr->Init();
    tsevr->Run();
    return 0;
}

client.cc

cpp 复制代码
#include "TcpServer.hpp"


// ./tcpclient serverip serverport
int main(int argc, char* argv[])
{
    if (argc != 3)
    {
        std::cerr << "Usage: " << argv[0] << "serverip serverport" << std::endl;
        exit(USAGE_ERR);
    }
    // 申请套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1)
    {
        logger(LogLevel::FATAL) << "socket error!";
        exit(SOCKET_ERR);
    }

    // 建立连接
    // 首先要定义出服务端的套接字信息结构体
    uint64_t serverport = std::stoi(argv[2]);
    std::string serverip = argv[1];
    InetAddr peer(serverip, serverport);
    int n = connect(sockfd, peer.NetAddr(), peer.AddrLen());
    if (n != 0)
    {
        logger(LogLevel::FATAL) << "connect error!";
        exit(CONNECT_ERR);        
    }

    // 开始通信
    while (true)
    {
        std::cout << "please enter# " << std::endl;
        std::string input;
        std::getline(std::cin, input);

        write(sockfd, input.c_str(), input.size());

        char buffer[128];
        ssize_t size = read(sockfd, buffer, sizeof(buffer) - 1);
        if (size)
        {
            buffer[size] = 0;
            std::cout << "translate result# " << buffer << std::endl;
        }
    }
    close(sockfd);
    return 0;
}

三、Command命令行解析版本

客户端发送的消息是一条条指令,服务端拿到这个指令转给上层去执行将结果发送给客户端

这里介绍一个接口 popen 作用是为调用这个接口的父进程创建一个子进程以及一个管道文件,传递的第一个参数就是指令字符串,子进程拿到这个字符串自动执行解析指令并且执行,将执行结果通过管道写给父进程,子进程打开的是写端,父进程是读端

并且返回值是一个文件类型,父进程想要看到指令执行结果,就按照读的方式打开这个文件流之后fgets获取文件中的数据保存在字符串中

pclose关闭文件

#include <stdio.h>

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

int pclose(FILE *stream);

Command.hpp

cpp 复制代码
#pragma once
#include "InetAddr.hpp"
#include "Common.hpp"
#include "Log.hpp"
#include <set>
#include <cstdio>
#include <string>


class Command
{
public:
    Command()
    {
        // 设置只能执行的指令,避免恶意指令的执行
        _white_commands_list.insert("ls -l");
        _white_commands_list.insert("whoami");
        _white_commands_list.insert("pwd");
        _white_commands_list.insert("ll");
        _white_commands_list.insert("ls -a -l");
    }
    // 判断指令是否合法的函数
    bool IsInWhiteList(std::string& cmd)
    {
        auto it = _white_commands_list.find(cmd);
        return it != _white_commands_list.end();
    }
    // 解析指令并且返回客户端以及解析结果
    std::string Execute(std::string& cmd, InetAddr& addr)
    {
        if (!IsInWhiteList(cmd))
        {
            logger(LogLevel::INFO) << "这是恶意执行,无法执行";
            return std::string("坏人");
        }

        std::string who = addr.StringAddr();
        // 开始执行
        FILE *fp = popen(cmd.c_str(), "r");
        if (fp == nullptr)
        {
            return std::string("你要执行的指令不存在: ") + cmd;
        }
        char line[1024];
        std::string res;
        while (fgets(line, sizeof(line), fp))
        {
            res += line;
        }
        pclose(fp);
        // 返回结果
        logger(LogLevel::INFO) << "指令解析完毕";
        std::string result = who + "的指令执行结果是: " + res;
        return result;
    }
    ~Command(){}
private:
    std::set<std::string> _white_commands_list;
};

server.cc

cpp 复制代码
#include "TcpServer.hpp"
#include "Common.hpp"
#include "Command.hpp"
#include <memory>

int main(int argc, char* argv[])
{
    if (argc != 2)
    {
        std::cerr << "Usage: " << argv[0] << " port" << std::endl;
        exit(USAGE_ERR);
    }
    uint64_t port = std::stoi(argv[1]);

    Command command;

    std::unique_ptr<TcpServer> tsevr = std::make_unique<TcpServer>(port, 
        [&command](std::string& word, InetAddr& addr){
            return command.Execute(word, addr);
    });
    tsevr->Init();
    tsevr->Run();
    return 0;
}

相当于实现了一个简单版的shell

相关推荐
劳埃德福杰2 小时前
【Kylin银河麒麟】文件系统磁盘空间满导致无法进入操作系统
运维·服务器·电脑·笔记本电脑·kylin
@encryption2 小时前
计算机网络发展
网络·计算机网络
逻辑峰2 小时前
ReadStat在Linux的安装和使用
linux·运维·服务器
上海云盾-小余2 小时前
零信任安全落地实战:企业如何构建无边界可信访问体系
网络·安全·web安全·架构
7yewh2 小时前
Dense / 全连接层 / Gemm — 综合全局特征理解与运用
网络·人工智能·python·深度学习·cnn
Lsir10110_2 小时前
【Linux】序列化与反序列化——网络计算器的实现
linux·运维·网络
脆皮的饭桶2 小时前
给负载均衡做高可用的工具Keepalived
运维·服务器·负载均衡
深念Y2 小时前
多拨与双WAN提速:原理、误区与运营商的“隐藏限制”
网络·智能路由器·ip·光猫·wan·多拨·opppe
23.2 小时前
【网络】TCP与HTTP:网络通信的核心机制解析
网络·tcp/ip·http