【Linux】套接字的理解 基于TCP协议的套接字编程(单/多进程 / 线程池|英汉互译 / C++)

文章目录

  • 1. 前言
    • 1.1 网络方面的预备知识👇
    • 1.2 了解TCP协议
  • 2. 关于套接字编程
    • 2.1 什么是套接字 Socket
    • 2.2 socket 的接口函数
    • 2.3 Udp套接字编程的步骤
    • 2.4 sockaddr 结构
  • 3. 代码实现
    • 3.1 makefile
    • 3.2 log.hpp
    • 3.3 tcp_server.hpp
      • ① 框架
      • ② service() 通信服务
      • ③ initServer()(初始化服务器)
      • ④ startServer()(启动服务器)
    • 3.4 tcp_server.cc
    • 3.5 tcp_client.cc
    • 3.6 结果演示
  • 4. 线程池版本(实现英汉互译)
    • 4.1 线程池ThreadPool的实现
      • Task.hpp
    • 4.2 对多进程代码的修改 - TcpServer
    • tcp_server.hpp
      • 框架
      • service() 通信服务
      • dictOnline()(英汉互译/网络词典)
      • initServer()(初始化服务器)
      • startServer()(启动服务器)
    • 结果演示
  • 5. 完整代码

1. 前言

1.1 网络方面的预备知识👇

网络基础 - 预备知识(协议、网络协议、网络传输流程、地址管理)


1.2 了解TCP协议

首先我们对Tcp协议进行一个了解:

【网络基础】深入理解TCP协议:协议段、可靠性、各种机制

简单总结而言, TCP协议具有以下特点:

1.可靠传输:TCP通过三次握手建立连接,并使用序号、确认ACK和重传机制来确保数据的可靠性传输。
2.面向连接:在数据传输之前,TCP必须先建立连接,双方通过握手过程建立起通信信道。
3.数据流式传输:TCP将数据拆分成小块,并通过IP地址和端口号标识发送和接收方的数据包,保证数据的有序传输。
4.拥塞控制和流量控制:TCP具有拥塞控制和流量控制机制,通过调整发送方的发送速率和接收方的反馈机制来避免网络拥塞和资源浪费。


2. 关于套接字编程

2.1 什么是套接字 Socket

套接字(Socket) 是计算机网络中用于实现进程间通信的一种机制。它允许在不同计算机之间或同一计算机的不同进程之间进行数据传输和通信

套接字可以看作是网络通信中的一个端点 ,它由 IP地址 端口号 组成, 用于唯一标识网络中的通信实体点 。套接字提供了一组接口(通常是API)用于创建、连接、发送、接收和关闭连接等操作,以实现数据的传输和通信。


套接字可以分为两种类型(了解) 流套接字(Stream Socket) 数据报套接字(Datagram Socket)

  1. 流套接字 :基于 传输控制协议(TCP) 的套接字,提供面向连接的、可靠的、双向的数据传输。

    • 流套接字通过建立连接来实现数据的可靠传输,适用于需要保证数据完整性和顺序性的应用,如网页浏览、文件传输等。
  2. 数据报套接字:基于 用户数据报协议(UDP) 的套接字,提供无连接的、不可靠的数据传输。

    • 数据报套接字不需要建立连接,可以直接发送数据报给目标主机,适用于实时性要求高、对数据完整性和顺序性要求不高的应用,如视频流传输、实时游戏等。

2.2 socket 的接口函数

下面列举在我们进行Tcp与Udp的套接字编程所用的API;

// 创建 socket 文件描述符 (TCP/UDP, 客户端 & 服务器)
int socket(int domain, int type, int protocol);

// 绑定端口号 (TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address,
socklen_t address_len);

// 开始监听socket (TCP, 服务器)
int listen(int socket, int backlog);

// 接收请求 (TCP, 服务器)
int accept(int socket, struct sockaddr* address,
socklen_t* address_len);

// 建立连接 (TCP, 客户端)
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen)

2.3 Udp套接字编程的步骤

  1. 创建套接字:使用 socket() 函数创建套接字。

  2. 绑定套接字:利用 bind() 将套接字绑定到一个 IP 地址和端口上。

  3. 监听连接:对于服务器端,使用 listen() 开始监听连接请求。

  4. 接受连接:对于服务器端,使用 accept() 接受客户端的连接请求并创建新的套接字用于通信。

  5. 发送数据:使用 send() 函数发送数据到连接的另一端。

  6. 接收数据:使用 recv() 函数接收从连接的另一端发送过来的数据。

  7. 关闭连接:通信结束后,使用 close() 关闭连接的套接字以释放资源。


2.4 sockaddr 结构

首先:

  • IPv4、 IPv6地址类型分别定义为常数AF_INET、 AF_INET6
    • 这样只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容
  • socket API 可以都用struct sockaddr *类型表示, 在使用的时候需要强制转化成sockaddr_in;
    • 好处在于程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数;

sockaddr 结构是在套接字编程中表示网络地址的通用结构。本身是一个抽象的结构,最常见的使用是:用于表示 IPv4 地址。

sockaddr 结构定义如下:

struct sockaddr {
    sa_family_t sa_family; // 地址家族(如 AF_INET)
    char sa_data[14]; // 地址数据
};

在实际使用中,经常使用 struct sockaddr_in 结构来表示 IPv4 地址,定义如下:

struct sockaddr_in {
    sa_family_t sin_family; // 地址家族(AF_INET)
    in_port_t sin_port; // 端口号
    struct in_addr sin_addr; // IP 地址
    char sin_zero[8]; // 填充字段,通常为0
};

3. 代码实现

3.1 makefile

首先写一个简单的makefile文件,用于后续执行程序便于测试

.PHONY:all
all:tcp_client tcp_server

tcp_client:tcp_client.cc
	g++ -o $@ $^ -std=c++11 #-lpthread
tcp_server:tcp_server.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -f tcp_client tcp_server

3.2 log.hpp

在我们编写代码时,对于 异常情况报告或正常情况通知 ,利用log.hpp进行日志信息的记录:

#pragma once

#include <iostream>
#include <cstdio>
#include <cstdarg>

#include "log.hpp"

// 宏定义 日志级别
#define DEBUG   0
#define NORMAL  1
#define WARNING 2
#define ERROR   3
#define FATAL   4

// 全局字符串数组 : 将日志级别映射为对应的字符串
const char *gLevelMap[] = {
    "DEBUG",
    "NORMAL",
    "WARNING",
    "ERROR",
    "FATAL"
};

#define LOGFILE "./threadpool.log" // LOGFILE: 表示日志文件的路径

void logMessage(int level, const char* format, ...)
{
    // 判断DEBUG_SHOW 是否定义,分别执行操作

#ifndef DEBUG_SHOW // 将日志级别映射为对应的字符串
    if(level == DEBUG) return; // DEBUG_SHOW不存在 且 日志级别为 DEBUG时,返回
#endif
    // DEBUG_SHOW存在 则执行下面的日志信息 
    char stdBuffer[1024];
    time_t timestamp = time(nullptr);

    // 将日志级别和时间戳格式化后的字符串将会被写入到 stdBuffer 缓冲区中
    snprintf(stdBuffer, sizeof(stdBuffer), "[%s] [%ld] ", gLevelMap[level], timestamp);
    
    char logBuffer[1024];
    va_list args;`在这里插入代码片`
    va_start(args, format);

    vsnprintf(logBuffer, sizeof(logBuffer), format, args);
    va_end(args);

    printf("%s%s\n", stdBuffer, logBuffer);
}

对于该日志类,不再重点,根据需要可以自行调整编写,不多讲解。

3.3 tcp_server.hpp

① 框架

tcp_server.hpp 即服务器类,首先是对于该服务器的 框架

  • 该框架包含了我们实现该类中会编写的 相关函数 以及 成员变量
// 通信服务的代码
static void service(int sock, const string& clientip, const int16_t& clientport){}

class TcpServer // tcp服务器类
{
private:
    const static int gbacklog = 20;

public:
    TcpServer(uint16_t port, string ip = ""): _port(port), _ip(ip), listensock(-1) // 设定缺省值
    {}

    // 初始化服务器
    void initServer()
    {}

	// 启动服务器
    void startServer()
    {}

    ~TcpServer(){}

private:
    uint16_t _port; // 端口号
    string _ip; // ip地址
    int listensock; // 套接字
};

② service() 通信服务

该代码用于 处理客户端与服务器间的通信:

  • 读取sock的内容:
    • 读取成功,则打印出客户端信息与发送的内容
    • 读取失败,打印日志并退出
  • 最后回显内容给客户端并关闭sock
static void service(int sock, const string& clientip, const int16_t& clientport)
{
    char buffer[1024];
    while(true)
    {
        // 网络通信 可以直接使用read/write
        ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
        if(s > 0) {// 成功读取
            buffer[s] = 0;
            cout << clientip << " : " << clientport << "# " << buffer << endl;
        }else if (s == 0){ // 对端关闭了连接
            logMessage(NORMAL, "%s:%d shutdown, metoo", clientip.c_str(), clientport);
            break;
        }else{ // 错误
            logMessage(ERROR, "read socket error, %d:%s", errno, strerror(errno));
            break;
        }
        // 读取成功 将buffer内容写入sock(回显)
        write(sock, buffer, strlen(buffer));
    }
    close(sock);
}

③ initServer()(初始化服务器)

下面是 初始化服务器: 的代码,简单描述其步骤:

  1. socket()创建监听套接字
  2. bind()绑定客户端端口与ip
  3. listen()设置监听状态(因为tcp是面向连接的,需要先建立连接才能进行通信)
void initServer()
    {
        // 1. 创建socket —— 进程、文件方面
        listensock = socket(AF_INET, SOCK_STREAM, 0); // ipv4协议,套接字类型,协议类型
        if(listensock < 0) // 创建套接字失败
        {
            logMessage(FATAL, "%d : %s", errno, strerror(errno));
            exit(2); // 退出进程
        }
        logMessage(NORMAL, "create socket success, sock: %d", listensock); // 创建成功,输出信息

        // 2. bind —— 网络、文件方面
        struct sockaddr_in local; // 表示ipv4地址
        memset(&local, 0, sizeof(local)); // 初始化
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());

        // 3. 设置监听状态
        // TCP面向连接,正式通信前,应先建立连接
        if(listen(listensock, gbacklog) < 0)
        {
            logMessage(FATAL, "listen error: %d:%s", errno, strerror(errno));
            exit(4);
        }
        logMessage(NORMAL, "init server success.");
    }


④ startServer()(启动服务器)

下面是启动服务器的代码,简述其步骤:
首先下面代码分为 单进程与多进程 版本,在注释有标出

  1. 通过accept() 获取连接
  2. 获取连接成功后 进行双方的通信:
    • 对于单进程,提取客户端的ip与port后直接调用之前的service()即可
    • 对于多进程,首先通过fork创建子进程,进行功能分配。
  3. 从功能上讲:
    • 单进程版本
      • 单个进程负责监听套接字并处理连接请求,在处理连接的过程中,
      • 单个进程可能会阻塞在读取或写入数据的操作上,导致无法及时处理其他连接请求。
    • 多进程版本
      • 在父进程中负责监听套接字,并循环接收连接请求。
      • 每当有新的连接请求到来时,父进程会创建一个新的子进程来处理该连接。
      • 子进程独立于父进程,负责与客户端进行通信,父进程则继续监听新的连接请求。
      • 每个子进程都有自己的资源空间,因此可以独立处理连接,避免了单进程版本中可能出现的阻塞问题,提高了并发处理能力。
void startServer()
{
	//此signal : 多线程代码
    signal(SIGCHLD, SIG_IGN); // 忽略SIGCHLD信号
    while(true)
    {
        // 4. 获取连接
        struct sockaddr_in src;
        socklen_t len = sizeof(src);
        int servicesock = accept(listensock, (struct sockaddr*)&src, &len);
        if(servicesock < 0)
        {
            logMessage(ERROR, "accept error, %d:%s", errno, strerror(errno));
            continue;
        }
        // 获取连接成功
        uint16_t client_port = ntohs(src.sin_port);
        string client_ip = inet_ntoa(src.sin_addr);
        logMessage(NORMAL, "link success, servicesock: %d| %s : %d |\n", servicesock, client_ip.c_str(), client_port);
        // 开始通信:
        // v1: 单进程循环
        // service(servicesock, client_ip, client_port);

        // v2: 多进程
        // 子进程用于给新的连接提供服务(service),父进程继续执行循环接收连接
        pid_t id = fork();
        assert(id != -1);
        if(id == 0)
        {
            // 子进程: 用于提供服务,无需监听socket
            close(listensock);
            service(servicesock, client_ip, client_port);
            exit(0); // 此时僵尸状态
        }
        close(servicesock); // 父进程不提供服务
    }
}

3.4 tcp_server.cc

  • tcp_server.cc 用于 形成最后的可执行程序 ,在main函数中 初始化服务器并启动服务器即可
  • 对于TcpServer对象的创建,我们可以使用unique_ptr智能指针进行创建对象,用于在动态内存中分配对象,可以在不需要时自动释放内存。
  • 通过获取到的参数,创建对象
using std::cout;
using std::endl;

static void usage(string proc)
{
    cout << "\nUsage:" << proc << " port" << endl;
}

int main(int argc, char* argv[])
{
    if(argc != 2) { // 参数数量错误
        usage(argv[0]); // 输出正确使用方法
        exit(1); // 退出
    }

    uint16_t port = atoi(argv[1]); // 获取端口号
    // 智能指针创建 TcpServer对象
    std::unique_ptr<TcpServer> svr(new TcpServer(port));
    svr->initServer();
    svr->startServer();

    return 0; 
}

3.5 tcp_client.cc

  • 同理对于tcp_client.cc,首先获取传来的ip与端口号
  • 循环内:
    • 如果还未建立连接 创建套接字socksockaddr_in 结构体,后进行connect() 连接
    • 建立连接后 :持续读取用户输入的内容,并接收来自客户端的回显信息。
// 打印正确的程序使用方法
void Usage(std::string proc)
{
    std::cout << "\nUsage: " << proc << " <ip> <port>" << std::endl;
}

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

    std::string serverIp = argv[1];
    uint16_t serverPort = atoi(argv[2]);
    
    bool connectAlive = false; // 是否已经建立了连接
    int sock = 0;
    while(true)
    {
        if(!connectAlive)
        {
            sock = socket(AF_INET, SOCK_STREAM, 0);
            if(sock < 0) { // 创建套接字失败
                std::cerr << "socket error" << std::endl;
                exit(2);
            }

            struct sockaddr_in server;
            memset(&server, 0, sizeof(server));
            server.sin_port = htons(serverPort);
            server.sin_family = AF_INET;
            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;
                exit(3);
            }
            // 建立连接成功,讲connectALive 设为true
            std::cout << "connect success." << std::endl;
            connectAlive = true;
        }

        std::cout << "请输入# " << std::endl;
        std::string line;
        std::getline(std::cin, line);
        if(line == "quit") break;

        ssize_t s = send(sock, line.c_str(), line.size(), 0);
        if(s > 0) 
        { // send 成功
            char buffer[1024];
            ssize_t r = recv(sock, buffer, sizeof(buffer), 0);
            if(r > 0) { // recv 成功
                buffer[r] = '\0';
                std::cout << "server response: " << buffer << std::endl;
            } else { // recv 失败
                std::cerr << "recv() error: client receiving message failed." << std::endl;
                close(sock);
                connectAlive = false;
            }
        } 
        else {
            std::cerr << "send() error: client sending message failed." << std::endl;
            close(sock);
        }
    }

    return 0;
}

3.6 结果演示

如下图所示,当启动客户端后,客户端会有一个监听套接字

此时我们可以有多个客户端同时进行连接通信,并且能正确的接收到来自服务器的回显信息。
在这里插入图片描述


4. 线程池版本(实现英汉互译)

4.1 线程池ThreadPool的实现

对于线程池版本,首先我们需要自实现一个线程池,有关线程池的详细内容/ 代码在下面👇:

ThreadPool代码实例 与 理解

  • 而线程池版本的套接字实现,我们需要 对上面链接代码部分进行修改
    • 需要根据要求(在这里即英汉互译)对Task.hpp(任务类)进行更改:

Task.hpp

对于任务类,由于我们通信时需要:套接字,对方的ip、端口号,以及处理方法,所以 对Task类的变量和成员函数进行修改 即可:

#pragma once

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

// typedef std::function<int(int, int)> func_t;
using func_t = std::function<void(int, const std::string& ip, const uint16_t& port, const std::string& name)>;

class Task
{
public:
    // 构造
    Task(){}
    Task(int sock, const std::string ip, const uint16_t port, func_t func)
    :_sock(sock),_ip(ip),_port(port),_func(func)
    {}

    void operator()(const std::string& name)
    {
        _func(_sock, _ip, _port, name);
    }

private:
    int _sock;
    uint16_t _port;
    std::string _ip;
    func_t _func;
};

4.2 对多进程代码的修改 - TcpServer

由于我们将改为线程池的版本,所以这里我们对服务器的逻辑tcp_server.hpp作修改:

tcp_server.hpp

框架

  • 框架用于展示整个文件中包含的函数,以及类的实现,省略具体实现:
#pragma once

#include <iostream>
#include <string>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <assert.h>
#include <signal.h>
#include <memory>
#include <unordered_map>

#include "ThreadPool/threadPool.hpp"
#include "ThreadPool/log.hpp"
#include "ThreadPool/Task.hpp"

// 通信服务
static void service(int sock, const std::string& clientip, const uint16_t& clientport, const std::string& thread_name)
{}

// 英汉互译
static void dictOnline(int sock, const std::string& clientip, const uint16_t& clientport, const std::string& thread_name)
{}

class TcpServer
{
private:
    const static int gbacklog = 20;

public: 
    // 构造 + 析构
    TcpServer(const uint16_t& port, const std::string& ip = "0.0.0.0")
    : _ip(ip), _port(port),
      _listensock(-1), _threadpool_ptr(ThreadPool<Task>::getThreadPool())
    {}

    ~TcpServer()
    {}

    // 功能函数
    // 启动服务器
    void initServer()
    { }

    void startServer()
    { }

private:
    std::string _ip;
    uint16_t _port;
    int _listensock;
    std::unique_ptr<ThreadPool<Task>> _threadpool_ptr;
};

service() 通信服务

对于通信服务部分,总体和多进程版本无异,区别在于参数上 传入了线程名 ,可以在输出消息时加上线程名。

static void service(int sock, const std::string& clientip, const uint16_t& clientport, const std::string& thread_name)
{
    char buffer[1024];
    while(true)
    {
        ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
        if(s > 0)
        {
            buffer[s] = 0;
            std::cout << thread_name << " | " << clientip << ":" << clientport << " | " << buffer << std::endl; 
        }
        else if (s == 0)
        {
            logMessage(DEBUG, "client quit, me too.");
            break;
        }
        else
        {
            logMessage(ERROR, "read error, me too.");
            break;
        }
        write(sock, buffer, strlen(buffer)); // 回写内容
    }
    close(sock);
}

dictOnline()(英汉互译/网络词典)

对于该部分,主要包含以下步骤:

  • 创建一个哈希类,作为词典用于对应中英文
  • 随后循环读取客户端的内容:
    • 如果可以在词典中找到,就记录该对应的词汇
    • 如果找不到,就打印错误信息
  • 最后将结果写回客户端
static void dictOnline(int sock, const std::string& clientip, const uint16_t& clientport, const std::string& thread_name)
{
    char buffer[1024];
    static std::unordered_map<std::string, std::string> dict = {
        {"hello", "你好"},
        {"world", "世界"},
        {"tcp", "传输控制协议"},
        {"udp", "用户数据报协议"}
    };
    // 添加4组数据
    dict["rpc"] = "远程过程调用";
    dict["ssh"] = "安全外壳";
    dict["http"] = "超文本传输协议";
    dict["https"] = "超文本传输协议安全";

    while(true)
    {
        ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
        if(s > 0)
        {
            buffer[s] = 0;
            std::string message;
            auto iter = dict.find(buffer);
            if(iter == dict.end()) message = "not found:(";
            else message = iter->second;
            std::cout << thread_name << " | " << clientip << ":" << clientport << " | " << buffer << " | " << message << std::endl;
            write(sock, message.c_str(), message.size()); // 将结果写回
        }
        else if (s == 0)
        {
            logMessage(DEBUG, "client quit, me too.");
            break;
        }
        else
        {
            logMessage(ERROR, "read error | %d : %s", errno, strerror(errno));
            break;
        } 
    }
    close(sock);
}

initServer()(初始化服务器)

初始化服务器的代码与多进程版本一致:

void initServer()
    {
        // 创建listensock
        _listensock = socket(AF_INET, SOCK_STREAM, 0);
        if(_listensock < 0) {
            logMessage(ERROR, "socket error, %d : %s", errno, strerror(errno));
            exit(1);
        }

        // 绑定
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = inet_addr(_ip.c_str());
        if(bind(_listensock, (struct sockaddr*)&local, sizeof(local)) < 0)
        {
            logMessage(ERROR, "bind error, %d : %s", errno, strerror(errno));
            exit(2);
        }


        // 监听
        if(listen(_listensock, gbacklog) < 0)
        {
            logMessage(ERROR, "listen error, %d : %s", errno, strerror(errno));
            exit(3);
        }

        logMessage(NORMAL, "init server success.");
    }

startServer()(启动服务器)

  • 对于启动服务器,首先需要 运行线程池 ,在正确建立连接后,将客户端的ip端口号与英汉互译的功能一同 传入Task对象
  • 最后将任务类添加到线程池中
void startServer()
{
    // 使用线程池
    _threadpool_ptr->run();
    while(true)
    {
        // 等待客户端连接
        struct sockaddr_in peer;
        socklen_t len = sizeof(peer);
        int sock = accept(_listensock, (struct sockaddr*)&peer, &len);
        if(sock < 0)
        {
            logMessage(ERROR, "accept error, %d : %s", errno, strerror(errno));
            continue;
        }
        // 成功获取连接
        uint16_t client_port = ntohs(peer.sin_port);
        std::string client_ip = inet_ntoa(peer.sin_addr);
        logMessage(NORMAL, "get a new client, ip: %s, port: %d", client_ip.c_str(), client_port);
        // 创建线程
        Task t(sock, client_ip, client_port, dictOnline);
        _threadpool_ptr->pushTask(t); // 添加 任务
    }
}

结果演示

通过下图,可以看出,启动服务器后,服务器可以接收多个客户端的信息,并正确发出反馈。

在这里插入图片描述


5. 完整代码

上述关于Tcp套接字通信的完整代码在👇:

Tcp套接字编程 - 实例代码

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

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

相关文章

Linux安装刻录软件

在工作场景经常使用光盘和刻录机&#xff0c;在windows系统下有nero软件&#xff0c;在linux下有k3b,但是原始的k3b只能一次刻录&#xff0c;十分浪费光盘&#xff0c;这里我们使用经优麒麟优化过的刻录软件&#xff0c;实现多次追加刻录。 进入优麒麟软件仓库&#xff0c;需要…

CentOS7.9安装mysql-8.0.36踩坑小记

前言&#xff1a; 最近想在一台测试服务器上&#xff0c;安装下最新的MySQL 8.0 版本数据库&#xff0c;想着挺简单的一件事&#xff0c;之前也手搓过 8.0 安装&#xff0c;这不手到擒来&#xff0c;没想到马失前蹄&#xff0c;遇到了一个小坑&#xff0c;耗费了不少时间&…

docker 安装RabbitMQ-web版本

直接拉去web版本 docker pull rabbitmq:management启动命令 设置用户名 admin 密码123456 docker run -dit --name rabbitmq -p 5672:5672 -p 15672:15672 -e RABBITMQ_DEFAULT_USERadmin -e RABBITMQ_DEFAULT_PASS123456 rabbitmq:management访问地址 http://127.0.0.1:…

AIGC实践|探索用AI实现小游戏开发全流程

前言&#xff1a; 在上一篇中&#xff0c;我们已经深入探讨了AI工具在动态绘本创作中的应用&#xff0c;体验了AI在创意内容生成上的魅力。本篇文章将带领大家进入一个全新的探索领域——游戏开发。 本文将详细介绍如何利用AI工具探索实现游戏开发的全过程&#xff0c;从概念…

第三期【数据库主题文档上传激励活动】已开启!快来上传文档赢奖励

2023年9月、11月&#xff0c;墨天轮社区相继举办了第一期与第二期【数据库主题文档上传激励活动】&#xff0c;众多用户积极参与、上传了大量优质的数据库主题干货文档&#xff0c;在记录经验的同时也为其他从业者带来了参考帮助&#xff0c;这正实现了“乐知乐享、共同成长”的…

存储+调优:存储-Cloud

存储调优&#xff1a;存储-Cloud Master Server 配置&#xff1a; IP192.168.1.254 useradd mfs tar zxf mfs-1.6.11.tar.gz.gz cd mfs-1.6.11 ./configure --prefix/usr --sysconfdir/etc --localstatedir/var/lib --with-default-usermfs --with-default-groupmfs --disabl…

CDN管理平台安装说明

CDN管理平台安装说明 系统需求 操作系统&#xff1a;Linux CPU不少于1核心 可用内存不少于1G 可用硬盘空间不小于10G 对于每日千万访问以上的CDN系统推荐配置如下&#xff1a; CPU不少于8核心 可用内存不少于8G 可用硬盘空间不小于200G 准备工作 在安装GoEdge之前&#xff0…

ClickHouse 几年内数据查询及细节

在 ClickHouse 中&#xff0c;查询三年内的时间数据可以使用以下方法&#xff1a; 1. 使用日期函数 可以使用 ClickHouse 支持的日期函数来筛选出三年内的数据。例如&#xff0c;使用 today() 函数获取当天日期&#xff0c;使用 toDate() 函数将日期转换为指定格式&#xff0…

前端更改线上请求地址

由于后台接口更改 , 线上请求地址需从 /api/api/ 改成 /api/ , 需实现的效果如下图 1 在原本的vite.config.js中将前端做的端口转发内容更改 , 更改一行即可 import { defineConfig } from vite import react from vitejs/plugin-react import path from path import * as fs …

520主题趣味小游戏玩法线上互动的作用是什么

行业商家借势520气氛&#xff0c;往往能低成本达到预期效果&#xff0c;包括但不限于品牌传播、渠道引流涨粉、用户促活引导等&#xff0c;除了前面推荐的互动玩法外&#xff0c;在【雨科】平台的这几款520趣味小游戏同样值得关注。 1、爱你不止520 这是一款九宫格抽奖活动&am…

浅谈后端整合Springboot框架后操作基础配置

boot基础配置 现在不访问端口8080 可以吗 我们在默认启动的时候访问的是端口号8080 基于属性配置的 现在boot整合导致Tomcat服务器的配置文件没了 我们怎么去修改Tomcat服务器的配置信息呢 配置文件中的配置信息是很多很多的... 复制工程 保留工程的基础结构 抹掉原始…

文件预览的实现

1.pdf预览 使用iframe 如果是预览本地文件&#xff0c;且是vue项目&#xff0c;pdf文件需要放在public文件夹下。 调试环境&#xff1a;vue、vant、js <template><div style"height: 100%;width: 100%"><iframe :src"pageUrl" style&quo…

vue3 路由跳转 携带参数

实现功能&#xff1a;页面A 跳转到 页面B&#xff0c;携带参数 路由router.ts import { createRouter, createWebHistory } from "vue-router";const routes: RouteRecordRaw[] [{path: "/demo/a",name: "aa",component: () > import(&quo…

CTF网络安全大赛简单web题目:eval

题目来源于&#xff1a;bugku 题目难度&#xff1a;简单 一道简单web的题目 题目源代码&#xff1a; <?phpinclude "flag.php";$a $_REQUEST[hello];eval( "var_dump($a);");show_source(__FILE__); ?> 这个PHP脚本有几个关键部分&#xff0c;但…

rust的版本问题,安装问题,下载问题

rust的版本、安装、下载问题 rust版本问题&#xff0c; 在使用rust的时候&#xff0c;应用rust的包&#xff0c;有时候包的使用和rust版本有关系。 error: failed to run custom build command for pear_codegen v0.1.2 Caused by: process didnt exit successfully: D:\rus…

Spring RequestMappingHandlerMapping详解

文章目录 前言一、AbstractHandlerMethodMapping1.1 mappingRegistry1.2 MappingRegistry 注册表1.3 getHandlerInternal1.4 lookupHandlerMethod 二、RequestMappingInfoHandlerMapping三、RequestMappingHandlerMapping总结 前言 RequestMappingHandlerMapping是Spring MVC中…

OpenAI、微软、智谱AI 等全球 16 家公司共同签署前沿人工智能安全承诺

人工智能&#xff08;AI&#xff09;的安全问题&#xff0c;正以前所未有的关注度在全球范围内被讨论。 日前&#xff0c;OpenAI 联合创始人、首席科学家 Ilya Sutskever 与 OpenAI 超级对齐团队共同领导人 Jan Leike 相继离开 OpenAI&#xff0c;Leike 甚至在 X 发布了一系列…

socket地址理解

socket介绍 套接字的基本概念 1. 套接字的定义&#xff1a; 套接字&#xff08;socket&#xff09;是计算机网络中用于通信的端点&#xff0c;它抽象了不同主机上应用进程之间双向通信的机制。 2. 套接字的作用&#xff1a; 套接字连接应用进程与网络协议栈&#xff0c;使…

JS对象超细

目录 一、对象是什么 1.对象声明语法 2.对象有属性和方法组成 二、对象的使用 1.对象的使用 &#xff08;1&#xff09;查 &#xff08;2&#xff09;改 &#xff08;3&#xff09;增 &#xff08;4&#xff09;删&#xff08;了解&#xff09; &#xff08;5&#xf…

Lazarus - 从 Hello 开始

我们在《Lazarus - 隐秘的神器》一文中了解到了 Lazarus 的历史和特点&#xff0c;此后将正式开始学习Lazarus 开发。 如果你也对 Windows、Pascal、Delphi 开发感兴趣&#xff0c;请关注 Lazarus专栏 &#x1f4f0; 安装开发环境 官网&#xff1a;Lazarus Homepage (lazarus-i…