TCP套接字

TCP套接字编程 TCP与UDP的区别相当大。它充分地实现了数据传输时的各种控制功能,可以进行丢包时重发控制,还可以对次序乱掉的分包进行顺序控制。而这些再UDP中都没有。此外,TCP作为一种面向有连接的协议,只要在确认通信对端存在时才会发生数据,从而可以控制通信流量的浪费。UDP不提供复杂的控制机制,利用IP提供面向无连接的通信服务。并且它是将应用程序发来的数据在收的那一刻,立即按照原样发送到网络上的一种机制。UDP是 User Datagram Protocol 的缩写,即用户数据报协议。 阅读详情

目录

一、echo版本

二、Dict翻译版本

三、Command命令行解析版本


一、echo版本

单进程echo版本

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

Common.hpp

#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

#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()

    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就会阻塞

    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进行通信

#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获取连接创建子进程,完成并行

    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。在我们创建线程的时候就直接给入口函数传入这个类实例化出来的对象

#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;
    }
};

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

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

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

二、Dict翻译版本

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

server.hpp

#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

#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

#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

#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

#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

我用 Python 把 Claude 变成了 “代码审查员“:每次提交前 AI 先 Review,Bug 漏网率降了 80% 这是成败的关键。我们需要给 Claude 立规矩,让它像一个严格的架构师那样思考。你是一名资深的首席软件架构师和安全专家。请审查以下 Git Diff 代码变更。你的任务是:1. **安全性检查**:寻找 SQL 注入、XSS、硬编码密钥、敏感信息泄露等。2. **潜在 Bug**:空指针、资源未关闭、竞态条件、逻辑死循环。3. **代码质量**:命名规范、冗余代码、可读性差、缺乏注释。4. **性能优化**:不必要的数据库查询、低效算法。 阅读详情

相关推荐

PCIe5.0均衡简介(整理版)

pcie5.0 均衡 equalization

m0_53559277的博客 9108

TCP套接字编程入门

概述套接字(socket)是一种通信机制,凭借这种机制,客户与服务器的通信既可以在本地单机上进行,也可以跨网络进行。 图:基本的TCP客户/服务器应用程序的套接字函数图中展示了一对TCP客户与服务器进程之间进行通信时调用套接字函数的交互情况。服务器首先启动,然后监听客户的连接。稍后客户试图连接服务器,客户连接成功后,客户给服务器发送请求,服务器处理请求,并且返回给客户一个响应。这个过程一直持续下去

Leo的博客 1万+

极验第四代滑块验证码破解(三):滑块轨迹构造

极验第四代滑块验证码破解(三):滑块轨迹构造声明 声明 原创文章,请勿转载! 本文内容仅限于安全研究,不公开具体源码。维护网络安全,人人有责。 本文关联文章超链接:

帯泪的鱼的博客 8624

Tcp套接字

TCP(Transmission Control Protocol,传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议。TCP套接字(Socket)是实现TCP协议进行网络通信的关键机制。TCP套接字广泛应用于各种需要可靠传输数据的网络应用程序中,如Web服务器(HTTP)、文件传输(FTP)、远程登录(SSH)等。:当客户端断开连接后,与客户端相连接的Socket对象也应当被关闭,否则其将会占用文件描述符表,导致资源浪费。在编程中,TCP套接字的实现依赖于操作系统提供的套接字API。

2302_78814792的博客 1667

用UltraISO制作镜像以RAW格式刻录系统到U盘后,在Windows上无法识别的解决办法

在这篇文章中,用UltraISO制作镜像后,会使得U盘无法识别!!!那么,如何抹掉U盘的数据,格式化U盘呢?

拥抱具身智能,昇腾Ascend C 2730

AIDL HAL服务从零到一:构建现代Android硬件抽象层

本文详细介绍了从零开始构建现代Android AIDL HAL服务的完整流程。通过定义接口、实现服务逻辑、配置构建与VINTF清单,并最终集成到系统镜像,开发者可以掌握利用AIDL替代传统HIDL来开发硬件抽象层的核心方法。文章重点解析了AIDL HAL的创建流程,并对比了其与HIDL在语法、工具链和集成方式上的优势,帮助开发者高效构建稳定的硬件接口。

weixin_29174141的博客 145

TCP套接字详解—C语言实现

本文旨在windows系统下使用TCP套接字实现客户端与服务端之间的通信 编译器:visual studio 2019(community)

CodePandas 6053

Multisim仿真入门:单相半波可控整流电路从搭建到波形分析(附避坑指南)

本文为电力电子技术初学者提供了一份详细的Multisim仿真指南,手把手教你从零搭建单相半波可控整流电路。通过对比纯电阻、阻感负载及加入续流二极管后的波形变化,直观理解晶闸管触发、电感续流等核心原理,并附有实用的参数设置与常见问题避坑指南,帮助你将抽象理论转化为可视化的工程实践。

weixin_29208077的博客 343

TCP流套接字编程

在上一篇中,我们已经讲解了什么是网络编程,为什么要使用网络编程,并讲解了TCP和UDP之间的区别,同时也讲解了在java中如何使用UDP数据报套接字编程,实现一个回显客户端-服务器程序,那么本篇我们就来讲解一下如何在java中使用TCP流套接字编程。

zhyhgx的博客 1792

沁恒CH32V30X学习笔记02--GPIO的使用教程及2次封装驱动

刚复位后,GPIO 口运行在初始状态,这时大多数 IO 口都是运行在状态所有的 GPIO 口都可以被配置外部中断输入通道,但一个外部中断输入通道最多只能映射到一个GPIO 引脚上,且外部中断通道的序号必须和 GPIO 端口的位号一致,,两方都是一对一的关系。复用功能。

Car12 705

YOLOv11涨点改进| TPAMI 2026 |独家创新首发、Conv改进篇| 引入LPM 局部先验特征增强模块,更加聚焦于目标区域并抑制背景干扰,助力目标检测、图像分割、图像恢复、图像增强有效涨点

🔥本文提出了一种改进YOLOv11网络的LPM(局部先验特征增强)模块,通过构建重要性图引导特征提取过程,使模型更聚焦目标区域并抑制背景干扰。LPM模块通过注意力机制强化关键区域信息,优化特征分布,提升小目标和复杂场景下的检测性能。实验表明,该模块在保持轻量结构的同时,显著提高了检测精度和鲁棒性。文章详细介绍了LPM的结构原理、核心代码实现方法,并提供了4种改进配置方案(yolov11n_LPM.yaml等),指导用户如何集成该模块到YOLOv11网络中。这些改进方案通过多阶段特征融合逐步强化细节表达.

Ai缝合怪、助力小伙伴高效发论文 127

【JavaEE】网络编程之TCP套接字、UDP套接字

网络编程的基本概念、TCP套接字和UDP套接字的API使用。

努力 1141

大模型实战—通义千问大模型微调

这个参数仅针对 llama 类型的模型结构,默认勾选的是 q_proj 和 v_proj,具体的名词不容易理解,我就不多说了,可以简单的认为是对模型的理解能力进行优化,一般这两个就够了。当这个值较高时,适应新任务的能力会增强,但是对基础模型的影响会比较大,有过拟合的风险,尤其是在数据量有限的情况下。为了方便测试,我在云环境也创建了一个镜像,相关的环境都配置好了,可以直接使用几个国内开源的大语言模型,比如清华智谱的ChatGLM3-6B、零一万物的Yi-34B,还有最近阿里云开源的Qwen1.5-32B。

7万+

Qt 基于TCP套接字编程

Qt 基于TCP套接字编程

青春有梦,且行且学习! 806

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

TCP的套接字

TCP的套接字

changsoon 8696

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

【网络通信】socket编程——TCP套接字

TCP依旧使用代码来熟悉对应的套接字,很多接口都是在udp中使用过的所以就不会单独把他们拿出来作为标题了,只会把第一次出现的接口作为标题。

qq_62939852的博客 2428

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

【TCP套接字编程,UDP套接字编程】

应用进程使用传输层提供的服务才能交换报文。地点:界面上的SAP(Socket)方式:Socket API。:分布式应用进程之间的门,传输层协议提供的端到端的服务接口。:应用进程与端到端传输协议(TCP或UDP)之间的门户。4)客户端从socket中读取一行字符,然后打印出来。1)客户端从标准输入装置读取一行字符,发送给服务器。3)服务器将字符转换成大写,然后返回给客户端。:从一进程向另一个进程可靠地传输。2)服务器从socket读取字符。UDP 为客户端和服务器提供。不可靠的字节组的传送服务。

forever_youyang的博客 1400

TCP套接字总结

TCP基本套接字函数 简介 介绍完TCP连接的基本流程后,本文将着重介绍TCP连接时所使用的套接字函数。 函数 socket函数 #include <sys/socket.h> int socket(int family, int type, int protocol); //family:所使用协议族;AF_INET为IPv4协议;AF_INET6为IPv6协议。 //type:套接字类型;SOCK_STREAM为字节流套接字。 //protocol:协议类型常值,如IPPROTO_TCP,或

yehongji1024的博客 378

TCP网络套接字

简单介绍tcp编程

2302_79538079的博客 1458

TCP的套接字的socket通信

TCP的套接字的socket通信 本篇主要通过实现TCP的套接字通信例程介绍套接字基础知识。 一、概念 套接字:TCP用主机的IP地址加上主机上的端口号作为TCP连接的端点,这种端点,就叫做套接字(socket)或插口。套接字用(IP地址:端口号)表示。 生成套接字的三个主要参数:通信的目的IP地址,使用传输层协议(TCP或UDP)和使用的端口。socket原意为插座,通过这...

qq_35600620的博客 1302

python 实现tcp套接字编程

套接字 *套接字:套接字是一个抽象层,是传输层实现端与端的通信,每一个传输层连接有两个端点,传输层连接的端点叫做套接字。套接字可以看成是两个网络应用程序进行通信时,各自通信连接中的一个端点。通信时,其中的一个网络应用程序将要传输的一段信息写入它所在主机的Socket中,该Socket通过网络接口卡的传输介质将这段信息发送给另一台主机的Socket中,使这段信息能传送到其他程序中。 *tcp套接字编...

weixin_45479532的博客 1523

Linux TCP网络套接字

对比 UDP 服务器,TCP 服务器多了获取新链接和监听的操作,而因为 TCP 是面向字节流的,所以接收和发送数据都是 IO 操作,也就是文件操作。

puppy_1mo的博客 1187
上一篇: 动态规划——简单多状态dp
下一篇: 应用层自定义协议与序列化
溪苏.
博客等级 码龄3年 995粉丝 138原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值