Linux - 第14节 - 网络编程套接字(三)

news2024/11/27 14:40:52

1.Linux远程控制的网络程序

1.1.Linux远程控制的网络程序(普通版)

创建serverTcp.cc文件,写入下图一所示的代码,创建clientTcp.cc文件,写入下图二所示的代码,创建log.hpp文件,写入下图三所示的代码,创建util.hpp文件,写入下图四所示的代码,创建ThreadPool.hpp文件,写入下图五所示的代码,创建Task.hpp文件,写入下图六所示的代码,创建Lock.hpp文件,写入下图七所示的代码,创建Makefile文件,写入下图八所示的代码,使用make命令生成serverTcp和clientTcp可执行程序,创建两个选项卡,一个选项卡使用./udpServer 8081命令运行serverTcp可执行程序,一个选项卡使用./clientTcp 127.0.0.1 8081命令运行clientTcp可执行程序,再创建一个选项卡,使用./clientTcp 127.0.0.1 8081命令运行clientTcp可执行程序,如下图九所示。

serverTcp.cc文件:

#include "util.hpp"
#include "Task.hpp"
#include "ThreadPool.hpp"
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <pthread.h>

class ServerTcp; // 申明一下ServerTcp

void execCommand(int sock, const std::string &clientIp, uint16_t clientPort)
{
    assert(sock >= 0);
    assert(!clientIp.empty());
    assert(clientPort >= 1024);

    char command[BUFFER_SIZE];
    while (true)
    {
        ssize_t s = read(sock, command, sizeof(command) - 1); //我们认为我们读到的都是字符串
        if (s > 0)
        {
            command[s] = '\0';
            logMessage(DEBUG, "[%s:%d] exec [%s]", clientIp.c_str(), clientPort, command);
            // 考虑安全
            std::string safe = command;
            if((std::string::npos != safe.find("rm")) || (std::string::npos != safe.find("unlink")))
            {
                break;
            }

            FILE *fp = popen(command, "r");
            if(fp == nullptr)
            {
                logMessage(WARINING, "exec %s failed, beacuse: %s", command, strerror(errno));
                break;
            }
            char line[1024];
            while(fgets(line, sizeof(line)-1, fp) != nullptr)
            {
                write(sock, line, strlen(line));
            }

            pclose(fp);
            logMessage(DEBUG, "[%s:%d] exec [%s] ... done", clientIp.c_str(), clientPort, command);
        }
        else if (s == 0)
        {
            // pipe: 读端一直在读,写端不写了,并且关闭了写端,读端会如何?s == 0,代表对端关闭
            // s == 0: 代表对方关闭,client 退出
            logMessage(DEBUG, "client quit -- %s[%d]", clientIp.c_str(), clientPort);
            break;
        }
        else
        {
            logMessage(DEBUG, "%s[%d] - read: %s", clientIp.c_str(), clientPort, strerror(errno));
            break;
        }
    }

    // 只要走到这里,一定是client退出了,服务到此结束
    close(sock); // 如果一个进程对应的文件fd,打开了没有被归还,文件描述符泄漏!
    logMessage(DEBUG, "server close %d done", sock);
}

class ThreadData
{
public:
    uint16_t clientPort_;
    std::string clinetIp_;
    int sock_;
    ServerTcp *this_;

public:
    ThreadData(uint16_t port, std::string ip, int sock, ServerTcp *ts)
        : clientPort_(port), clinetIp_(ip), sock_(sock), this_(ts)
    {
    }
};

class ServerTcp
{
public:
    ServerTcp(uint16_t port, const std::string &ip = "")
        : port_(port),
          ip_(ip),
          listenSock_(-1),
          tp_(nullptr)
    {
    }
    ~ServerTcp()
    {
    }

public:
    void init()
    {
        // 1. 创建socket
        listenSock_ = socket(PF_INET, SOCK_STREAM, 0);
        if (listenSock_ < 0)
        {
            logMessage(FATAL, "socket: %s", strerror(errno));
            exit(SOCKET_ERR);
        }
        logMessage(DEBUG, "socket: %s, %d", strerror(errno), listenSock_);

        // 2. bind绑定
        // 2.1 填充服务器信息
        struct sockaddr_in local; // 用户栈
        memset(&local, 0, sizeof local);
        local.sin_family = PF_INET;
        local.sin_port = htons(port_);
        ip_.empty() ? (local.sin_addr.s_addr = INADDR_ANY) : (inet_aton(ip_.c_str(), &local.sin_addr));
        // 2.2 本地socket信息,写入sock_对应的内核区域
        if (bind(listenSock_, (const struct sockaddr *)&local, sizeof local) < 0)
        {
            logMessage(FATAL, "bind: %s", strerror(errno));
            exit(BIND_ERR);
        }
        logMessage(DEBUG, "bind: %s, %d", strerror(errno), listenSock_);

        // 3. 监听socket,为何要监听呢?tcp是面向连接的!
        if (listen(listenSock_, 5 /*后面再说*/) < 0)
        {
            logMessage(FATAL, "listen: %s", strerror(errno));
            exit(LISTEN_ERR);
        }
        logMessage(DEBUG, "listen: %s, %d", strerror(errno), listenSock_);
        // 运行别人来连接你了

        // 4. 加载线程池
        tp_ = ThreadPool<Task>::getInstance();
    }

    void loop()
    {
        tp_->start();
        logMessage(DEBUG, "thread pool start success, thread num: %d", tp_->threadNum());
        while (true)
        {
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            // 4. 获取连接, accept 的返回值是一个新的socket fd ??
            // 4.1 listenSock_: 监听 && 获取新的链接-> sock
            // 4.2 serviceSock: 给用户提供新的socket服务
            int serviceSock = accept(listenSock_, (struct sockaddr *)&peer, &len);
            if (serviceSock < 0)
            {
                // 获取链接失败
                logMessage(WARINING, "accept: %s[%d]", strerror(errno), serviceSock);
                continue;
            }
            // 4.1 获取客户端基本信息
            uint16_t peerPort = ntohs(peer.sin_port);
            std::string peerIp = inet_ntoa(peer.sin_addr);

            logMessage(DEBUG, "accept: %s | %s[%d], socket fd: %d",
                       strerror(errno), peerIp.c_str(), peerPort, serviceSock);
            // 5 提供服务, echo -> 小写 -> 大写
            // 5.3 v3.2
            Task t(serviceSock, peerIp, peerPort, execCommand);
            tp_->push(t);
           
        }
    }

private:
    // sock
    int listenSock_;
    // port
    uint16_t port_;
    // ip
    std::string ip_;
    // 引入线程池
    ThreadPool<Task> *tp_;
};

static void Usage(std::string proc)
{
    std::cerr << "Usage:\n\t" << proc << " port ip" << std::endl;
    std::cerr << "example:\n\t" << proc << " 8080 127.0.0.1\n"
              << std::endl;
}

// ./ServerTcp local_port local_ip
int main(int argc, char *argv[])
{
    if (argc != 2 && argc != 3)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = atoi(argv[1]);
    std::string ip;
    if (argc == 3)
        ip = argv[2];
    
    ServerTcp svr(port, ip);
    svr.init();
    svr.loop();
    return 0;
}

clientTcp.cc文件:

#include "util.hpp"
// 2. 需要bind吗??需要,但是不需要自己显示的bind! 不要自己bind!!!!
// 3. 需要listen吗?不需要的!
// 4. 需要accept吗?不需要的!

volatile bool quit = false;

static void Usage(std::string proc)
{
    std::cerr << "Usage:\n\t" << proc << " serverIp serverPort" << std::endl;
    std::cerr << "Example:\n\t" << proc << " 127.0.0.1 8081\n"
              << std::endl;
}
// ./clientTcp serverIp serverPort
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    std::string serverIp = argv[1];
    uint16_t serverPort = atoi(argv[2]);

    // 1. 创建socket SOCK_STREAM
    int sock = socket(AF_INET, SOCK_STREAM, 0);
    if (sock < 0)
    {
        std::cerr << "socket: " << strerror(errno) << std::endl;
        exit(SOCKET_ERR);
    }

    // 2. connect,发起链接请求,你想谁发起请求呢??当然是向服务器发起请求喽
    // 2.1 先填充需要连接的远端主机的基本信息
    struct sockaddr_in server;
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverPort);
    inet_aton(serverIp.c_str(), &server.sin_addr);
    // 2.2 发起请求,connect 会自动帮我们进行bind!
    if (connect(sock, (const struct sockaddr *)&server, sizeof(server)) != 0)
    {
        std::cerr << "connect: " << strerror(errno) << std::endl;
        exit(CONN_ERR);
    }
    std::cout << "info : connect success: " << sock << std::endl;

    std::string message;
    while (!quit)
    {
        message.clear();
        std::cout << "请输入你的消息>>> ";
        std::getline(std::cin, message); // 结尾不会有\n
        if (strcasecmp(message.c_str(), "quit") == 0)
            quit = true;

        ssize_t s = write(sock, message.c_str(), message.size());
        if (s > 0)
        {
            message.resize(1024);
            ssize_t s = read(sock, (char *)(message.c_str()), 1024);
            if (s > 0)
                message[s] = 0;
            std::cout << "Server Echo>>> " << message << std::endl;
        }
        else if (s <= 0)
        {
            break;
        }
    }
    close(sock);
    return 0;
}

log.hpp文件:

#pragma once
 
#include <cstdio>
#include <ctime>
#include <cstdarg>
#include <cassert>
#include <cstring>
#include <cerrno>
#include <stdlib.h>
 
#define DEBUG 0
#define NOTICE 1
#define WARINING 2
#define FATAL 3
 
const char *log_level[]={"DEBUG", "NOTICE", "WARINING", "FATAL"};
 
// logMessage(DEBUG, "%d", 10);
void logMessage(int level, const char *format, ...)
{
    assert(level >= DEBUG);
    assert(level <= FATAL);
 
    char *name = getenv("USER");
 
    char logInfo[1024];
    va_list ap; // ap -> char*
    va_start(ap, format);
 
    vsnprintf(logInfo, sizeof(logInfo)-1, format, ap);
 
    va_end(ap); // ap = NULL
 
 
    FILE *out = (level == FATAL) ? stderr:stdout;
 
    fprintf(out, "%s | %u | %s | %s\n", \
        log_level[level], \
        (unsigned int)time(nullptr),\
        name == nullptr ? "unknow":name,\
        logInfo);
}

util.hpp文件:

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <cassert>
#include <ctype.h>
#include <unistd.h>
#include <strings.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include "log.hpp"

#define SOCKET_ERR 1
#define BIND_ERR   2
#define LISTEN_ERR 3
#define USAGE_ERR  4
#define CONN_ERR   5

#define BUFFER_SIZE 1024

ThreadPool.hpp文件:

#pragma once

#include <iostream>
#include <cassert>
#include <queue>
#include <memory>
#include <cstdlib>
#include <pthread.h>
#include <unistd.h>
#include <sys/prctl.h>
#include "Lock.hpp"

using namespace std;

int gThreadNum = 15;

template <class T>
class ThreadPool
{
private:
    ThreadPool(int threadNum = gThreadNum) : threadNum_(threadNum), isStart_(false)
    {
        assert(threadNum_ > 0);
        pthread_mutex_init(&mutex_, nullptr);
        pthread_cond_init(&cond_, nullptr);
    }
    ThreadPool(const ThreadPool<T> &) = delete;
    void operator=(const ThreadPool<T>&) = delete;

public:
    static ThreadPool<T> *getInstance()
    {
        static Mutex mutex;
        if (nullptr == instance) //仅仅是过滤重复的判断
        {
            LockGuard lockguard(&mutex); //进入代码块,加锁。退出代码块,自动解锁
            if (nullptr == instance)
            {
                instance = new ThreadPool<T>();
            }
        }

        return instance;
    }
    //类内成员, 成员函数,都有默认参数this
    static void *threadRoutine(void *args)
    {
        pthread_detach(pthread_self());
        ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);
        while (1)
        {
            tp->lockQueue();
            while (!tp->haveTask())
            {
                tp->waitForTask();
            }
            //这个任务就被拿到了线程的上下文中
            T t = tp->pop();
            tp->unlockQueue();
            t(); // 让指定的先处理这个任务
        }
    }
    void start()
    {
        assert(!isStart_);
        for (int i = 0; i < threadNum_; i++)
        {
            pthread_t temp;
            pthread_create(&temp, nullptr, threadRoutine, this);
        }
        isStart_ = true;
    }
    void push(const T &in)
    {
        lockQueue();
        taskQueue_.push(in);
        choiceThreadForHandler();
        unlockQueue();
    }
    ~ThreadPool()
    {
        pthread_mutex_destroy(&mutex_);
        pthread_cond_destroy(&cond_);
    }
    int threadNum()
    {
        return threadNum_;
    }

private:
    void lockQueue() { pthread_mutex_lock(&mutex_); }
    void unlockQueue() { pthread_mutex_unlock(&mutex_); }
    bool haveTask() { return !taskQueue_.empty(); }
    void waitForTask() { pthread_cond_wait(&cond_, &mutex_); }
    void choiceThreadForHandler() { pthread_cond_signal(&cond_); }
    T pop()
    {
        T temp = taskQueue_.front();
        taskQueue_.pop();
        return temp;
    }

private:
    bool isStart_;
    int threadNum_;
    queue<T> taskQueue_;
    pthread_mutex_t mutex_;
    pthread_cond_t cond_;

    static ThreadPool<T> *instance;
    // const static int a = 100;
};

template <class T>
ThreadPool<T> *ThreadPool<T>::instance = nullptr;

Task.hpp文件:

#pragma once

#include <iostream>
#include <string>
#include <functional>
#include <pthread.h>
#include "log.hpp"

class Task
{
public:
    //等价于
    // typedef std::function<void (int, std::string, uint16_t)> callback_t;
    using callback_t = std::function<void (int, std::string, uint16_t)>;
private:
    int sock_; // 给用户提供IO服务的sock
    uint16_t port_;  // client port
    std::string ip_; // client ip
    callback_t func_;  // 回调方法
public:
    Task():sock_(-1), port_(-1)
    {}
    Task(int sock, std::string ip, uint16_t port, callback_t func)
    : sock_(sock), ip_(ip), port_(port), func_(func)
    {}
    void operator () ()
    {
        logMessage(DEBUG, "线程ID[%p]处理%s:%d的请求 开始啦...",\
            pthread_self(), ip_.c_str(), port_);

        func_(sock_, ip_, port_);

        logMessage(DEBUG, "线程ID[%p]处理%s:%d的请求 结束啦...",\
            pthread_self(), ip_.c_str(), port_);
    }
    ~Task()
    {}
};

Lock.hpp文件:

#pragma once

#include <iostream>
#include <pthread.h>

class Mutex
{
public:
    Mutex()
    {
        pthread_mutex_init(&lock_, nullptr);
    }
    void lock()
    {
        pthread_mutex_lock(&lock_);
    }
    void unlock()
    {
        pthread_mutex_unlock(&lock_);
    }
    ~Mutex()
    {
        pthread_mutex_destroy(&lock_);
    }

private:
    pthread_mutex_t lock_;
};

class LockGuard
{
public:
    LockGuard(Mutex *mutex) : mutex_(mutex)
    {
        mutex_->lock();
        std::cout << "加锁成功..." << std::endl;
    }

    ~LockGuard()
    {
        mutex_->unlock();
        std::cout << "解锁成功...." << std::endl;
    }

private:
    Mutex *mutex_;
};

Makefile文件:

.PHONY:all
all:clientTcp serverTcp

clientTcp: clientTcp.cc
	g++ -o $@ $^ -std=c++11
serverTcp:serverTcp.cc
	g++ -o $@ $^ -std=c++11 -lpthread

.PHONY:clean
clean:
	rm -f serverTcp clientTcp

注:

1.popen函数的功能是可以分三步描述:首先创建一个管道,然后自动创建子进程执行command命令,将子进程执行后的数据通过管道放入到popen函数返回的FILE*文件中。popen函数函数声明如下图所示。参数command是需要被执行的命令,参数type指定文件指针的打开模式,可以是r(只读)或w(只写)等。

如果文件打开失败返回null并设置错误码,如果文件打开成功返回文件指针。

使用popen函数需要包含<stdio.h>头文件。

popen函数返回的文件指针使用完后,应该使用pclose函数将文件关闭。

1.2.Linux远程控制的网络程序(守护进程版)

创建serverTcp.cc文件,写入下图一所示的代码,创建clientTcp.cc文件,写入下图二所示的代码,创建log.hpp文件,写入下图三所示的代码,创建util.hpp文件,写入下图四所示的代码,创建ThreadPool.hpp文件,写入下图五所示的代码,创建Task.hpp文件,写入下图六所示的代码,创建Lock.hpp文件,写入下图七所示的代码,创建daemonize.hpp文件,写入下图八所示的代码,创建Makefile文件,写入下图九所示的代码,使用make命令生成serverTcp和clientTcp可执行程序,创建两个选项卡,一个选项卡使用./udpServer 8081命令运行serverTcp可执行程序,一个选项卡使用./clientTcp 127.0.0.1 8081命令运行clientTcp可执行程序,再创建一个选项卡,使用./clientTcp 127.0.0.1 8081命令运行clientTcp可执行程序,如下图十所示。

这里只要使用./udpServer 8081命令将服务端程序运行起来,就已经将服务端程序部署在了服务器中,即使此时关闭xshell选项卡退出用户登录,其他客户端主机也可以连接到云服务器上的服务端程序。

注:

1.一般以服务器的方式工作,对外提供服务的服务器,都是以守护进程(精灵进程)的方式在服务器中工作的,一旦启动之后,除非用户主动关闭,否则一直运行。

守护进程有一个特点,那就是其ppid为1,也就是说其父进程必须是操作系统,如下图所示,其中PGID为当前进程所属的进程组,SID为当前进程的会话ID。

进程组解释:

我们使用sleep 1000 | sleep 2000 |sleep 3000 &命令在后台创建三个进程,然后使用 ps axj | head -1 && ps axj | grep sleep命令查看这三个进程,如下图所示。三个进程的ppid都是28406,即bash进程,三个进程同属于28547进程组,即第一个进程的pid,也就是说同时创建三个进程,第一个创建的进程是该进程组的组长。

因为三个进程同属一个进程组,所以我们使用jobs命令可以看到这三个进程是在一个任务组的,如下图所示。

会话解释:

我们使用主机登录云服务器用户时,Linux云服务器会给我们形成一个叫会话的东西,会话内部由多个进程组构成,其中必须有且仅有一个前台进程组,如下图一所示。

操作系统提供的注销就是退出并重新登陆用户,重新创建会话及内部的进程组。我们有时电脑卡可以用注销来解决,本质就是将此刻会话里面的进程组全部关闭。

上图所示三个sleep进程的会话ID都是28375,使用 ps axj | head -1 && ps ajx | grep 28375 命令,如下图二所示,bash进程的pid为28375,同时也是进程组的组长和会话话首进程。创建三个sleep进程时,三个sleep进程构建了自己的进程组,但是三个sleep进程所属的会话依旧是bash会话。

当我们登录云服务器用户时,bash构建了一个会话,自己是会话话首,如果我们后面再启动新进程或启动新进程组,它们都是属于该bash会话的。

如果我们使用fg命令把某个进程任务启动到前台,那么就不能使用类似ls等命令了,如下图三所示,因为一个会话有且仅有一个前台进程组,如果将某个进程任务启动到前台,那么bash命令行解释器就会放到后台,放到后台就无法接收输入的命令了。

因此,在命令行中启动一个进程,本质是在会话中启动一个进程组(组内可以是一个进程),来完成某种任务。所有会话内的进程fork创建子进程或线程,一般而言依旧属于当前会话。

守护进程:对外提供服务的服务器进程,不能属于某个会话,否则服务器进程会受到会话对应的用户登录或注销操作的影响。我们应该将服务器进程脱离某一个用户的会话,让其独立的在服务器中形成自己的新会话,即自成进程组、自成新会话,这种进程就是守护进程或精灵进程。

2.要编写守护进程,需要使用setsid函数,将调用的进程设置为独立的会话,setsid函数声明如下图所示,哪一个进程调用该函数,那么该进程就会自成进程组、自成新会话。

如果调用成功会返回调用该函数进程的pid,如果调用失败返回-1,并且设置错误码。

使用setsid函数需要包含<unistd.h>头文件。

进程组的组长不能调用setsid函数,如果强行调用那么就会调用失败。但是只要正常启动某个可执行程序,该可执行程序一定就是组长,如何使得服务器进程不成为组长呢?答案是可以让服务器进程成为进程组内的第二个进程,常规做法:fork子进程,子进程就不再是组长进程了,它就可以成功调用setsid函数。

编写守护进程必做的工作:创建子进程,执行setsid函数和服务器进程的服务操作。

编写守护进程选做的工作:

(1)对于管道,写端一直在写,读端关闭,写端会被SIGPIPE信号终止。如果客户端退出,服务端也会收到SIGPIPE信号,因此在服务端可以忽略SIGPIPE信号。

(2)更改守护进程的工作目录,便于以绝对路径的方式,寻找Linux文件结构中的任何一个配置文件。

(3)一旦服务器进程成为守护进程,那么该进程就和键盘显示器等没有关系了,即和标准输入、标准输出、标准错误没有关系了,因此有两种做法。

第一种(不推荐):将0、1、2文件描述符关闭,但很少有人这样做。

第二种(推荐):/dev/null 是Linux下的垃圾桶或信息黑洞,如下图所示,凡是将消息写入到该目录操作或从该目录读取消息操作都会被直接丢弃。因此这里可以打开/dev/null,并对0、1、2文件描述符重定向。

3.想要关闭部署在云服务器上的服务进程,可以使用ps axj | grep serverTcp命令找到对应服务进程的pid,然后kill -9 pid值杀死服务进程。

4.守护进程的进程名一般以d结尾,因此这里将服务进程名改为serverTcpd,如下图所示。

5. 这里因为daemonize.hpp文件设置守护进程时,将0、1、2文件描述符重定向到了/dev/null垃圾桶,所以所有的日志信息都不会再被打印了。我们可以在log.txt文件中 #define LOGFILE "serverTcp.log",然后打开LOGFILE文件,使用dup2函数将标准输出和标准错误都写入到LOGFILE文件中。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Dart整理笔记 | Dart参考手册

Dart SDK 安装 如果是使用Flutter 开发&#xff0c;Flutter SDK自带&#xff0c;无需单独安装Dart SDK。 如果需要独立调试运行Dart代码&#xff0c;需要独立安装Dart SDK。 官方文档&#xff1a;https://dart.dev/get-dart windows(推荐): http://www.gekorm.com/dart-wind…

秒级数据写入,毫秒查询响应,天眼查基于 Apache Doris 构建统一实时数仓

导读&#xff1a; 随着天眼查近年来对产品的持续深耕和迭代&#xff0c;用户数量也在不断攀升&#xff0c;业务的突破更加依赖于数据赋能&#xff0c;精细化的用户/客户运营也成为提升体验、促进消费的重要动力。在这样的背景下正式引入 Apache Doris 对数仓架构进行升级改造&a…

浅聊一下Linuxptp

浅聊一下Linuxptp 文章目录 浅聊一下Linuxptp1.什么是Linuxptp2.安装Linuxptp3.源码解析一下1.8个带main函数的源文件1.hwstamp_ctl.c2.nsm.c3.phc2sys.84.phc_ctl.85.pmc.86.ptp4l.c7.timemaster.c8.ts2phc.c 2.clock.c文件 4.自己实践 1.什么是Linuxptp LinuxPTP&#xff08…

HIS系统是什么意思?HIS系统的主要功能有哪些?

HIS系统是什么意思&#xff1f; HIS系统即医院信息系统(全称为Hospital information System) &#xff0c;是指利用计算机软硬件技术和网络通信技术等现代化手段&#xff0c;对医院及其所属各部门的人流、物流、财流进行综合管理&#xff0c;对在医疗活动各阶段产生的数据进行采…

构造函数(包括默认构造函数) ,析构函数的使用与特性

文章目录 一、构造函数二、默认构造函数&#xff08;也是构造函数&#xff09;默认构造函数的种类&#xff1a;1.无参类型2.全缺省类型3.编译器自动生成的4.汇总 三、析构函数 一、构造函数 构造函数是一个特殊的成员函数&#xff0c;名字与类名相同,创建类类型对象时由编译器自…

opencv_c++学习(七)

一、图像颜色空间变换 一、图像颜色空间介绍 RGB颜色模型 具体的体现样式如下&#xff1a; 在opencv中有可以实现数据类型的转换接口&#xff0c;如下&#xff1a; Mat:convertTo (OutputArray m, int rtype, alpha, double 1, double beta)实现如下&#xff1a; a.conve…

Python-字典与集合

学习内容&#xff1a;Python基础入门知识 专栏作者&#xff1a;不渴望力量的哈士奇不渴望力量的哈士奇擅长Python全栈白宝书[更新中],⑤ - 数据库开发实战篇,网安之路,等方面的知识,不渴望力量的哈士奇关注云原生,算法,python,集成测试,去中心化,web安全,智能合约,devops,golan…

如何使用jenkins、ant、selenium、testng搭建自动化测试框架

如果在你的理解中自动化测试就是在eclipse里面讲webdriver的包引入&#xff0c;然后写一些测试脚本&#xff0c;这就是你所说的自动化测试&#xff0c;其实这个还不能算是真正的自动化测试&#xff0c;你见过每次需要运行的时候还需要打开eclipse然后去选择运行文件吗&#xff…

NR RLC(二)相关参数及format

欢迎关注同名微信公众号“modem协议笔记”。 实际查看RLC部分log难免要翻协议&#xff0c;查阅最多的就是相关参数的含义&#xff0c;反而RLC具体过程就没有像当初阅读时那样特别关注了。其实清楚RLC参数含义&#xff0c;看38.322就没那么困难。而RLC具体过程往往要用到相关参…

azkaban --- 案例实操

目录 案例一 &#xff1a; 输出Hello World 案例二 &#xff1a;作业依赖 案例三 &#xff1a;内嵌工作流 案例四 &#xff1a;自动失败 案例五 &#xff1a;手动失败 案例六 &#xff1a;JavaProcess 案例七 &#xff1a;启动服务 案例八 &#xff1a;Hbase 案例九 …

SpringBoot整合企业微信消息推送(四十五)

从头开始&#xff0c;并不意味着失败&#xff0c;相反&#xff0c;正是拥抱成功的第一步&#xff0c;即使还会继续失败 上一章简单介绍了 SpringBoot整合钉钉消息推送(四十四) , 如果没有看过,请观看上一章 一. 企业微信前期准备 用户需要注册一个企业微信&#xff0c; 并且登…

ANR基础 - Input系统

系列文章目录 提示&#xff1a;这里可以添加系列文章的所有文章的目录&#xff0c;目录需要自己手动添加 例如&#xff1a;第一章 Python 机器学习入门之pandas的使用 文章目录 系列文章目录前言一、Input系统概述二、整体框架1.整体框架类图2.核心启动过程2.1 initialize2.1 I…

浅析一下PTPD

浅聊一下PTPD 文章目录 浅聊一下PTPD1.什么是PTPD2.PTPD源码浅析一下1.src文件1.arith.c2.bmc.c3.constant.h 和 datatypes.h4.display.c5.management.c6.protocol.c7.ptp_datatypes.h8.ptp_primitives.h9.ptp_timers.c10.ptpd.c11.signaling.c12.timedomain.c 2.def文件夹3.de…

ROS:gazebo创建仿真地图,turtlebot3加载仿真地图进行建图,生成yaml和pgm地图信息

一.安装turtlebot3 Ubuntu18.04 实现&#xff1a;安装turtlebot3功能包、虚拟机与机器人之间的网络配置、测试机器人Cartographer建图_Charlesffff的博客-CSDN博客 二.安装gazebo ROS18.04&#xff1a;安装gazebo&#xff0c;下载模型_gazebo下载模型_Charlesffff的博客-CSD…

Linux 设备驱动程序(二)

系列文章目录 Linux 内核设计与实现 深入理解 Linux 内核&#xff08;一&#xff09; 深入理解 Linux 内核&#xff08;二&#xff09; Linux 设备驱动程序&#xff08;一&#xff09; Linux 设备驱动程序&#xff08;二&#xff09; Linux设备驱动开发详解 文章目录 系列文章目…

大型央企集团财务经营分析框架系列(三)

01集团经营管理分析的切入点 集团经营管理分析的切入点往往是从财务分析开始。 往往在一家企业里面&#xff0c;财务方面的信息化建设是要早于其它方面的信息化建设的&#xff0c;业务标准化程度比较高&#xff0c;数据标准化程度也比较高&#xff0c;分析框架也相对成熟。 …

栈和队列的相关功能实现及其基础应用

前言&#xff1a;栈和队列是常见的数据结构&#xff0c;它们在计算机科学中被广泛应用。栈和队列都是一些元素的集合&#xff0c;它们的主要区别在于数据的组织方式和访问顺序。在栈中&#xff0c;元素的添加和删除都在同一端进行&#xff0c;称为栈顶&#xff0c;而在队列中&a…

PMP考试100个主要知识点

1.一个项目在启动阶段会进行量级估算&#xff0c;准确范围是-50至100%。2000版的量级估算准确度为&#xff1a;-25%到75%。 2.质量控制通常先于范围确认执行&#xff0c;但这两个过程可以并列进行参考 3.Cost-plus-fixed-fee(CPFF)成本加固定费用合同。成本补偿型合同包括成本加…

快速入门ChatGPT和AIGC:底层原理、热门工具、行业现状【我们能做什么】

最近大家热议的ChatGPT和AI绘画工具的底层技术原理是什么&#xff1f;是如何发展到现在的&#xff1f;有哪些应用场景、热门工具&#xff1f;AIGC产业上下游有哪些公司&#xff1f;作为普通用户&#xff0c;我们还能接触哪些应用AI技术打造的商业解决方案&#xff1f;…… 我们…

微信小程序 录音+播放组件封装

展示 长按录音 松开结束录音 点击播放 再次点击暂停 再次点击继续播放 展示效果&#xff1a; 录音功能 录音功能&#xff08;手指按下开始录音 手指松开结束录音&#xff09;&#xff1a; 使用wx原生录音功能在 component 外新建 wx.getRecorderManager() RecorderManager…