网络套接字补充——TCP网络编程

六、TCP网络编程

6.1IP地址字符串和整数之间的转换接口

//字符串转整数接口
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int inet_aton(const char *cp, struct in_addr *inp);
int inet_pton(int af, const char *strptr, void *addrptr);//注意dst指的是in_addr *的地址
in_addr_t inet_addr(const char *cp);//将字符串转32位并且是网络序列的;
//整数转字符串
char *inet_ntoa(struct in_addr in);//将整数转为字符串并且将网络字节序转为主机字节序
const char *inet_ntop(int af, const void *addrptr,
                      char *strptr, socklen_t size);

​ 需要注意的是inet_ntoa函数这个函数返回的是一个静态变量地址,使用时有覆盖问题和线程安全问题;最好是使用inet_ntop;

6.2补充知识

1.将网络套接字进行封装

​ 构造函数之中最好少做一些有风险的事情,这样可以保证最起码对象是没有问题的;其他如打开文件之类的操作就交给其他函数去完成;

2.获取新连接会产生多个文件描述符

​ 服务器本地的文件描述符用来进行监听连接,获取新连接,真正进行IO通信的文件描述符是后生成的;这样既提高了服务器的并发度;

3.telnet的使用

​ 默认使用的就是TCP;

​ 使用ctrl+]进入,回车后进行输入(会自动在输入的文本后面加\r\n)会回显数据,q退出;

4.TCP连接中的异常问题

​ a.当客户端直接退出时,服务端就会读到0,此时需要关闭为客户端打开的文件描述符;

​ b.读端关闭,写端继续写会导致操作系统发送SIGPIPE信号,然后出现杀死服务器的情况,所以一般要将13号信号忽略;
5.网络抖动断开连接,客户端自动发起连接请求设计

​ 长服务不合理,但是可以将客户端设置成循环发起连接;当网络出错会发生读写错误,这时候就可以进行对服务器重连;

int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        return 0;
    }
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    // 发起连接请求

    // 设置服务器套接字
    sockaddr_in server;
    bzero(&server, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);
    inet_pton(AF_INET, serverip.c_str(), &server.sin_addr);

    while (true)
    {
        int cnt = 5;
        bool isreconnect = false;
        // 1.创建套接字
        int sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (sockfd < 0)
        {
            cerr << "sockket" << endl;
            return 1;
        }
        do
        {
            // 2.发起连接
            if (connect(sockfd, (sockaddr *)&server, sizeof(server)) < 0)
            {
                isreconnect = true;
                cnt--;
                cerr << "connect error, reconnect count: " << 5 - cnt << endl;
                sleep(1);
            }
            else if (isreconnect)
            {
                isreconnect = false;
                cout << "connect success" << endl;
            }
        } while (isreconnect && cnt);
        if (isreconnect == true)
        {
            cout << "reconnect fail" << endl;
            close(sockfd);
            return 1;
        }

        // 3.产生数据并且发送数据
        string message;

        cout << "Please Enter@ ";
        getline(cin, message);
        if (write(sockfd, message.c_str(), message.size()) < 0)
        {
            cerr << "write error" << endl;
            isreconnect = true;
            continue;
        }
        char buff[4096];
        int n = read(sockfd, buff, sizeof(buff) - 1);
        if (n > 0)
        {
            buff[n] = 0;
            cout << buff << endl;
        }
        close(sockfd);
    }

    return 0;
}

6.tcp服务器重连

​ 服务器断开后不能直接连接,一般要等待120s左右;

6.3使用接口

6.3.1创建套接字

​ 和udp使用是一样的;

6.3.2绑定套接字

​ 和udp使用是一样的;

6.3.3设置监听

​ 由于TCP是面向连接的,所以在通信前要建立连接,将套接字设置为监听状态;

#include <sys/types.h>         
#include <sys/socket.h>
int listen(int sockfd, int backlog);
//第二个参数表示的是全连接的队列的长度,一般不能设置的太大;
6.3.4获取新连接

​ 此处包括以上接口都是阻塞的;

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
//返回值,成功返回一个文件描述符,失败返回-1,错误码被设置;

​ 获取新连接成功后要根据客户端的套接字信息提供服务;

6.3.5客户端发起连接请求

​ 客户端需要绑定但是不需要显式进行绑定,系统会在客户端发起连接请求的时候自动绑定

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

6.4查看网络状态

netstat -nltp
#n显示成数字,l表示listen,表示tcp

6.5单进程版echo服务器

​ 缺陷是同时最多只能有一个客户端进行访问;UDP所有的客户端用的是一个sockfd,一个文件,可以同时读写,而TCP每个客户端对应一个sockfd,一个文件;单进程下对一个文件读写时,服务器因为处理消息是循环处理,必须读完退出循环服务,才能继续获取新连接,此时另一个客户端已经想打开的sockfd文件写入很多数据,当服务端接收连接请求时,会将发送过来的一大批数据处理后返回,这样就无法实现正常的服务器;

char buff[4096];
while (true)
{
    // 数据读取
    ssize_t n = read(sockfd, buff, sizeof(buff) - 1);
    if (n > 0)
    {
        buff[n] = '\0';
        std::cout << "client say@ " << buff << std::endl;

        // 数据回显
        std::string echo_string;
        echo_string += "tcpserver say#";
        echo_string += buff;
        write(sockfd, echo_string.c_str(), echo_string.size());
    }
    else if (n == 0)
    {
        lg(Info, "%s:%d quit..., server close sockfd: %d", clientip.c_str(), clientport, sockfd);
        break;
    }
    else
    {
        lg(Warning, "read error, sockfd: %d, clientip: %s, clientport: %d", sockfd, clientip.c_str(), clientport);
        break;
    }
}

6.6多进程版echo服务器

​ 1.子进程可以看见listenfd_,所以要关闭无关的文件描述符;2.父进程不关心sockfd,要去接收新的连接,如果不关闭就会导致之后的很多文件描述符没有关闭,不断从新的下标打开文件描述符,而不是重新分配;

​ 2.子进程中继续fork(),然后子进程退出,被父进程阻塞等待回收,父进程继续获取新的连接,孙子进程被操作系统领养,执行服务部分;也可以使用信号异步等待的方式实现;

​ 3.也可以在循环执行获取连接和执行任务之前创建子进程,但是会存在数据不一致问题需要用信号量;

​ 4.多进程创建的成本过高,所以应该选择多线程;

//方式1
pid_t id = fork();
if (id < 0)
{
    std::cerr << "fork error" << std::endl;
}
else if (id == 0)
{
    close(listensockfd_);
    if (fork() > 0)
    {
        exit(0);
    }
    // 此处执行代码的是孙子进程,会被做系统领养
    service(sockfd, clientip, clientport);
    close(sockfd);
    exit(0);
}
close(sockfd);
pid_t rid = waitpid(id, nullptr, 0);
(void)rid;
//方式2
signal(SIGCHLD, SIG_IGN);
pid_t id = fork();
if (id == 0)
{
    close(listensockfd_);
    service(sockfd, clientip, clientport);
    close(sockfd);
    exit(0);
}
close(sockfd);

6.7多线程版本的echo服务器

​ 1.因为线程中大部分资源都是共享的所以不可以关闭文件描述符,否则会出错;

​ 2.线程没有退出时会有峰值的,服务器此时压力很大,所以长服务是不合理的;

​ 3.创建线程也是有成本的,即系统调用的成本,所以应该用线程池;

struct threaddata
{
    threaddata(const int sockfd, const std::string &clientip, const uint16_t &clientport, tcpserver *t) : sockfd_(sockfd), clientport_(clientport),
    clientip_(clientip), t_(t)
    {
    }
    int sockfd_;
    uint16_t clientport_;
    std::string clientip_;
    tcpserver *t_;
};

pthread_t tid;
threaddata *td = new threaddata(sockfd, clientip, clientport, this);
pthread_create(&tid, nullptr, routine, (void *)td);

static void *routine(void *args)
{
    threaddata *td = static_cast<threaddata *>(args);
    pthread_detach(pthread_self());
    td->t_->service(td->sockfd_, td->clientip_, td->clientport_);
    delete td;
    return nullptr;
}

6.8线程池版本的echo服务器

​ 1.线程池里不可以执行长时间的服务;2.服务器关闭了客户端套接字,客户端继续写入,会触发服务器异常,返回一个RST消息,然后客户端操作系统发送SIGPIPE信号杀死客户端进程;

class Task
{
    public:
    Task(const int &sockfd, const std::string &clientip, const uint16_t &clientport)
        : sockfd_(sockfd), clientport_(clientport), clientip_(clientip) {}

    void run()
    {
        char buff[4096];
        // 数据读取
        ssize_t n = read(sockfd_, buff, sizeof(buff) - 1);
        if (n > 0)
        {
            buff[n] = '\0';
            std::cout << "client say@ " << buff << std::endl;

            // 数据回显
            std::string echo_string;
            echo_string += "tcpserver say#";
            echo_string += buff;
            write(sockfd_, echo_string.c_str(), echo_string.size());
        }
        else if (n == 0)
        {
            lg(Info, "%s:%d quit..., server close sockfd: %d", clientip_.c_str(), clientport_, sockfd_);
        }
        else
        {
            lg(Warning, "read error, sockfd: %d, clientip: %s, clientport: %d", sockfd_, clientip_.c_str(), clientport_);
        }
        close(sockfd_);
    }
    void operator()()
    {
        run();
    }

    ~Task()
    {
    }

    private:
    int sockfd_;
    uint16_t clientport_;
    std::string clientip_;
};

Task t(sockfd, clientip, clientport);
ThreadPool<Task>::GetInstance()->Push(t);

#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <pthread.h>
#include "Task.hpp"
#include <unistd.h>

struct ThreadInfo
{
    pthread_t tid;
    std::string name;
};

static const int defaultnum = 5;

template <class T>
    class ThreadPool
    {
        private:
        void Lock()
        {
            pthread_mutex_lock(&_mutex);
        }

        void UnLock()
        {
            pthread_mutex_unlock(&_mutex);
        }

        void Wakeup()
        {
            pthread_cond_signal(&_cond);
        }

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

        bool IsQueueEmpty()
        {
            return _tasks.empty();
        }

        std::string GetThreadName(pthread_t tid)
        {
            for (const auto e : _threads)
            {
                if (e.tid == tid)
                {
                    return e.name;
                }
            }
            return "None";
        }

        public:
        T Pop()
        {
            T t = _tasks.front();
            _tasks.pop();
            return t;
        }

        void Push(const T &t)
        {
            Lock();
            _tasks.push(t);
            Wakeup();
            UnLock();
        }

        static void *HandlerTask(void *args) // 类内函数默认都有一个this指针,静态成员函数无法直接看到成员属性
        {
            ThreadPool<T> *tp = static_cast<ThreadPool<T> *>(args);
            std::string name = tp->GetThreadName(pthread_self());
            while (true)
            {
                tp->Lock();
                while (tp->IsQueueEmpty())
                {
                    tp->ThreadSleep();
                }
                Task t = tp->Pop();
                tp->UnLock();
                t();
            }
        }

        void Start() // 创建线程
        {
            int num = _threads.size();
            for (int i = 0; i < num; i++)
            {
                _threads[i].name = "thread-" + std::to_string(i + 1);
                pthread_create(&(_threads[i].tid), nullptr, HandlerTask, this);
            }
        }

        static ThreadPool<T> *GetInstance()
        {
            pthread_mutex_lock(&_smutex);
            if (_tp == nullptr)
            {
                std::cout << "log : singleton create done first!" << std::endl;
                _tp = new ThreadPool<T>();
            }
            pthread_mutex_unlock(&_smutex);
            return _tp;
        }

        private:
        ThreadPool(int num = defaultnum) : _threads(num)
        {
            pthread_mutex_init(&_mutex, nullptr);
            pthread_cond_init(&_cond, nullptr);
        }

        ThreadPool(const ThreadPool<T> &tp) = delete;
        const ThreadPool<T> &operator=(const ThreadPool<T> tp) = delete;
        ~ThreadPool()
        {
            pthread_mutex_destroy(&_mutex);
            pthread_cond_destroy(&_cond);
        }

        std::vector<ThreadInfo> _threads;
        std::queue<T> _tasks;
        pthread_mutex_t _mutex;
        pthread_cond_t _cond;
        static ThreadPool<T> *_tp;
        static pthread_mutex_t _smutex;
    };
template <class T>
    ThreadPool<T> *ThreadPool<T>::_tp = nullptr;
template <class T>
    pthread_mutex_t ThreadPool<T>::_smutex = PTHREAD_MUTEX_INITIALIZER;

6.9线程池版翻译服务器

​ 打开KV式的字符串文件,来比较进行翻译;

#include <iostream>
#include <string>
#include <unordered_map>
#include <fstream>
#include <cstring>
#include "log.hpp"

extern Log lg;

const std::string filename = "./dict.txt";

// 打开字典并且自动将初始化dict
class init
{
    public:
    bool split(const std::string &line, std::string &part1, std::string &part2)
    {
        auto pos = line.find(sep);
        if (pos == std::string::npos)
        {
            return false;
        }
        part1 = line.substr(0, pos);
        part2 = line.substr(pos + sep.size());
        return true;
    }
    init()
    {
        // 1.打开文件
        std::ifstream in(filename); // 默认打开文件
        if (!in.is_open())
        {
            lg(Fatal, "open %s file error, errno: %d, strerror: %s", filename.c_str(), errno, strerror(errno));
            exit(4);
        }
        std::string line;
        // 2.对文件进行按行读取
        while (std::getline(in, line))
        {
            std::string part1, part2;
            split(line, part1, part2);
            dict_[part1] = part2;
        }
        // 3.关闭文件
        in.close();
    }
    std::string translation(const std::string &key)
    {
        auto it = dict_.find(key);
        if (it != dict_.end())
        {
            return dict_[key];
        }
        else
        {
            return "unknown";
        }
    }

    private:
    std::unordered_map<std::string, std::string> dict_;
    static const std::string sep;
};
const std::string init::sep = ": ";
// 处理任务
buff[n] = '\0'; // 当作字符串使用
std::string echo_string;
echo_string += it.translation(buff);
write(sockfd_, echo_string.c_str(), echo_string.size());

七、对服务守护进程化

7.1会话session相关概念

​ 在Linux系统中,用户进行登陆的时候,操作系统会为用户形成一个会话(session);为每一个会话创建一个bash进程,来为用户提供命令行的服务,这个bash与键盘和显示器是直接相关的;这个bash默认是前台进程,而一个会话最多只能有一个前台进程,但是可以有多个后台进程

​ 键盘信号只能发送给前台进程,bash对键盘信号做了特殊处理无法直接ctrl+c杀死;

​ 当直接运行程序时形成的进程默认是前台进程,可以被键盘信号直接俄杀死,但是在命令行最后加上&就会形成后台进程,bash默认变为前台进程,而后台进程无法获取键盘信号,会一直执行;

​ 前台进程和后台进程都可以正常运行;但是后台进程运行不影响前台输入命令,这样提高了并发度;前台和后台进程的区别就是只有前台进程才可以获取键盘输入信息;

​ 为了保证总有进程可以键盘输入,所以前台进程是必须一直存在;

在这里插入图片描述

7.2会话相关操作

​ 注意是在当前会话进行操作,其他会话是看不到的;

1.前台进程

./a.out
#前台任务的方式运行程序;

2.后台进程

./a.out &
#后台任务的方式运行程序;

3.fg

fg 任务号
#将任务号对应的后台任务提到前台并唤醒,方便用ctrl+c杀死进程;
#任务号默认从1开始,之后每次创建一个后台程序,任务号就++;

在这里插入图片描述

4.jobs

jobs
#查看当前会话的后台任务;

在这里插入图片描述

5.ctrl+z

ctrl+z
#将前台任务暂停,因为暂停后的组内进程没有意义的,导致没人获取键盘,所以会被放到后台去,bash提到前台

6.bg

bg 任务号
#将后台暂停的任务唤醒

7.3Linux中进程间关系

​ 即进程之间除了独立的关系外还构成组的关系;进程组来负责完成一个任务

​ 操作系统要将会话进行管理,登陆时创建了session的结构体来描述会话的信息;通过sid来标识会话的唯一性,sid为bash进程pid;

​ 单进程的任务是自成一个进程组;多进程的任务,父进程是组长,构成一个进程组;进程组以组长的pid为进程组号;

在这里插入图片描述

​ 当会话关闭时,bash会释放(前台任务会关闭),但是后台任务确实没有释放;由于bash是后台任务的父进程,直接释放会导致执行后台任务的进程组内进程托孤给操作系统,需要注意的是ppid变成了1,与终端没有关系了;其他属性也被保留;这就会导致后台任务收到会话登录和退出的影响;

​ Windows中的用户在退出(注销)时会将会话内的所有的进程全部关闭;

在这里插入图片描述

7.4守护进程化

​ 为了使得进程不被会话登录和退出影响,需要进行守护进程化;即让一个进程自成一个会话,不需要和键盘显示器进行关联,自成进程组自成会话;

​ 守护进程的本质也是孤儿进程;

​ 守护进程不需要使用显示器和键盘文件了;但是也不可以将这些文件关闭,防止后续使用出错 ;最好的方式就是重定向到**/dev/null**这个垃圾桶,所有的内容都会被自动丢弃;这样就实现了远程服务的效果;

守护进程一般以d结尾;

#include <unistd.h>
pid_t setsid(void);
//进程组组长不可以守护进程化,所以需要子进程来执行任务;
//1.自己实现守护进程
void Daemon(const std::string &cwd = "")
{
    // 1.忽略异常信号
    signal(SIGPIPE, SIG_IGN);
    signal(SIGCHLD, SIG_IGN);
    signal(SIGSTOP, SIG_IGN);

    // 2.变成独立的会话
    if (fork() > 0)
        exit(0);
    setsid();

    // 3.更改调用进程的当前工作目录
    if (!cwd.empty())
    {
        chdir(cwd.c_str());
    }

    // 4.将标准输入、标准输出、标准错误重定向到/dev/null中,或者写到日志当中去
    int fd = open("/dev/null", O_RDWR);
    if (fd > 0)
    {
        dup2(fd, 0);
        dup2(fd, 1);
        dup2(fd, 2);
    }
}

//2.系统调用守护进程
#include <unistd.h>
int daemon(int nochdir, int noclose);
//第一个参数为0表示将进程当前的工作目录改为根目录,第二个参数为.表示不将文件描述符重定向到\dev\null;

八、TCP三次握手和四次挥手

​ TCP协议在建立连接是通过三次握手,而释放连接使用的是四次挥手;建立连接成功之前accept会阻塞,成功之后才会返回;关闭一次文件就是两次挥手,双方都是要关闭文件的所以共挥手四次;

​ TCP和UDP都是全双工通信的;TCP底层创建了两个缓冲区,一个是发送缓冲区,一个是接收缓冲区;即一个文件描述符对应两个文件缓冲区,所以通信时全双工的,读和写是不会互相影响的;UDP没有对应的发送缓冲区;

​ 操作系统要对连接进行管理,创建结构体对象进行描述,然后用链表进行管理;

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

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

相关文章

Go微服务实战——服务治理(负载均衡,请求重试,服务熔断,服务降级)

负载均衡 在微服务架构中各个服务都是独立部署、可独立扩展和管理的。在上一节Go微服务实战——服务的注册与获取&#xff08;nacos做服务注册中心&#xff09;将所有的服务注册到注册中心&#xff0c;供其他服务使用。 这是对于整个系统的层面&#xff0c;对于单个服务来说&…

Linux:运营商在网络中扮演的角色

文章目录 ip目前的问题ip目前的几种解决方案私有ipVS公有ip运营商再谈ip划分运营商的角度看ip 本篇总结的是运营商在网络中扮演的角色 ip目前的问题 在目前看来&#xff0c;ip最大的问题是ip号不够用了&#xff0c;那这个问题如何解决呢&#xff1f; 在之前的内容中有子网掩…

什么是智慧公厕?智慧旅游下的智慧公厕功能和特点

智慧旅游下的智慧公厕功能和特点&#xff1f;智慧旅游是景区、公园、游乐场、文化场馆等领域的一种信息化解决方案&#xff0c;智慧公厕是智慧旅游极为重要的一部分&#xff0c;能大大提升游客满意度。智慧公厕采用物联网、互联网、大数据、云计算等技术&#xff0c;实现旅游景…

RPM与YUM

目录 rpm包的管理 介绍 rpm包的简单查询指令 rpm包名基本格式 rpm包的其他查询指令: 卸载rpm包 yum 介绍 rpm包的管理 介绍 rpm用于互联网下载包的打包及安装工具,它包含在某些Linux分发版中.它生成具有.RPM扩展名的文件.RPM是RedHat Package Manager(RedHat)软件包管…

SDWebImage源码解析---疑难问题解答

SDWebImage的简单流程图&#xff1a; 上图大致流程是对的&#xff0c;有几个没写到的地方&#xff1a; 加载沙盒中对应的图片后&#xff0c;不仅要显示&#xff0c;而且要把图片缓存到内存中下载完毕后&#xff0c;有一个异步解码的过程&#xff0c;没体现出来 网上有大佬做了…

修改nuxtjs项目中的浏览器图标步骤

处理步骤&#xff1a; 打开配置页面 使用el-upload 上传图片到后台 后台把图片转为ico&#xff0c;返回图标路径 配置页面修改本页面预览图&#xff0c;点击保存&#xff0c;修改的数据库。 通知nuxt布局页面&#xff0c;修改head节点中的图标属性&#xff0c;…

智慧酒店(二):AI智能分析网关V4视频分析技术在酒店管理中的应用

一、人工智能技术如何应用在酒店管理中&#xff1f; 随着科技的飞速发展&#xff0c;人工智能技术已经逐渐渗透到我们生活的方方面面&#xff0c;其中&#xff0c;酒店管理行业便是其应用的重要领域之一。人工智能技术以其高效、精准的特点&#xff0c;为酒店管理带来了革命性…

基于java的智能停车场管理系统

开发语言&#xff1a;Java 框架&#xff1a;ssm 技术&#xff1a;JSP JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09; 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclip…

文件管理原理

文章目录 1)一个文件&#xff0c;它是文件内容和文件属性的集合 文件文件属性文件内容 文件属性 文件内容 2)文件分为打开的文件和未打开的文件 3)打开的文件是谁打开的&#xff1f; 由进程打开&#xff01;而研究一个被打开的进程本质就是研究进程和文件的关系。 而被打开的…

vue快速入门(二)安装vue调试插件

教程很详细&#xff0c;直接上过程 上一篇 新增内容 在国内网站下载谷歌插件安装插件 点击跳转极简插件 此处我们以Chrome浏览器为例 到这里我们就成功安装了插件 使用上一篇博客的代码在浏览器F12调试一下 这样就可以使用了&#xff01;&#xff01;&#xff01;

依赖倒转原则

1.1 MM请求电脑 MM电脑坏了&#xff0c;需要修电脑&#xff0c;是因为每次打开QQ,一玩游戏&#xff0c;机器就死了。出来蓝底白字的一堆莫名奇妙的英文。蓝屏死机了&#xff0c;估计内存有问题。 1.2 电话遥控修电脑 遥控修理电脑&#xff0c;打开内存条&#xff0c;两根内存…

Python学习从0到1 day20 第二阶段 面向对象 ② 封装

缘分 朝生暮死犹如露水 —— 24.4.1 学习目标&#xff1a; 1.理解封装的概念 2.掌握私有成员的使用 一、面向对象三大特性&#xff1a; 面向对象编程&#xff0c;是许多编程语言都支持的一种编程思想 简单理解是&#xff1a;基于模板&#xff08;类&#xff09;去创建实体&…

免费分享一套SpringBoot+Vue健身房管理系统,帅呆了~~

大家好&#xff0c;我是java1234_小锋老师&#xff0c;看到一个不错的SpringBootVue健身房管理系统&#xff0c;分享下哈。 项目视频演示 【免费】SpringBootVue健身房管理系统 Java毕业设计_哔哩哔哩_bilibili【免费】SpringBootVue健身房管理系统 Java毕业设计项目来自互联…

【SQL Server】2. 将数据导入导出到Excel表格当中

最开始&#xff0c;博主介绍一下自己的环境&#xff1a;SQL Sever 2008 R2 SQL Sever 大致都差不多 1. 通过自带软件的方式 首先找到下载SQL Sever中提供的导入导出工具 如果开始界面没有找到自己下载的路径 C:\Program Files\Microsoft SQL Server\100\DTS\Binn下的DTSWiz…

题目:小明的背包5(蓝桥OJ 1178)

问题描述&#xff1a; 解题思路&#xff1a; 分组背包模板题&#xff0c;与优化01背包的不同之处在于第一维不可省略&#xff0c;要写s循环。注意要初始化 #include <bits/stdc.h> using namespace std; const int N 1e3 9; int dp[N][N]; // 分组背包模板&#xff0c;…

正则表达式浅析

正则表达式&#xff0c;又称正规表示法、常规表示法&#xff08;英语&#xff1a;Regular Expression&#xff0c;在代码中常简写为regex、regexp或RE&#xff09;&#xff0c;计算机科学的一个概念。正则表达式使用单个字符串来描述、匹配一系列符合某个句法规则的字符串。在很…

【详细教程制作】用户列表

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;开发者-曼亿点 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 曼亿点 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a…

什么是工业协议转换软件?

在现代工业自动化领域&#xff0c;随着技术的不断革新和智能化水平的提升&#xff0c;各种工业设备和系统之间的通信变得日益重要。然而&#xff0c;由于历史、技术差异和标准多样化等原因&#xff0c;不同的工业设备和系统往往采用各自独特的通信协议&#xff0c;导致它们之间…

短视频素材哪里找?6个短视频素材下载推荐

哈喽&#xff01;短视频制作的小艺术家们&#xff0c;是不是时常在探寻短视频素材哪里找的秘密&#xff1f;放下你的疑惑吧&#xff0c;我来带你揭开6个藏宝图&#xff0c;领你进入短视频素材的奇妙世界&#xff0c;让你的作品在抖音、快手等平台上大放异彩&#xff01; 蛙学网…

经典文献阅读之--als_ros(移动机器人的可靠蒙特卡罗定位)

0. 简介 在本文中&#xff0c;我们关注移动机器人定位的可靠性问题。蒙特卡罗定位&#xff08;MCL&#xff09;广泛用于移动机器人的定位。然而&#xff0c;由于缺乏判定MCL估计可靠性的方法&#xff0c;其安全性仍难以保证。本文提出了一种新型定位框架&#xff0c;能够同时实…