计算机网络(3) --- 网络套接字TCP

计算机网络(2) --- 网络套接字UDP_哈里沃克的博客-CSDN博客https://blog.csdn.net/m0_63488627/article/details/131977544?spm=1001.2014.3001.5501

目录

1.TCP

1.服务端接口介绍

1.listen状态

2.accept获取链接

2.客户端接口介绍

2.TCP的服务器和客户端接口实现

1.服务端

1.成员函数

2.接口

start()实现方式

1.单一执行流

2.多进程

3.多线程

4.线程池

3.main函数

2.客户端

1.成员函数

2.接口

3.mian函数

3.守护进程化

1.守护进程

2.代码


1.TCP

回顾一下UDP套接字,实现很简单,只需要初始化然后再send即可。但是TCP更加复杂

1.服务端接口介绍

1.listen状态

listen状态适用于获取先链接的,第二个参数不能填太大的int类型数据。

2.accept获取链接

 服务器只有先accept获取新链接(客户端传来的套接字),才能接受到套接字。

1.调用成功返回一个文件描述符, 失败返回-1。这个返回值其实对应的是一个套接字。

2.第一个sockfd其实是接收用的套接字,它不参与到具体的操作内;而返回值返回的套接字才是客户端返回的套接字,这个套接字是用于处理的。

3.由于tcp是面向字节流的,所以接受到的套接字就可以使用read和write进行操作了

4.如果该套接字使用完毕,一定要释放(close)套接字。因为文件描述符的本质是数组,而数组有一定的上限,我们不能只入而不释放,如果不释放会出现文件描述符泄漏。

2.客户端接口介绍

1.connect:发起链接

链接对应服务端的ip和port,表示自己要链接哪个服务端

2.TCP的服务器和客户端接口实现

1.服务端

namespace Server
{
    enum
    {
        USAGE_ERR = 1,
        SOCKET_ERR,
        BIND_ERR,
        LISTEN_ERR,
        OPEN_ERR
    };

    static const uint16_t gport = 8080;
    static const int gbacklog = 5;

    class tcpServer
    {
    public:
        tcpServer(const uint16_t &port = gport)
            : _listensock(-1), _port(port)
        {
        }

        void initServer()
        {
            // 1.创建
            _listensock = socket(AF_INET, SOCK_STREAM, 0);
            if (_listensock < 0)
            {
                logMessage(FATAL, "create listensock error");
                exit(SOCKET_ERR);
            }
            logMessage(NORMAL, "create listensock success");

            // 2.bind
            struct sockaddr_in local;
            memset(&local, 0, sizeof local);
            local.sin_family = AF_INET;
            local.sin_port = htons(_port);
            local.sin_addr.s_addr = INADDR_ANY;
            if (bind(_listensock, (struct sockaddr *)&local, sizeof local) < 0)
            {
                logMessage(FATAL, "bind error");
                exit(BIND_ERR);
            }
            logMessage(NORMAL, "bind listensock success");

            // 3.设置socket,为监听状态
            if (listen(_listensock, gbacklog))
            {
                logMessage(FATAL, "listen error");
                exit(LISTEN_ERR);
            }
            logMessage(NORMAL, "listen listensock success");
        }

        void start()
        {
            for (;;)
            {
                // 单线程版,只能有一个执行流,其他的无法进入执行,因为serviceIO死循环
                // 1.sever获取新链接
                struct sockaddr_in peer;
                socklen_t len = sizeof peer;
                int sock = accept(_listensock, (struct sockaddr *)&peer, &len);
                if (sock < 0)
                {
                    logMessage(ERROR, "accept error, next"); // accept失败不影响整体的代码,继续访问即可
                    continue;
                }
                logMessage(NORMAL, "accept a new link success");
                std::cout << "sock: " << sock << std::endl;

                // sock是面向字节流的,后续全部都是文件操作
                serviceIO(sock);
                close(sock); //不释放会出现文件描述符泄漏
            }
        }

        void serviceIO(int sock)
        {
            char buffer[1024];
            while (true)
            {
                ssize_t n = read(sock, buffer, sizeof(buffer) - 1);
                if (n > 0)
                {
                    buffer[n] = 0;
                    std::cout << "recv message: " << buffer << std::endl;

                    std::string outbuffer = buffer;
                    outbuffer += "server[echo]";

                    write(sock, outbuffer.c_str(), outbuffer.size());
                }
                else if (n == 0)
                {
                    // 代表client退出
                    logMessage(NORMAL, "client quit,me too");
                    break;
                }
            }
        }

        ~tcpServer()
        {
        }

    private:
        uint16_t _port;
        int _listensock; // 不是用来通信的,而是用于监听链接的
    };
}

1.成员函数

uint16_t _port:端口号

int _listensock:监听套接字

2.接口

1.initServer()创建套接字,先初始化监听套接字;生成对应的监听套接字;随后将当前的IP设置为任意IP,并且设置port最后绑定(bind)起来;随后还需要listen监听套接字

2.start()先定义新套接字,accept获取新套接字,执行套接字传输的任务。

start()实现方式

1.单一执行流
        void start()
        {
            for (;;)
            {
                // 1.sever获取新链接
                struct sockaddr_in peer;
                socklen_t len = sizeof peer;
                int sock = accept(_listensock, (struct sockaddr *)&peer, &len);
                if (sock < 0)
                {
                    logMessage(ERROR, "accept error, next"); // accept失败不影响整体的代码,继续访问即可
                    continue;
                }
                logMessage(NORMAL, "accept a new link success");
                std::cout << "sock: " << sock << std::endl;

                // sock是面向字节流的,后续全部都是文件操作
                serviceIO(sock);
                close(sock); //不释放会出现文件描述符泄漏
            }
        }

这样的缺点很明显:只有一个执行流操作服务端,也就意味着客户端只有一个能链接服务端。而且serviceIO()是一个死循环函数,那么只有客户端主动退出才能让其他客户端链接。这是串行的执行逻辑

2.多进程
1.信号版
        void start()
        {
            signal(SIGCHLD, SIG_IGN);
            for (;;)
            {
                // 1.sever获取新链接
                struct sockaddr_in peer;
                socklen_t len = sizeof peer;
                int sock = accept(_listensock, (struct sockaddr *)&peer, &len);
                if (sock < 0)
                {
                    logMessage(ERROR, "accept error, next"); // accept失败不影响整体的代码,继续访问即可
                    continue;
                }
                logMessage(NORMAL, "accept a new link success");
                std::cout << "sock: " << sock << std::endl;

                // 多进程
                pid_t id = fork();
                if (id == 0)
                {
                    // child
                    close(_listensock);
                    serviceIO(sock);
                    close(sock);
                    exit(0);
                }
                close(sock);
            }
        }

1.直接忽略子进程阻塞的状态,这样操作系统会自动回收

2.只要子进程自动回收,那么父进程就不需要等待,直接不断的按照需求新建子进程,而子进程执行结束不需要管理也不会有内存泄漏问题

3.在父进程处close(sock)的做法是为了不让文件描述符泄漏,子进程的close不影响父进程,如果父进程不弄,子进程把自己的套接字关闭不代表父进程的套接字也被关闭了

2.waitpid版
        void start()
        {
            for (;;)
            {
                // 1.sever获取新链接
                struct sockaddr_in peer;
                socklen_t len = sizeof peer;
                int sock = accept(_listensock, (struct sockaddr *)&peer, &len);
                if (sock < 0)
                {
                    logMessage(ERROR, "accept error, next"); // accept失败不影响整体的代码,继续访问即可
                    continue;
                }
                logMessage(NORMAL, "accept a new link success");
                std::cout << "sock: " << sock << std::endl;

                // 多进程
                pid_t id = fork();
                if (id == 0)
                {
                    // child
                    close(_listensock);
                    if (fork() > 0)
                        exit;

                    // 孙子进程变为孤儿进程
                    serviceIO(sock);
                    close(sock);
                    exit(0);
                }
                close(sock);
                // father
                pid_t ret = waitpid(id, nullptr, 0); //子进程进去直接死亡回收,不需要管理
                if (ret > 0)
                    std::cout << "wait success: " << ret << std::endl;
            }
        }

1.父进程先非阻塞等待

2.子进程中创建孙子进程,孙子进程执行接收的文件,子进程直接退出,让父进程回收。此时孙子进程变成孤儿进程托付给操作系统,操作系统管理孙子进程的结束。

多进程展现的问题就是消费太多资源,本来一个执行流能解决的事情却创造了调度执行流的基本单位进程来实现,而且拷贝需要时间,成本过大。

3.多线程
    class ThreadData
    {
    public:
        ThreadData(tcpServer *self, int sock)
            : _self(self), _sock(sock)
        {
        }

    public:
        tcpServer *_self;
        int _sock;
    };

        void start()
        {
            for (;;)
            {
                // 1.sever获取新链接
                struct sockaddr_in peer;
                socklen_t len = sizeof peer;
                int sock = accept(_listensock, (struct sockaddr *)&peer, &len);
                if (sock < 0)
                {
                    logMessage(ERROR, "accept error, next"); // accept失败不影响整体的代码,继续访问即可
                    continue;
                }
                logMessage(NORMAL, "accept a new link success");
                std::cout << "sock: " << sock << std::endl;

                // 多线程
                pthread_t tid;
                ThreadData *td = new ThreadData(this, sock);
                pthread_create(&tid, nullptr, threadRoutine, td);
            }
        }

        static void *threadRoutine(void *args)
        {
            pthread_detach(pthread_self());
            ThreadData *td = static_cast<ThreadData *>(args);

            td->_self->serviceIO(td->_sock);
            delete td;
            close(td->_sock);
            return nullptr;
        }

1.创造线程,让子线程进行接收执行。当然为了不需要串行执行,在threadRoutine()中,优先将当前线程进行线程分离。这样主线程就不需要回收子线程,子线程结束操作系统就会自动回收。

2.执行后需要将当前的文件描述符回收,由于函数是线程的公共资源,所以threadRoutine内就能影响到。

4.线程池
//线程池
using namespace ThreadNs;

const int gnum = 10;

template <class T>
class ThreadPool;

template <class T>
class ThreadData
{
public:
    ThreadData(ThreadPool<T> *tp, const std::string &n)
        : threadpool(tp), name(n)
    {
    }

public:
    ThreadPool<T> *threadpool;
    std::string name;
};

template <class T>
class ThreadPool
{
private:
    static void *handlerTask(void *args)
    {
        ThreadData<T> *td = static_cast<ThreadData<T> *>(args);
        while (true)
        {
            T t;
            {
                // td->threadpool->lockQueue();
                LockGuard lockguard(td->threadpool->mutex());
                while (td->threadpool->isQueueEmpty())
                {
                    td->threadpool->threadWait();
                }
                t = td->threadpool->pop();
            }
            // td->threadpool->unlockQueue();
            std::cout << td->name << "处理完了任务: " << t.toTaskString() << "并处理完成,结果是: " << t();
        }
        return nullptr;
    }

    ThreadPool(const int &_num = gnum)
        : _num(gnum)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_cond, nullptr);
        for (int i = 0; i < _num; i++)
        {
            _threads.push_back(new Thread());
        }
    }

    void operator=(const ThreadPool &) = delete;

    ThreadPool(const ThreadPool &) = delete;

public:
    static ThreadPool<T> *getInstance()
    {
        if (_tp == nullptr)
        {
            _singleton_lock.lock();
            if (_tp == nullptr)
            {
                _tp = new ThreadPool<Task>();
            }
            _singleton_lock.unlock();
        }
        return _tp;
    }

    // void lockQueue()
    // {
    //     pthread_mutex_lock(&_mutex);
    // }

    // void unlockQueue()
    // {
    //     pthread_mutex_unlock(&_mutex);
    // }

    bool isQueueEmpty()
    {
        return _task_queue.empty();
    }

    void threadWait()
    {
        pthread_cond_wait(&_cond, &_mutex);
    }

    T pop()
    {
        T t = _task_queue.front();
        _task_queue.pop();
        return t;
    }

    pthread_mutex_t *mutex()
    {
        return &_mutex;
    }

public:
    void run()
    {
        for (const auto &t : _threads)
        {
            ThreadData<T> *td = new ThreadData<T>(this, t->threadname());

            t->start(handlerTask, td);
            std::cout << t->threadname() << " start ..." << std::endl;
        }
    }

    void push(const T &in)
    {
        // pthread_mutex_lock(&_mutex);
        LockGuard lockguard(&_mutex);
        _task_queue.push(in);
        pthread_cond_signal(&_cond);
        // pthread_mutex_unlock(&_mutex);
    }

    ~ThreadPool()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond);
        for (const auto &t : _threads)
            delete t;
    }

private:
    int _num;
    std::vector<Thread *> _threads;
    std::queue<T> _task_queue;
    pthread_mutex_t _mutex;
    pthread_cond_t _cond;

    static ThreadPool<T> *_tp;
    static std::mutex _singleton_lock;
};

template <class T>
ThreadPool<T> *ThreadPool<T>::_tp = nullptr;
static std::mutex _singleton_lock;
/

        void start()
        {
            // 线程池初始化
            ThreadPool<Task>::getInstance()->run();
            for (;;)
            {
                // 1.sever获取新链接
                struct sockaddr_in peer;
                socklen_t len = sizeof peer;
                int sock = accept(_listensock, (struct sockaddr *)&peer, &len);
                if (sock < 0)
                {
                    logMessage(ERROR, "accept error, next"); // accept失败不影响整体的代码,继续访问即可
                    continue;
                }
                logMessage(NORMAL, "accept a new link success");
                std::cout << "sock: " << sock << std::endl;

                // 4.线程池
                ThreadPool<Task>::getInstance()->push(Task(sock, serviceIO));
            }
        }

3.main函数

using namespace std;
using namespace Server;

static void Usage(string proc)
{
    cout << "Usage:\n\t" << proc << " local_ip local_port\n\n";
}

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = atoi(argv[1]);
    std::unique_ptr<tcpServer> tsvr(new tcpServer(port));
    tsvr->initServer();
    tsvr->start();
    return 0;
}

1. 一旦启动,就会进入LISTEN状态

2.客户端

#define NUM 1024

namespace Client
{
    class tcpClient
    {
    public:
        tcpClient(const std::string &serverip, const uint16_t &serverport)
            : _serverip(serverip), _serverport(serverport), _sock(-1)
        {
        }

        void initClient()
        {
            // 1.创建socket
            _sock = socket(AF_INET, SOCK_STREAM, 0);
            if (_sock < 0)
            {
                std::cerr << "socket error: " << errno << " : " << strerror(errno) << std::endl;
                exit(2);
            }
            // 2.client要不要bind[必须要的],client要不要显示的bind,不需要
            // 3.客户端不需要listen
            // 4.也不需要accept
        }

        void start()
        {
            // 5.要发起链接
            struct sockaddr_in server;
            memset(&server, 0, sizeof server);
            server.sin_family = AF_INET;
            server.sin_port = htons(_serverport);
            server.sin_addr.s_addr = inet_addr(_serverip.c_str());

            if (connect(_sock, (struct sockaddr *)&server, sizeof server) != 0)
            {
                std::cerr << "connect error: " << errno << " : " << strerror(errno) << std::endl;
            }
            else
            {
                std::string msg;
                while (true)
                {
                    std::cout << "Enter# ";
                    std::getline(cin, msg);
                    write(_sock, msg.c_str(), msg.size());

                    char buffer[NUM];
                    int n = read(_sock, buffer, sizeof(buffer) - 1);
                    if (n > 0)
                    {
                        buffer[n] = 0;
                        std::cout << "server回显的信息: " << buffer << std::endl;
                    }
                    else
                    {
                        break;
                    }
                }
            }
        }

        ~tcpClient()
        {
            if (_sock >= 0)
                close(_sock);
        }

    private:
        int _sock;
        std::string _serverip;
        uint16_t _serverport;
    };
}

1.成员函数

int _sock:套接字
std::string _serverip:server的IP地址
uint16_t _serverport:server发PORT

2.接口

3.mian函数

static void Usage(string proc)
{
    cout << "Usage:\n\t" << proc << " server_ip server_port\n\n";
}

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

    uint16_t serverport = atoi(argv[2]);
    std::string serverip = argv[1];
    std::unique_ptr<tcpClient> tcli(new tcpClient(serverip, serverport));
    tcli->initClient();
    tcli->start();

    return 0;
}

在同一个服务器运行客户端和服务端

1.ESTABLISHED:指的是客户端被服务端所接收了

2.tcp的Server查到的是服务器的链接

3.tcp的Client查到的是客户端的链接

4.两端都是全双工的

3.守护进程化

1.守护进程

1.xshell在操作系统下会生成一个会话,此时bash充当前台任务,其他都是后台任务

2.前台任务有且只能有一个,而后台任务能允许有多个

3.所有的作业可以前后台转换。先fg转换,再ctrl+Z切换后台暂停,最后bg使得暂停任务重新开始

4.这些任务可能受到用户的登录和注销的影响的,我们需要将作业自称独立会话,和终端设备无关,这样的进程为守护进程

1.setsid:将非组员的进程独立成一个会话,并且该进程变成此会话的组长

2.不能是原先就是组长的进程

/dev/null:黑洞文件,信息重定向到该文件默认数据全部清空丢弃。

2.代码

#define DEV "/dev/null"

void daemonself(const char *currPath = nullptr)
{
    // 1.让调用进程忽略异常信号
    signal(SIGPIPE, SIG_IGN);

    // 2.如何让自己不是组长,setsid
    if (fork() > 0)
        exit(0);
    // 守护进程 -- 精灵进程,孤儿进程的一种
    pid_t n = setsid();
    assert(n != -1);

    // 3.守护进程脱离终端,关闭或者重定向以前进程默认打开文件 012
    int fd = open(DEV, O_RDWR);
    if (fd >= 0)
    {
        dup2(fd, 0);
        dup2(fd, 1);
        dup2(fd, 2);

        close(fd);
    }
    else
    {
        close(0);
        close(1);
        close(2);
    }

    // 4.可选:进程执行路径发生变化
    if (currPath)
        chdir(currPath);
}

1.让调用进程忽略异常信号:不出现进程错误的问题使得守护过程终止

2.让自己不是组长(setsid):守护化的条件就是进程不能是会话的组长,要想不是,上面的处理方式是fork一个子进程,当前进程直接释放,使得fork的子进程变成孤儿进程,孤儿进程托付给操作系统,此时的组长是bash,那么就可以进行setsid了

3.守护进程脱离终端,关闭或者重定向以前进程默认打开文件 012:不直接关闭,将/dev/null这个黑洞文件重定向到012中。

4.可选:进程执行路径发生变化。将当前进程执行的路径换掉。

5.此时服务端的main函数逻辑为:

int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = atoi(argv[1]);
    std::unique_ptr<tcpServer> tsvr(new tcpServer(port));
    tsvr->initServer();

    // 守护进程化
    daemonself();
    tsvr->start();
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/62254.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【TypeScript】TS接口interface类型(三)

【TypeScript】TS接口interface类型&#xff08;三&#xff09; 【TypeScript】TS接口interface类型&#xff08;三&#xff09;一、接口类型二、实践使用2.1 常规类型2.2 设置属性只读 readonly2.3 设置索引签名2.4 设置可选属性2.5 函数类型接口 一、接口类型 TypeScript中的…

QtWebApp同时开启http服务和https服务,接受来自客户端的不同请求并进行相应的处理

零、前言 在 QtWebApp开发https服务器&#xff0c;完成客户端与服务器基于ssl的双向认证&#xff0c;纯代码操作 一文中已经用纯代码的形式完成了客户端和服务端的 https 协议交互。 不过&#xff0c;只是开放了https服务&#xff0c;更多情况下&#xff0c;http服务和https服…

奥威BI—数字化转型首选,以数据驱动企业发展

奥威BI系统BI方案可以迅速构建企业级大数据分析平台&#xff0c;可以将大量数据转化为直观、易于理解的图表和图形&#xff0c;推动和促进数字化转型的进程&#xff0c;帮助企业更好地了解自身的运营状况&#xff0c;及时发现问题并采取相应的措施&#xff0c;提高运营效率和质…

多线程(JavaEE初阶系列7)

目录 前言&#xff1a; 1.常见的锁策略 1.1乐观锁和悲观锁 1.2轻量级锁和重量级锁 1.3自旋锁和挂起等待锁 1.4互斥锁与读写锁 1.5可重入锁与不可重入锁 1.6公平锁与非公平锁 2.CAS 2.1什么是CAS 2.2自旋锁的实现 2.3原子类 3.synchronized 3.1synchronized的原理以…

解决Element Plus中Select在El Dialog里层级过低的问题(修改select选项框样式)

Element Plus是Vue.js的一套基于Element UI的组件库&#xff0c;提供了丰富的组件用于构建现代化的Web应用程序。其中&#xff0c;<el-select>是一个常用的下拉选择器组件&#xff0c;但在某些情况下&#xff0c;当<el-select>组件嵌套在<el-dialog>&#xf…

DP-GAN剩余代码

在前面计算完损失后&#xff0c;该进行更新&#xff1a; 1&#xff1a;netEMA是模型的生成器&#xff1a; 遍历生成器的state_dict&#xff0c;将每一个键对应的值乘以EMA_decay。 接着根据当前迭代步数计算num_upd&#xff0c;每1000,2500,10000代倍数就执行一次。 当num…

Spring(11) Bean的生命周期

目录 一、简介二、Bean的流程1.BeanDefinition2.Bean 的生命周期 三、代码验证1.User 实体类2.MyBeanPostProcessor 后置处理器3.SpringConfig 扫描包配置4.UserTest 测试类5.测试结果6.模拟AOP增强 一、简介 首先&#xff0c;为什么要学习 Spring 中 Bean 的生命周期呢&#…

【网络工程】网络流量分析工具 Wireshark

文章目录 第一章&#xff1a;WireShark介绍第二章&#xff1a;WireShark应用第三章&#xff1a;Wireshark 实战 第一章&#xff1a;WireShark介绍 Wireshark (前身 Ethereal)&#xff1a;它是一个强大的网络封包分析软件工具 ! 此工具使用WinPCAP作为接口&#xff0c;直接与网卡…

TiDB Serverless 正式商用,全托管的云服务带来数据管理和应用程序开发的全新体验

八 年 前 &#xff0c;我们构建了 TiDB&#xff0c;一个开源分布式关系型数据库。 我们的目标是重新定义开发者和企业处理数据的方式&#xff0c;满足不断增长的可扩展性、灵活性和性能需求。 从那时起&#xff0c;PingCAP 便致力于为开发者和企业提供快速、灵活和规模化的数据…

线性代数 | 机器学习数学基础

前言 线性代数&#xff08;linear algebra&#xff09;是关于向量空间和线性映射的一个数学分支。它包括对线、面和子空间的研究&#xff0c;同时也涉及到所有的向量空间的一般性质。 本文主要介绍机器学习中所用到的线性代数核心基础概念&#xff0c;供读者学习阶段查漏补缺…

Autosar通信入门系列05-聊聊一帧Can/CanFD报文发送时间?

本文框架 1. 概述2. 一帧CAN报文发送时间计算3. 一帧CanFD报文的传输时间计算3.1 标准CAN与CANFD两者间的区别3.2 CANFD报文传输时间计算 1. 概述 本篇我们一起看下一帧Can报文发送需要多长时间&#xff0c;下述文章里我们会首先计算下Can分别对应的字节数&#xff0c;再根据传…

Linux 下设置开机自启动的方法

文章目录 事先准备对于普通的 Linux对于 RedHat Enterprise Linux 9 笔者的运行环境&#xff1a; 设置成功过的 Linux&#xff1a; RedHat Enterprise Linux 9 x86_64 CentOS 8 x86_64 事先准备 进行这个教程之前&#xff0c;必须要先安装好一个 Linux 操作系统。这个 Linux…

C 语言高级3--函数指针回调函数,预处理,动态库的封装

目录 1.函数指针和回调函数 1.1 函数指针 1.1.1 函数类型 1.1.2 函数指针(指向函数的指针) 1.1.3 函数指针数组 1.1.4 函数指针做函数参数(回调函数) 2.预处理 2.1 预处理的基本概念 2.2 文件包含指令(#include) 2.2.1 文件包含处理 2.2.2 #incude<>和#include&q…

基于Jenkins+Python+Ubuntu+Docker的接口/UI自动化测试环境部署详细过程

基于JenkinsPythonUbuntuDocker的接口/UI自动化测试环境部署详细过程 1 Jenkins是什么&#xff1f;2 Jenkins目标是什么&#xff1f;3 什么是CI/CD?3.1 CI持续集成3.2 CD持续部署3.3 CD持续交付 4 Ubuntu环境4.1 环境需求4.2 实现思路 5 Ubuntu下安装Docker6 安装Jenkins6.1 拉…

Linux Day06

目录 一、printf输出问题 二、复制进程fork 2.1进程 2.2 pid_t fork(void); 注意&#xff1a; 2.3逻辑地址和物理地址 2.4写时拷贝技术 一、printf输出问题 printf 函数并不会直接将数据输出到屏幕&#xff0c;而是先放到缓冲区中&#xff0c;只有一下三种情况满 足&a…

24考研数据结构-第五章:树与二叉树

目录 第五章&#xff1a;树5.1树的基本概念5.1.1树的定义5.1.2 基本术语5.1.3 树的性质 5.2二叉树的概念5.2.1 二叉树的定义与特性5.2.2 几种特殊的二叉树5.2.3 二叉树的性质5.2.4 完全二叉树的性质5.2.5 二叉树的存储结构1. 顺序存储重要的基本操作非完全二叉树2. 链式存储逆向…

【Paper】2020_网络化多智能体系统的事件触发一致性研究_徐勇

徐勇. 网络化多智能体系统的事件触发一致性研究[D].浙江大学,2020.DOI:10.27461/d.cnki.gzjdx.2020.001385. 文章目录 5 已知 DoS 攻击策略下多智能体系统的事件触发安全一致性分析5.1 引言5.2 数学模型与问题描述5.3 控制器和事件触发条件的设计5.5 数值仿真程序 Main.m程序 M…

使用FreeMarker导出word文档(支持导出图片)

今天跟大家分享一下工作中比较实用的导出word 带图片的功能。 对于在idea开发中我们需要引入以下依赖&#xff1a; 2.对于eclipse 开发我们需要进入对应的jar包 这个必须放在lib下&#xff0c;同样也需要在当前项目的环境是加入该依赖 需要在MEAT-INF加入 首先制定word 导出…

UE5 半透明覆层材质

文章目录 前言介绍示例1示例2示例3 前言 本文采用虚幻5.2.1版本演示&#xff0c;介绍半透明覆层材质&#xff08;覆层材质&#xff09;。 介绍 半透明覆层材质是 UE5.1 版本 更新的功能&#xff0c;使用半透明覆层材质&#xff0c;可以轻松的给物体表面附着一层材质。 在UE5…