Linux--Socket 编程 TCP(Echo Server)

news2024/11/27 18:41:51

目录

1.认识TCP接口

  2.Echo Server

2.1添加的日志系统(代码)

 2.2解析网络地址

 2.3 服务端逻辑 (代码)

2.4客户端逻辑(代码) 

2.5代码测试 


1.认识TCP接口

下面介绍程序中用到的 socket API,这些函数都在 sys/socket.h 中。
socket():

• socket()打开一个网络通讯端口,如果成功的话,就像 open()一样返回一个文件描
述符;
• 应用程序可以像读写文件一样用 read/write 在网络上收发数据;
• 如果 socket()调用出错则返回-1;
• 对于 IPv4, family 参数指定为 AF_INET;
• 对于 TCP 协议,type 参数指定为 SOCK_STREAM, 表示面向流的传输协议
• protocol 参数的介绍从略,指定为 0 即可。

bind():

• 服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服
务器程序的地址和端口号后就可以向服务器发起连接; 服务器需要调用 bind 绑定一
个固定的网络地址和端口号;
• bind()成功返回 0,失败返回-1。
• bind()的作用是将参数 sockfd 和 myaddr 绑定在一起, 使 sockfd 这个用于网络
通讯的文件描述符监听 myaddr 所描述的地址和端口号;
• 前面讲过,struct sockaddr *是一个通用指针类型,myaddr 参数实际上可以接受
多种协议的 sockaddr 结构体,而它们的长度各不相同,所以需要第三个参数 addrlen
指定结构体的长度;
 

我们的程序中对 myaddr 参数是这样初始化的:

1. 将整个结构体清零;
2. 设置地址类型为 AF_INET;
3. 网络地址为 INADDR_ANY, 这个宏表示本地的任意 IP 地址,因为服务器可能有
多个网卡,每个网卡也可能绑定多个 IP 地址, 这样设置可以在所有的 IP 地址上监听,
直到与某个客户端建立了连接时才确定下来到底用哪个 IP 地址;
4. 端口号为 SERV_PORT, 我们定义为 9999;

listen():

• listen()声明 sockfd 处于监听状态, 并且最多允许有 backlog 个客户端处于连接
等待状态, 如果接收到更多的连接请求就忽略, 这里设置不会太大(一般是 5)。
• listen()成功返回 0,失败返回-1;
 

accept():

• 三次握手完成后, 服务器调用 accept()接受连接;
• 如果服务器调用 accept()时还没有客户端的连接请求,就阻塞等待直到有客户端
连接上来;
• addr 是一个传出参数,accept()返回时传出客户端的地址和端口号;
• 如果给 addr 参数传 NULL,表示不关心客户端的地址;
• addrlen 参数是一个传入传出参数(value-result argument), 传入的是调用者提
供的, 缓冲区 addr 的长度以避免缓冲区溢出问题, 传出的是客户端地址结构体的实
际长度(有可能没有占满调用者提供的缓冲区);

• 返回值:

  • 如果accept()成功,它将返回一个新的套接字描述符,这个描述符用于与客户端的通信。原始的sockfd套接字描述符则继续留在监听状态,准备接受新的连接请求。

  • 如果accept()失败,它将返回-1,并设置全局变量errno以指示错误的类型。

我们的服务器程序结构是这样的:

connect

• 客户端需要调用 connect()连接服务器;
• connect 和 bind 的参数形式一致, 区别在于 bind 的参数是自己的地址, 而
connect 的参数是对方的地址;
• connect()成功返回 0,出错返回-1;
 


  2.Echo Server


2.1添加的日志系统(代码)

 LockGuard.hpp

#pragma once

#include <pthread.h>

class LockGuard
{
public:
    LockGuard(pthread_mutex_t *mutex):_mutex(mutex)
    {
        pthread_mutex_lock(_mutex);
    }
    ~LockGuard()
    {
        pthread_mutex_unlock(_mutex);
    }
private:
    pthread_mutex_t *_mutex;
};

Log.hpp

#pragma once
#include <iostream>
#include <sys/types.h>
#include <unistd.h>
#include <ctime>
#include <cstdarg>
#include <fstream>
#include <cstring>
#include <pthread.h>
#include "LockGuard.hpp"

namespace log_ns
{

    enum
    {
        DEBUG = 1,
        INFO,
        WARNING,
        ERROR,
        FATAL
    };

    std::string LevelToString(int level)
    {
        switch (level)
        {
        case DEBUG:
            return "DEBUG";
        case INFO:
            return "INFO";
        case WARNING:
            return "WARNING";
        case ERROR:
            return "ERROR";
        case FATAL:
            return "FATAL";
        default:
            return "UNKNOWN";
        }
    }

    std::string GetCurrTime()
    {
        time_t now = time(nullptr);
        struct tm *curr_time = localtime(&now);
        char buffer[128];
        snprintf(buffer, sizeof(buffer), "%d-%02d-%02d %02d:%02d:%02d",
                 curr_time->tm_year + 1900,
                 curr_time->tm_mon + 1,
                 curr_time->tm_mday,
                 curr_time->tm_hour,
                 curr_time->tm_min,
                 curr_time->tm_sec);
        return buffer;
    }

    class logmessage
    {
    public:
        std::string _level;
        pid_t _id;
        std::string _filename;
        int _filenumber;
        std::string _curr_time;
        std::string _message_info;
    };

#define SCREEN_TYPE 1
#define FILE_TYPE 2

    const std::string glogfile = "./log.txt";
    pthread_mutex_t glock = PTHREAD_MUTEX_INITIALIZER;

    // log.logMessage("", 12, INFO, "this is a %d message ,%f, %s hellwrodl", x, , , );
    class Log
    {
    public:
        Log(const std::string &logfile = glogfile) : _logfile(logfile), _type(SCREEN_TYPE)
        {
        }
        void Enable(int type)
        {
            _type = type;
        }
        void FlushLogToScreen(const logmessage &lg)
        {
            printf("[%s][%d][%s][%d][%s] %s",
                   lg._level.c_str(),
                   lg._id,
                   lg._filename.c_str(),
                   lg._filenumber,
                   lg._curr_time.c_str(),
                   lg._message_info.c_str());
        }
        void FlushLogToFile(const logmessage &lg)
        {
            std::ofstream out(_logfile, std::ios::app);
            if (!out.is_open())
                return;
            char logtxt[2048];
            snprintf(logtxt, sizeof(logtxt), "[%s][%d][%s][%d][%s] %s",
                     lg._level.c_str(),
                     lg._id,
                     lg._filename.c_str(),
                     lg._filenumber,
                     lg._curr_time.c_str(),
                     lg._message_info.c_str());
            out.write(logtxt, strlen(logtxt));
            out.close();
        }
        void FlushLog(const logmessage &lg)
        {
            // 加过滤逻辑 --- TODO

            LockGuard lockguard(&glock);
            switch (_type)
            {
            case SCREEN_TYPE:
                FlushLogToScreen(lg);
                break;
            case FILE_TYPE:
                FlushLogToFile(lg);
                break;
            }
        }
        void logMessage(std::string filename, int filenumber, int level, const char *format, ...)
        {
            logmessage lg;

            lg._level = LevelToString(level);
            lg._id = getpid();
            lg._filename = filename;
            lg._filenumber = filenumber;
            lg._curr_time = GetCurrTime();

            va_list ap;
            va_start(ap, format);
            char log_info[1024];
            vsnprintf(log_info, sizeof(log_info), format, ap);
            va_end(ap);
            lg._message_info = log_info;

            // 打印出来日志
            FlushLog(lg);
        }
        ~Log()
        {
        }

    private:
        int _type;
        std::string _logfile;
    };

    Log lg;

#define LOG(Level, Format, ...)                                        \
    do                                                                 \
    {                                                                  \
        lg.logMessage(__FILE__, __LINE__, Level, Format, ##__VA_ARGS__); \
    } while (0)
#define EnableScreen()          \
    do                          \
    {                           \
        lg.Enable(SCREEN_TYPE); \
    } while (0)
#define EnableFILE()          \
    do                        \
    {                         \
        lg.Enable(FILE_TYPE); \
    } while (0)
};


 2.2解析网络地址

 此功能实现较为简单,请看注释:

#pragma once

#include <iostream>
#include <string>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
//网络地址
class InetAddr
{
private:
    void ToHost(const struct sockaddr_in &addr)//网络序列转主机序列
    {
        _port = ntohs(addr.sin_port);//这个端口号是随机bind的
        _ip = inet_ntoa(addr.sin_addr);//四字节地址转字符串
    }

public:
    InetAddr(const struct sockaddr_in &addr):_addr(addr)
    {
        ToHost(addr);
    }
    std::string Ip()
    {
        return _ip;
    }
    uint16_t Port()
    {
        return _port;
    }
    ~InetAddr()
    {
    }

private:
    std::string _ip;
    uint16_t _port;
    struct sockaddr_in _addr;//保存一下网络序列
};


 2.3 服务端逻辑 (代码)

TcpServer.hpp代码主体逻辑:

  1. 构造函数 (TcpServer(uint16_t port = gport)): 初始化服务器对象时,可以指定一个端口号(默认为8888)。它设置了服务器的监听端口、监听套接字(初始化为-1,实际值在InitServer中设置)和服务器是否正在运行的标志。

  2. 初始化服务器 (InitServer()): 这个方法负责设置服务器的网络环境。它首先创建一个socket,然后将其绑定到服务器的地址和端口上,并监听连接请求。如果在这个过程中发生任何错误(如socket创建失败、绑定失败或监听失败),则会记录错误日志并退出程序。

  3. 启动服务器循环 (Loop()): 这个方法是服务器的主循环,它首先设置_isrunningtrue,然后进入一个无限循环,不断接受客户端的连接请求。对于每个新的连接请求,它都会调用accept函数来获取一个新的socket(sockfd),该socket用于与客户端进行通信。然后,它调用Service函数来处理这个连接。如果_isrunning被设置为false(尽管在这个示例中没有显示直接设置它的代码,但在实际应用中可能需要某种方式来优雅地关闭服务器),则循环会终止。

  4. 处理连接 (Service(int sockfd, InetAddr addr)): 这个方法负责处理与客户端的通信。它进入一个无限循环,不断从客户端socket读取数据,并将读取到的数据(前面添加了"[server echo] #"前缀)发送回客户端。如果读取到0字节(表示客户端关闭了连接),或者发生读取错误,循环会终止,并且会关闭与客户端的socket连接。

#pragma once
#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <pthread.h>
#include "Log.hpp"
#include "InetAddr.hpp"

using namespace log_ns;

enum
{
    SOCKET_ERROR = 1,
    BIND_ERROR,
    LISTEN_ERR
};

const static int gport = 8888;
const static int gsock = -1;
const static int gblcklog = 8;//套接字监听队列的最大长度


class TcpServer
{
public:
    TcpServer(uint16_t port = gport)
        : _port(port),
          _listensockfd(gsock),
          _isrunning(false)
    {
    } 
    void InitServer()//初始化
    {
        // 1. 创建socket
        //SOCK_STREAM表示流式套接字(TCP典型),AF_INET代表IPv4地址族
        _listensockfd = ::socket(AF_INET, SOCK_STREAM, 0);
        if (_listensockfd < 0)
        {
            LOG(FATAL, "socket create error\n");
            exit(SOCKET_ERROR);
        }
        //创建socket成功
        LOG(INFO, "socket create success, sockfd: %d\n", _listensockfd); // 3

  // 2. bind
        struct sockaddr_in local;//用于表示Internet地址,特别是IPv4地址和端口号。
        memset(&local, 0, sizeof(local));//先清空再使用
        local.sin_family = AF_INET;//表示你的套接字将使用IPv4协议。
        local.sin_port = htons(_port);//端口号,htons主机序列转为网络序列
        // local.sin_addr.s_addr = inet_addr(_localip.c_str()); // 1. 需要4字节IP 2. 需要网络序列的IP -- 暂时
        local.sin_addr.s_addr = INADDR_ANY; // 服务器端,进行任意IP地址绑定

        // 2. bind sockfd 和 Socket addr
        if (::bind(_listensockfd, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            LOG(FATAL, "bind error\n");
            exit(BIND_ERROR);
        }
        LOG(INFO, "bind success\n");

        // 3. 因为tcp是面向连接的,tcp需要未来不断地能够做到获取连接
        //将套接字设置为监听进入连接的状态,准备接受客户端的连接请求
        if (::listen(_listensockfd, gblcklog) < 0)
        {
            LOG(FATAL, "listen error\n");
            exit(LISTEN_ERR);
        }
        LOG(INFO, "listen success\n");
    }

    void Loop()//启动
    {
        _isrunning = true;
        while (_isrunning)
        {
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            // 4. 获取新连接
            //返回的为IO套接字,该过程有用户连接才通过_listensockfd获取新链接,否则阻塞等待
            int sockfd = ::accept(_listensockfd, (struct sockaddr *)&client, &len);
            if (sockfd < 0)
            {
                LOG(WARNING, "accept error\n");
                continue;//再继续accept
            }
            InetAddr addr(client);
            //来了一个新链接
            LOG(INFO, "get a new link, client info : %s, sockfd is : %d\n", addr.AddrStr().c_str(), sockfd);
            Service(sockfd, addr);//提供服务
        }
        _isrunning = false;
    }
    void Service(int sockfd, InetAddr addr)
    {
        // 长服务(客户不停止,服务器也不停止)
        while (true)
        {
            char inbuffer[1024]; // 当做字符串
            //像读文件一样从sockfd中读(接收消息)
            ssize_t n = ::read(sockfd, inbuffer, sizeof(inbuffer) - 1);
            if (n > 0)
            {
                inbuffer[n] = 0;
                LOG(INFO, "get message from client %s, message: %s\n", addr.AddrStr().c_str(), inbuffer);

                std::string echo_string = "[server echo] #";
                echo_string += inbuffer;
                //像写文件一样向sockfd中写(发送消息)
                write(sockfd, echo_string.c_str(), echo_string.size());
            }
            else if (n == 0)//表示读到了文件结尾,表示客户端结束
            {
                LOG(INFO, "client %s quit\n", addr.AddrStr().c_str());
                break;
            }
            else
            {//读出错了
                LOG(ERROR, "read error: %s\n", addr.AddrStr().c_str());
                break;
            }
        }
        ::close(sockfd);//关文件描述符
    }

    ~TcpServer() {}

private:
    uint16_t _port;
    int _listensockfd;//监听套接字
    bool _isrunning;
};

TcpServerMain.cc

#include "TcpServer.hpp"

#include <memory>


// ./tcpserver 8888
int main(int argc, char *argv[])
{
    if(argc != 2)
    {
        std::cerr << "Usage: " << argv[0] << " local-port" << std::endl;
        exit(0);
    }
    uint16_t port = std::stoi(argv[1]);
    //port参数被传递给TcpServer的构造函数,
    //以便服务器知道在哪个端口上监听连接请求。
    std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(port);
    tsvr->InitServer();
    tsvr->Loop();

    return 0;
}


2.4客户端逻辑(代码) 

TcpClientMain.cc代码主体逻辑:

  1. 参数检查:首先,程序检查命令行参数的数量是否正确。它需要两个额外的参数:服务器的IP地址和端口号。如果参数数量不正确,程序会打印使用说明并退出。

  2. 创建Socket:通过调用socket()函数创建一个TCP socket(即使用SOCK_STREAM作为socket类型)。如果socket创建失败,程序会打印错误信息并退出。

  3. 设置服务器地址信息:程序接着使用sockaddr_in结构体来存储服务器的地址信息。它首先清零整个结构体,然后设置地址族为AF_INET(IPv4),使用htons()函数将端口号从主机字节序转换为网络字节序,并使用inet_pton()函数将服务器IP地址的字符串形式转换为网络字节序的二进制形式。

  4. 连接到服务器:通过调用connect()函数尝试连接到服务器。这个函数需要socket文件描述符、指向服务器地址的指针以及地址的长度作为参数。如果连接失败,程序会打印错误信息并退出。

  5. 与服务器通信:一旦连接成功,程序进入一个无限循环,等待用户从标准输入(stdin)输入消息。对于每次输入的消息,程序使用write()函数将其发送到服务器。然后,程序使用read()函数从socket读取服务器发送回来的响应。如果读取到的字节数大于0,程序会将接收到的数据作为字符串打印到标准输出(stdout)。如果读取到的字节数为0或发生错误(例如连接被关闭),则跳出循环。

  6. 关闭Socket:在退出循环后,程序使用close()函数关闭socket文件描述符,以释放与socket相关的资源。

注意,read()函数没有处理EAGAINEWOULDBLOCK错误,这些错误可能发生在非阻塞socket上。然而,在这个例子中,socket是默认创建的,因此它是阻塞的,所以这些错误不太可能发生。如果希望处理非阻塞socket或超时,则需要在socket()调用后设置socket为非阻塞模式,并在read()调用时处理可能的错误情况。

#include <iostream>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

// ./tcpclient server-ip server-port
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        std::cerr << "Usage: " << argv[0] << " server-ip server-port" << std::endl;
        exit(0);
    }
    std::string serverip = argv[1];
    uint16_t serverport = std::stoi(argv[2]);

    // 1. 创建socket
    int sockfd = ::socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        std::cerr << "create socket error" << std::endl;
        exit(1);
    }

    //服务器端口号是固定的,但是客户端不是,因为客服是随机的
    // client的端口号,一般不让用户自己设定,而是让client OS随机选择?怎么选择,什么时候选择呢?
    // client 需要 bind它自己的IP和端口, 但是client 不需要 “显示指明” bind它自己的IP和端口, 
    // client 在首次向服务器发送数据的时候,OS会自动给client bind它自己的IP和端口,
    //避免端口冲突

    //填充服务器的相关信息
    struct sockaddr_in server;//服务器的套接字信息
    memset(&server, 0, sizeof(server));
    server.sin_family = AF_INET;
    server.sin_port = htons(serverport);//要把主机序列转为网络序列
    // inet_addr把字符串形式的ip转为4字节(进程序列转网络序列)
   ::inet_pton(AF_INET, serverip.c_str(), &server.sin_addr);
   //客户端需要调用 connect()连接服务器;
    int n = ::connect(sockfd, (struct sockaddr *)&server, sizeof(server));
    if (n < 0)
    {//连接失败
        std::cerr << "connect socket error" << std::endl;
        exit(2);
    }

    while(true)//连接成功后,客户端与服务端进行通讯
    {
        std::string message;
        std::cout << "Enter #";
        std::getline(std::cin, message);
        //发消息
        write(sockfd, message.c_str(), message.size());

        char echo_buffer[1024];

        //读消息
        n = read(sockfd, echo_buffer, sizeof(echo_buffer));
        if(n > 0)
        {
            echo_buffer[n] = 0;
            std::cout << echo_buffer << std::endl;
        }
        else
        {
            break;
        }
    }
    ::close(sockfd);
    return 0;
}


2.5代码测试 

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

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

相关文章

“简源共生“:融合乔布斯与埃隆·马斯克智慧之光的设计思维在产品开发中的应用

在科技创新的浩瀚星空中&#xff0c;史蒂夫乔布斯&#xff08;Steve Jobs&#xff09;与埃隆马斯克&#xff08;Elon Musk&#xff09;无疑是两颗璀璨的明星&#xff0c;他们以独特的设计思维引领了时代的潮流&#xff0c;塑造了无数颠覆性产品。本文旨在深入剖析这两位巨匠的设…

文本编辑三剑客(grep)

目录 正则表达式 元字符 grep 案例 我在编写脚本的时候发现&#xff0c;三个文本编辑的命令&#xff08;grep、sed、awk&#xff0c;被称为文本编辑三剑客&#xff0c;我习惯叫它三巨头&#xff09;用的还挺多的&#xff0c;说实话我一开始学的时候也有些懵&#xff0c;主要…

深入分析 Android ContentProvider (八)

文章目录 深入分析 Android ContentProvider (八)ContentProvider 高级使用及最佳实践案例分析&#xff08;续&#xff09;1. 深入了解跨应用数据共享示例&#xff1a;跨应用数据共享的完整实现1. 定义权限2. 定义 ContentProvider3. ContentProvider 实现 2. 实践案例&#xf…

UG NX2406 安装教程

软件介绍 UG是一个交互式CAD/CAM(计算机辅助设计与计算机辅助制造)系统&#xff0c;它功能强大&#xff0c;可以轻松实现各种复杂实体及造型的建构。 它在诞生之初主要基于工作站&#xff0c;但随着PC硬件的发展和个人用户的迅速增长&#xff0c;在PC上的应用取得了迅猛的增长…

用TypeScript完成的贪吃蛇小游戏

食物类Fod // 定义 class Food {// 定义一个属性表示食物所对应的元素element:HTMLElement;constructor(){//加个&#xff01;表示不能为空,非空断言操作符 //获取页面中的food元素并将其赋值给element this.elementdocument.getElementById(food)!;}// 定义一个获取食物x轴坐…

【C++】c++语法基础

引入&#xff0c;第一个c程序 这是用c写的helloworld程序 #include<iostream> using namespace std; int main() {cout << "hello,world\n" << endl;return 0;} 接下来我们将根据上述的代码来学习c的基本语法。 命名空间&#xff08;namespace…

PHP:连接钉钉接口-钉钉回调事件,本地测试数据

前置数据参考 数据说明:参见官方文档回调事件消息体加解密 - 钉钉开放平台 (dingtalk.com) URL后面带的参数: signature=5a65ceeef9aab2d149439f82dc191dd6c5cbe2c0&timestamp=1445827045067&nonce=nEXhMP4r Post参数: { "encrypt":"1a3NB…

日常开发记录分享——C#控件ToolTip实现分栏显示内容

文章目录 需求来源实现思路实施请看VCR等等别走&#xff0c;有优化 需求来源 需要在鼠标浮动到指定位置后提示出详细的信息&#xff0c;一开始使用的tooltip实现&#xff0c;但是里面的内容效果并不理想&#xff0c;需要有条理性&#xff0c;于是就想到能不能将展示的东西分列…

邮件推送API如何集成到现有系统发送邮件?

邮件推送API安全性策略&#xff1f;如何选择邮件推送API服务商&#xff1f; 在当今数字化时代&#xff0c;邮件通信是企业和个人交流的重要方式之一。集成邮件推送API到现有系统可以大大提升通信效率和自动化程度。AokSend将介绍如何将邮件推送API集成到现有系统中&#xff0c…

关于P2P(点对点)

P2P 是一种客户端与客户端之间&#xff0c;点对点连接的技术&#xff0c;在早前的客户端都是公网IP&#xff0c;没有NAT的情况下&#xff0c;P2P是较为容易实现的。 但现在的P2P&#xff0c;实现上面会略微有一些复杂&#xff1a;需要采取UDP打洞的技术&#xff0c;但UDP打出来…

自动控制: 时间最优的PID控制算法

自动控制&#xff1a; 时间最优的PID控制算法 在计算机控制系统中&#xff0c;时间最优控制旨在使系统从一个初始状态转到另一个目标状态所经历的过渡时间最短。利用最大值原理&#xff0c;可以设计出控制量只在 u ( t ) ≤ 1 u(t) \leq 1 u(t)≤1范围内取值的时间最优控制系…

(39)智能电池

文章目录 前言 1 通过任务规划器进行设置 2 补充信息 3 限制条件 4 参数说明 前言 虽然还不是很普遍&#xff0c;但智能电池更容易从飞行器上安装和拆卸&#xff0c;并且能够提供更多关于电池状态的信息&#xff0c;包括容量、单个电池电压、温度等。 ArduPilot 支持几种…

【分布式系统】 单机架构 | 分布式架构 | 集群 | 主从架构 | 分库分表 | 冷热分离 | 微服务

文章目录 [toc] 分布式系统一、单机架构二、分布式系统三、应用服务器集群四、读写分离 / 主从分离架构五、引入缓存/冷热分离架构六、垂直分库七、微服务架构——业务拆分代价优势 八、名词解释1.应用&#xff08;Application&#xff09;/系统(System)2.模块&#xff08;Mode…

解决“QtCreator无法呼出搜狗输入法“问题

由于在Ubuntu系统上&#xff0c;QtCreator软件默认使用IBus类型的输入法&#xff0c;而搜狗输入法是fcitx类型的&#xff0c;所以需要在Linux的系统设置 -->区域与语言 里 -->勾选 fcitx类型&#xff0c;如图(1)所示。     这里以QtCreator 4.5.2Ubuntu 18为例&#xf…

学习测试14-实战3-复习-使用CANoe打开半成品

数据 链接: https://pan.baidu.com/s/1k0SFq0luDvEbqimFgtfyKg?pwd9a5t 提取码: 9a5t 复制这段内容后打开百度网盘手机App&#xff0c;操作更方便哦 1&#xff0c;导入信号、报文、节点 2&#xff0c;导入数据库 3&#xff0c;导入can代码 4&#xff0c;导入环境变量 5&#x…

RTP协议基础

概述 1. 基本概念 RTP协议&#xff0c;全称为Real-time Transport Protocol&#xff08;实时传输协议&#xff09;是一种用于在IP网络上传输音频、视频等实时数据的网络协议。 在流媒体&#xff08;流媒体就是指可在线/实时观看音视频的互联网产品&#xff09;数据传输过程中&…

抄作业-跟着《React通关秘籍》捣鼓React-playground-上集

文章目录 前言1. 搭建react 开发环境2、react hooks 知识3. 目标&#xff1a;跟着小册实现 react-playground3.1 整体布局初始化项目使用Alloment 来实现左右分屏的拖拉功能 3.2 代码编辑器Monaco Editor 3.3 实现了多文件的切换用 useContext 来共享数据。优化 tab的样式&…

Vue响应式的原理

一. Vue响应式原理的核心概念 1. Vue响应式原理基于以下核心概念&#xff1a; ① 响应式对象&#xff1a;Vue使用Object.defineProperty()来 reactive&#xff08;反应&#xff09;对象中的属性&#xff0c;使其变化可以被检测。 注意&#xff1a; ★ Object.definePropert…

Python字符串处理技巧:一个小技巧竟然能省下你一半时间!

获取Pyhon及副业知识&#xff0c;关注公众号【软件测试圈】 效率翻倍的秘密&#xff1a;Python字符串操作的5个惊人技巧 在Python编程中&#xff0c;字符串处理在数据分析、Web开发、自动化脚本等多个领域都有广泛应用。Python提供了一系列强大的字符串处理函数&#xff0c;能够…

蚓链数字化生态平台:构建城市智能商业,引领协同发展新潮流

​在当今数字化飞速发展的时代&#xff0c;城市商业的运行模式正在经历着数字化变革。蚓链数字化生态平台应运而生&#xff0c;以其强大的功能和创新的理念&#xff0c;成为构建城市智能商业枢纽中心的关键力量&#xff0c;推动着平台互通、业务贯通、管理协同的全新发展格局。…