【计算机网络】Linux环境中的TCP网络编程

news2024/11/18 20:21:02

文章目录

  • 前言
  • 一、TCP Socket API
    • 1. socket
    • 2. bind
    • 3. listen
    • 4. accept
    • 5. connect
  • 二、封装TCPSocket
  • 三、服务端的实现
    • 1. 封装TCP通用服务器
    • 2. 封装任务对象
    • 3. 实现转换功能的服务器
  • 四、客户端的实现
    • 1. 封装TCP通用客户端
    • 2. 实现转换功能的客户端
  • 五、结果演示
  • 六、多进程版服务器
  • 七、线程池版服务器


前言

TCP和UDP都是工作在传输层,用于程序之间传输数据。二者之间的区别是TCP是面向连接的,而UDP是面向数据报的。那就意味着,TCP能够进行可靠的数据传输,而UDP进行不可靠的数据传输。关于TCP协议和UDP协议的详细内容可见博主的后续文章,本文的主要内容是关于TCP socket的网络编程。

接下来我们将基于TCP网络编程实现一个将小写字母转换成大写字母的网络服务器。

一、TCP Socket API

以下是关于使用TCP协议用到的socket API,这些函数都包含在头文件sys/socket.h中。

1. socket

函数定义:

NAME
       //socket - create an endpoint for communication
SYNOPSIS
       #include <sys/socket.h>
       int socket(int domain, int type, int protocol);
       

功能:
socket()会打开一个网络通信端口,如果打开成功,则像open()函数一样返回一个文件描述符,如果失败则返回 -1。这样网络应用程序就可以像读写文件那样使用read/write在网络上读取和发送数据。

参数详解:

  1. 第一个参数domain用于设置网络通信的域,函数socket()根据这个参数选择通信协议的族。对于IPv4,domain参数指定为AF_INET,而IPv6则是AF_INET6。并且AF_INETPFINET的值是一致的。

  2. 第二个参数type用于设置通信协议的族,这些族也在文件sys/socket.h中定义,包含如下表所示的值。

类型说明
SOCK_STREAM用于TCP连接,提供序列化、可靠的、双向连接的字节流
SOCK_DGRAM用于UDP连接(无连接状态的消息)
SOCK_SEQPACKET序列化包,提供一个序列化的、可靠的、双向的基于连接的数据传输通道,数据长度定长。每次调用读系统调用时数据需要将全部数据读出
SOCK_RAWRAW类型,提供原始网络协议访问
SOCK_RDM提供可靠的数据报文,不过可能数据会有乱序
SOCK_PACKET这是一个专用类型,不能在通用程序中使用,用于直接从设备驱动接收数据

【补充说明】

  • 类型为SOCK_STREAM的套接字表示一个双向的字节流,与管道类似。流式的套接字在进行数据收发之前必须已经连接,连接使用connet()函数进行。一旦连接,可以使用read/write函数进行数据的传输。流式通信方式保证数据不会丢失或者重复接收,当数据在一段时间内仍然没有接收完毕,可以将这个连接认为已经断开。
  • SOCK_DGRAMSOCK_RAW这两种套接字可以使用函数sendto()来发送数据,使用recvfrom()函数接收数据,recvfrom()接收来自指定IP地址的发送方的数据。
  1. 第三个参数protocol用于指定某个协议的特定类型,即type类型中的某个类型。通常某个协议中只有一种特定类型,这样protocol参数仅能设置为0,但是有些协议有多种类型,就需要设置这个参数来选择特定的类型。

2. bind

函数定义:

NAME
       //bind - bind a name to a socket

SYNOPSIS
       #include <sys/socket.h>
       
       int bind(int socket, const struct sockaddr *address, socklen_t address_len);
       

因为服务器程序所监听的网络地址和端口号通常都是固定不变的,客户端得知了服务器程序的地址和端口号后就可以向服务器发起连接,而服务器需要绑定一个固定的网络地址和端口号。因此bind()的作用是将参数sockfdmyaddr绑定在一起,使sockfd这个用于网络通讯的文件描述符监听sockaddr所描述的地址和端口号。绑定成功返回0,失败则返回-1。

博主的上一篇文章【网络套接字编程】中提到过,struct sockaddr *是一个通用指针类型,myaddr参数实际上可以接受多种协议的sockaddr结构体,而它们的长度各不相同,所以需要第三个参数addrlen指定结构体的长度。

在程序中myaddr的定义及初始化如下:

struct sockaddr_in myaddr;
bzero(&myaddr, sizeof(myaddr));
myaddr.sin_family = AF_INET;
myaddr.sin_addr.s_addr = htonl(INADDR_ANY);
myaddr.sin_port = htons(SERV_PORT);
  1. 定义myaddr
  2. 使用bzero函数将整个结构体清零
  3. 设置网络通信的域为AF_INET
  4. 将网络地址设置为INADDR_ANY,这个宏表示本地的任意IP地址,因为服务器可能有多个网卡,每个网卡也可能绑定多个IP 地址,这样设置可以在所有的IP地址上监听,直到与某个客户端建立了连接时才确定下来到底用哪个IP 地址
  5. 最后填充端口号

虽然bind()中的第二个参数类型是sockaddr,但是我们真正填充信息使用的数据结构是sockaddr_in,这个结构里主要有三部分信息:地址类型、端口号、IP地址。最后在进行函数传参的时候只需要将sockaddr_in*强制类型转换成sockaddr即可。

3. listen

函数定义:

NAME
       //listen - listen for socket connections and limit the queue of incoming connections

SYNOPSIS
       #include <sys/socket.h>

       int listen(int socket, int backlog);

listen()函数用于声明sockfd处于监听状态,并且最多允许有backlog个客户端处于连接等待状态,如果接收到更多的连接请求就忽略。(详细内容可见博主的后续文章【TCP协议】)。listen()函数调用成功返回0,调用失败则返回 -1。

4. accept

函数定义:

NAME
       accept - accept a new connection on a socket

SYNOPSIS
       #include <sys/socket.h>

       int accept(int socket, struct sockaddr *restrict_address, socklen_t *restrict_address_len);

accept()函数的作用是,当客户端与服务端的三次握手完成后,服务器调用accept()函数接受连接。如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端请求连接。
返回值:

  • 调用成功则返回客户端socket()返回的文件描述符,调用失败则返回 -1。

参数:

  • 第一个参数socket即是调用socket()函数返回的文件描述符。
  • 第二个参数restrict_address是输出型参数,用于获取客户端的网络地址和端口号,如果该参数为空,则表示当前服务端不关心客户端的地址。
  • 第三个参数restrict_address_len也是输出型参数,它表示的是缓冲区restrict_address的长度,以避免缓冲区溢出问题,最后传出客户端地址结构体的实际长度。

accept()函数在服务器程序中的使用结构如下:

while (true)
{
	sockaddr_in peer_addr;
	socklen_t len = sizeof(peer_addr);
	int peer_sock = accept(_fd, (sockaddr *)&peer_addr, &len);
	ssize_t read_size = read(peer_sock, buf, sizeof(buf));
	. . .
	close(peer_sock);
}

5. connect

函数定义:

NAME
       //connect - connect a socket

SYNOPSIS
       #include <sys/socket.h>

       int connect(int socket, const struct sockaddr *address, socklen_t address_len);

作用与参数说明:
connect函数用于客户端连接服务器。其参数与bind()函数的参数一致,区别在于bind()函数绑定的参数是自己的地址,而connect()函数的连接是服务器的地址。
返回值:

  • 调用成功返回0,调用失败则返回 -1。

二、封装TCPSocket

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <cassert>

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

#define CHECK_RET(exp) \
    if (!(exp))        \
    {                  \
        return false;  \
    }

class TcpSocket
{
public:
    TcpSocket() : _fd(-1) {}
    ~TcpSocket() {}

public:
    bool Socket()
    {
        _fd = socket(AF_INET, SOCK_STREAM, 0); // AF_INET表示采用IPv4, SOCK_STREAM表示采用tcp协议
        if (_fd < 0)
        {
            std::cerr << "create socket error!" << std::endl;
            return false;
        }
        return true;
    }

    bool Close()
    {
        close(_fd);
        return true;
    }

    bool Bind(const std::string &ip, uint16_t port)
    {
        sockaddr_in addr;
        // 填充addr信息
        addr.sin_family = AF_INET;
        addr.sin_port = htons(port);
        // addr.sin_addr.s_addr = ip.empty() ? htonl(INADDR_ANY) : inet_addr(ip.c_str());
        ip.empty() ? (addr.sin_addr.s_addr = INADDR_ANY) : inet_aton(ip.c_str(), &addr.sin_addr);

        if (bind(_fd, (const sockaddr *)&addr, sizeof(addr)) < 0)
        {
            std::cerr << "bind error!" << std::endl;
            return false;
        }
        return true;
    }

    bool Listen(int num)
    {
        if (listen(_fd, num) < 0)
        {
            std::cerr << "listen error!" << std::endl;
            return false;
        }
        return true;
    }

    bool Accept(TcpSocket *peer, std::string *ip = nullptr, std::uint16_t *port = nullptr)
    {
        sockaddr_in peer_addr;
        socklen_t len = sizeof(peer_addr);
        int peer_sock = accept(_fd, (sockaddr *)&peer_addr, &len);
        if (peer_sock < 0)
        {
            std::cerr << "accept error!" << std::endl;
            return false;
        }

        peer->_fd = peer_sock;
        if (ip != nullptr)
        {
            *ip = inet_ntoa(peer_addr.sin_addr);
        }
        if (port != nullptr)
        {
            *port = ntohs(peer_addr.sin_port);
        }
        return true;
    }

    bool Recv(std::string *buf)
    {
        buf->clear();
        char inbuf[1024 * 10] = {0};
        ssize_t read_size = recv(_fd, inbuf, sizeof(inbuf), 0);
        if (read_size < 0)
        {
            std::cerr << "recv error!" << std::endl;
            return false;
        }

        if (read_size == 0)
        {
            return false;
        }

        buf->assign(inbuf, read_size);

        return true;
    }

    bool Send(const std::string &buf)
    {
        ssize_t write_size = send(_fd, buf.c_str(), buf.size(), 0);
        if (write_size < 0)
        {
            std::cerr << "send error!" << std::endl;
            return false;
        }
        return true;
    }

    bool Connect(const std::string &ip, uint16_t port)
    {
        sockaddr_in addr;
        // 填充addr信息
        addr.sin_family = AF_INET;
        addr.sin_port = htons(port);
        addr.sin_addr.s_addr = ip.empty() ? htonl(INADDR_ANY) : inet_addr(ip.c_str());
        if (connect(_fd, (sockaddr *)&addr, sizeof(addr)) < 0)
        {
            std::cerr << "connect error!" << std::endl;
            return false;
        }
        return true;
    }

    int GetFd()
    {
        return _fd;
    }

private:
    int _fd;
};

三、服务端的实现

1. 封装TCP通用服务器

#include "TcpSocket.hpp"
#include "Task.hpp"

typedef std::function<void(TcpSocket, const std::string &, uint16_t)> Handler;

// void transServer(TcpSocket sock, const std::string &ip, uint16_t port)
// {
//     std::string inbuf;

//     while (true)
//     {
//         if (!sock.Recv(&inbuf))
//         {
//             // 如果读取失败,结束循环
//             printf("[client %s:%d] disconnect!\n", ip.c_str(), port);
//             break;
//         }

//         if (strcasecmp(inbuf.c_str(), "quit") == 0)
//         {
//             printf("[client %s:%d] quit!\n", ip.c_str(), port);
//             break;
//         }

//         printf("transform before: %s[%d]--> %s\n", ip.c_str(), port, inbuf.c_str());
//         fflush(stdout);

//         for (int i = 0; i < inbuf.size(); ++i)
//         {
//             if (isalpha(inbuf[i]) && islower(inbuf[i]))
//             {
//                 inbuf[i] = toupper(inbuf[i]);
//             }
//         }

//         printf("transform after: %s[%d]--> %s\n", ip.c_str(), port, inbuf.c_str());
//         fflush(stdout);

//         sock.Send(inbuf);
//     }
//     sock.Close();
// }

class TcpServer
{
public:
    TcpServer(int port, const std::string &ip = "")
        : _port(port), _ip(ip)
    {
        _sock.Socket();
    }
    ~TcpServer() {}

public:
    bool Start(Handler handler)
    {
        // 绑定IP和端口号
        CHECK_RET(_sock.Bind(_ip, _port));
        // 监听
        CHECK_RET(_sock.Listen(5));

        // 进入循环
        while (true)
        {
            // 进行accept
            TcpSocket peer_sock;
            std::string ip;
            uint16_t port = 0;
            if (!_sock.Accept(&peer_sock, &ip, &port))
            {
                continue;
            }

            printf("[client %s:%d] connect!\n", ip.c_str(), port);

            // 执行任务
            // TODO
            Task task(peer_sock, ip, port, handler);
            task();
        }
    }

private:
    // tcp socket对象
    TcpSocket _sock;
    // 端口号
    uint16_t _port;
    // ip地址
    std::string _ip;
};

2. 封装任务对象

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

#include "TcpSocket.hpp"

class Task
{
    typedef std::function<void(TcpSocket, const std::string &, uint16_t)> callback_t;

public:
    Task(TcpSocket sock, const std::string &ip, uint16_t port, callback_t func)
        :_sock(sock), _ip(ip), _port(port), _func(func) {}

    ~Task() {}

    void operator()()
    {
        printf("线程ID[%p]处理client[%s:%d]的请求开始了...\n", pthread_self(), _ip.c_str(), _port);
        fflush(stdout);

        _func(_sock, _ip, _port);

        printf("线程ID[%p]处理client[%s:%d]的请求结束了...\n", pthread_self(), _ip.c_str(), _port);
        fflush(stdout);

    }
private:
    TcpSocket _sock;
    std::string _ip;
    uint16_t _port;
    callback_t _func; // 处理任务的回调函数
};

3. 实现转换功能的服务器

#include <iostream>
#include "TcpSocket.hpp"
#include "TcpServer.hpp"

void transServer(TcpSocket sock, const std::string &ip, uint16_t port)
{
    std::string inbuf;

    while (true)
    {
        if (!sock.Recv(&inbuf))
        {
            // 如果读取失败,结束循环
            printf("[client %s:%d] disconnect!\n", ip.c_str(), port);
            break;
        }

        if (strcasecmp(inbuf.c_str(), "quit") == 0)
        {
            printf("[client %s:%d] quit!\n", ip.c_str(), port);
            break;
        }

        printf("transform before: %s[%d]--> %s\n", ip.c_str(), port, inbuf.c_str());
        fflush(stdout);

        for (int i = 0; i < inbuf.size(); ++i)
        {
            if (isalpha(inbuf[i]) && islower(inbuf[i]))
            {
                inbuf[i] = toupper(inbuf[i]);
            }
        }

        printf("transform after: %s[%d]--> %s\n", ip.c_str(), port, inbuf.c_str());
        fflush(stdout);

        sock.Send(inbuf);
    }
    sock.Close();
}



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

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

    std::string ip;
    if (argc == 3)
    {
        ip = argv[1];
    }

    uint16_t port = atoi(argv[2]);

    TcpServer server(port, ip);
    server.Start(transServer);

    return 0;
}

四、客户端的实现

1. 封装TCP通用客户端

#include "TcpSocket.hpp"

class TcpClient
{
public:
    TcpClient(const std::string& ip, uint16_t port)
        :_ip(ip), _port(port) 
    {
        _sock.Socket();
    }

    ~TcpClient()
    {
        _sock.Close();
    }
public:
    bool Connect()
    {
        return _sock.Connect(_ip, _port);
    }

    bool Recv(std::string* buf)
    {
        return _sock.Recv(buf);
    }

    bool Send(const std::string& buf)
    {
        _sock.Send(buf);
    }

    int GetFd()
    {
        return _sock.GetFd();
    }
private:
    TcpSocket _sock;
    std::string _ip;
    uint16_t _port;
};

2. 实现转换功能的客户端

#include <iostream>
#include "TcpClient.hpp"

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 8080\n"
              << std::endl;
}
// ./clientTcp serverIp serverPort
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        return 1;
    }

    std::string serverIp = argv[1];
    uint16_t serverPort = atoi(argv[2]);

    TcpClient client(serverIp, serverPort);

    // 建立连接
    if (!client.Connect())
    {
        std::cout << "connecte errer!" << std::endl;
        return 2;
    }
    std::cout << "connecte success! fd: " << client.GetFd() << std::endl;

    std::string message;
    while (!quit)
    {
        message.clear();
        std::cout << "请输入您的内容# ";
        std::getline(std::cin, message);
        if (strcasecmp(message.c_str(), "quit") == 0)
        {
            quit = true;
        }

        if (client.Send(message))
        {
            message.resize(message.size());
            if (client.Recv(&message))
            {
                std::cout << "Server Echo ---> " << message << std::endl;
            }
        }
        else
        {
            break;
        }
    }

    return 0;
}

五、结果演示

启动服务端:

启动客户端:

客户端连接服务端成功,此时服务端状态:

测试样例:
客户端输入样例,发现转换成功。

存在的问题:
此时,启动又一个客户端连接服务器,在此输入时,我们会发现输入的内容会卡住显示屏上。

此时我们关闭第一个客户端后发现有得出转换后的结果。


原因在于当前的服务器是单进程版本的,只能够同时为一个客户端服务,所以再来一个客户端会阻塞等待服务器结束对上一个客户端的服务。以下的改进的多进程和多线程版本的服务器。

六、多进程版服务器

改进思想是,父进程为每个客户端的请求都创建一个子进程去处理任务,父进程不做任何工作,但要注意的是父进程中要关闭不断创建的客户端的peer_sock,避免内存泄漏。子进程执行客户端的请求,在请求结束后调用exit函数退出,但不必单独释放_sock对象,因为会自动调用其析构函数。同时设置signal函数,对SIGCHLD做忽略动作,使得父进程不必等待子进程,只处理自己的任务。

#pragma once

#include "TcpSocket.hpp"
#include "Task.hpp"
#include <cassert>
#include <signal.h>
#include <unistd.h>

typedef std::function<void(TcpSocket, const std::string &, uint16_t)> Handler;

class TcpProcessServer
{
public:
    TcpProcessServer(int port, const std::string &ip = "")
        : _port(port), _ip(ip)
    {
        _sock.Socket();
        signal(SIGCHLD, SIG_IGN);
    }
    ~TcpProcessServer() {}

public:
    bool Start(Handler handler)
    {
        // 绑定IP和端口号
        CHECK_RET(_sock.Bind(_ip, _port));
        // 监听
        CHECK_RET(_sock.Listen(5));

        // 进入循环
        while (true)
        {
            // 进行accept
            TcpSocket peer_sock;
            std::string ip;
            uint16_t port = 0;
            if (!_sock.Accept(&peer_sock, &ip, &port))
            {
                continue;
            }

            printf("[client %s:%d] connect!\n", ip.c_str(), port);

            // 执行任务
            // 多进程 v1.0

            pid_t id = fork();
            if (id > 0)
            {
                // 父进程,不需要做什么
                peer_sock.Close(); // 父进程中需要关闭
            }
            else if (id == 0)
            {
                // 子进程
                // 子进程的 socket 的关闭在析构函数中进行
                Task task(peer_sock, ip, port, handler);
                task();
                // 处理任务结束,退出子进程
                exit(0);
            }
            else
            {
                // fork失败
                std::cerr << "fork error!" << std::endl;
                return false;
            }
        }
        return true;
    }

private:
    // tcp socket对象
    TcpSocket _sock;
    // 端口号
    uint16_t _port;
    // ip地址
    std::string _ip;
};

结果:

此时服务器便可以为多个客户端同时进行服务。

七、线程池版服务器

实现线程池:

#pragma once

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

const uint32_t gDefaultThreadNum = 5; // 默认线程池中线程数量

template <class T>
class ThreadPool
{
public:
    ThreadPool(uint32_t threadNum = gDefaultThreadNum)
        : _isStart(false), _ThreadNum(threadNum)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_cond, nullptr);
    }
    ~ThreadPool()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond);
    }

public:
    // 启动线程池
    void start()
    {
        // 判断线程池是否启动
        assert(!_isStart); // 如果已经启动则失败
        for (int i = 0; i < _ThreadNum; ++i)
        {
            pthread_t tid;
            pthread_create(&tid, nullptr, threadRoutine, this);
        }
        // 线程池已经启动
        _isStart = true;
    }

    // 放入任务
    void push(const T &in)
    {
        lockQueue();
        _taskQueue.push(in);
        handleTask();
        unlockQueue();
    }

    // 消费任务
    T pop()
    {
        T task = _taskQueue.front();
        _taskQueue.pop();
        return task;
    }

private:
    static void *threadRoutine(void *args)
    {
        ThreadPool<T> *ptp = static_cast<ThreadPool<T> *>(args);
        while (true)
        {
            ptp->lockQueue();
            // 判断当前任务队列中有没有任务
            while (!ptp->hasTask())
            {
                // 没有任务,进行循环等待
                ptp->waitTask();
            }

            // 当前线程获取任务
            T t = ptp->pop();

            ptp->unlockQueue();

            // 当前线程处理任务
            t();
        }
    }

    void lockQueue()
    {
        pthread_mutex_lock(&_mutex);
    }
    void unlockQueue()
    {
        pthread_mutex_unlock(&_mutex);
    }

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

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

    bool hasTask()
    {
        return !_taskQueue.empty();
    }

private:
    bool _isStart;            // 判断线程池是否开启
    uint32_t _ThreadNum;      // 线程池中的线程数量
    std::queue<T> _taskQueue; // 任务队列
    pthread_mutex_t _mutex;   // 保护任务队列的锁
    pthread_cond_t _cond;     // 线程池的条件变量
};

线程池版服务器:

#pragma once

#include "TcpSocket.hpp"
#include "Task.hpp"
#include "ThreadPool.hpp"

typedef std::function<void(TcpSocket, const std::string &, uint16_t)> Handler;

class TcpThreadPoolServer
{
public:
    TcpThreadPoolServer(int port, const std::string &ip = "")
        : _port(port), _ip(ip)
    {
        _sock.Socket();
        _tp.start();
    }
    ~TcpThreadPoolServer() {}

public:
    bool Start(Handler handler)
    {
        // 绑定IP和端口号
        CHECK_RET(_sock.Bind(_ip, _port));
        // 监听
        CHECK_RET(_sock.Listen(5));

        // 进入循环
        while (true)
        {
            // 进行accept
            TcpSocket peer_sock;
            std::string ip;
            uint16_t port = 0;
            if (!_sock.Accept(&peer_sock, &ip, &port))
            {
                continue;
            }

            printf("[client %s:%d] connect!\n", ip.c_str(), port);

            // 执行任务
            // TODO
            Task task(peer_sock, ip, port, handler);
            _tp.push(task);
        }
    }

private:
    // tcp socket对象
    TcpSocket _sock;
    // 端口号
    uint16_t _port;
    // ip地址
    std::string _ip;
    // 线程池
    ThreadPool<Task> _tp;
};

结果

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

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

相关文章

Kubernetes + Docker 部署一个yolov5检测服务(基于FastDeploy)

Kubernetes Docker 从零部署一个yolov5检测服务&#xff0c;服务基于PaddlePaddle/FastDeploy的服务化部署&#xff1b;所有软件从零安装。 文章目录1.说明2.环境3.安装过程 3.1安装 Docker 3.2安装 minikube 3.3安装 Kubectl4.部署过程 4.1 Docker相关 4.2 k8s相关 4.3 启动服…

开发必备技术--docker(使用篇)

文章目录前言Docker的基本概念概念数据卷虚拟网络镜像操作镜像名称镜像命令容器操作基本操作容器创建数据卷操作创建和查看数据卷其他指令实战前言 续接上一篇博文&#xff1a; 开发必备技术–docker&#xff08;一&#xff09; 这也是开学了&#xff0c;假期的最后一篇博文&a…

minio下载文件速度很慢的原因分析与说明

文章目录1.实战背景2.问题描述3.问题分析4.问题解决1.实战背景 最近在做一个项目&#xff0c;需要用到minio来搭建文件系统&#xff0c;先简单说一下我在项目中设置的上传文件流程&#xff1a; 前端将分块文件逐一传给后端&#xff0c;后端再存储到 linux服务器的minio 当中。…

JAVA集合专题3 —— vector + LinkedList + Set

目录vector的特点LinkedList底层结构模拟双向链表比较ArrayList和LinkedListSet接口基本介绍Set接口的遍历方式Set接口实现类对象的特点Set接口实现类HashSet模拟HashSet/HashMap的底层结构vector的特点 Vector底层是一个对象数组Vector是线程同步的&#xff0c;即线程安全的&…

保姆级 | ChatGPT接入微信教程

文章目录 0x00 前言 0x01 环境说明 0x02 准备工作 0x03 报错 Not available 解决方法 0x04 登录Open AI账号 0x05 获取账号API 0x06 配置阿里云开源项目 0x07 OpenAI接入微信 0x08 ChatGPT微信使用演示 0x09 参考文献 0x10 总结 0x00 前言 ChatGPT 美国 OpenAI 研发…

使用 Sahi 实现 Web 自动化测试

Sahi 是 Tyto Software 旗下的一个基于业务的开源 Web 应用自动化测试工具。Sahi 运行为一个代理服务器&#xff0c;并通过注入 JavaScript 来访问 Web 页面中的元素。Sahi 支持 HTTPS 并且独立于 Web 站点&#xff0c;简单小巧却功能强大。它相对于 Selenium 等自动化测试工具…

【408】操作系统 - 刻骨铭心自测题1(上)

文章目录OS练习题第一部分&#xff1a;1&#xff1a;2&#xff1a;3&#xff1a;4&#xff1a;5&#xff1a;6&#xff1a;7&#xff1a;8&#xff1a;9&#xff1a;10&#xff1a;11&#xff1a;12&#xff1a;13&#xff1a;14&#xff1a;15&#xff1a;16&#xff1a;17&am…

C++ 类与对象(下)

✅<1>主页&#xff1a;我的代码爱吃辣 &#x1f4c3;<2>知识讲解&#xff1a;C &#x1f525;<3>创作者&#xff1a;我的代码爱吃辣 ☂️<4>开发环境&#xff1a;Visual Studio 2022 &#x1f4ac;<5>前言&#xff1a;C类与对象的收尾工作&#…

Android10/11 原生Launcher3深度定制

一、引言关于Android10和11系统Launcher3的定制有很多&#xff0c;根据项目的需求会进行各种定制开发&#xff0c;于是就需要研究Launcher3的源码。本文主要从Android 11的Launcher3QuickStep着手&#xff08;go版本或者其他版本类似&#xff09;从常用的修改进行分析&#xff…

[论文阅读] DAE-GCN: Identifying Disease-Related Features for Disease Prediction

[论文地址] [代码] [MICCAI 21] Abstract 学习与疾病相关的表征在基于图像的癌症诊断中起着至关重要的作用&#xff0c;因为它具有可信、可解释和良好的概括能力。一个好的表征不仅应该与疾病无关的特征相分离&#xff0c;而且还应该包含病变的属性信息&#xff08;如形状、边…

【博客616】prometheus staleness对PromQL查询的影响

prometheus staleness对PromQL查询的影响 1、prometheus staleness 官方文档的解释&#xff1a; 概括&#xff1a; 运行查询时&#xff0c;将独立于实际的当前时间序列数据选择采样数据的时间戳。这主要是为了支持聚合&#xff08;sum、avg 等&#xff09;等情况&#xff0c…

【ChatGpt】——不一样的使用感受分享

作者&#xff1a;狮子也疯狂 专栏&#xff1a;《基础知识查漏》 坚持做好每一步&#xff0c;幸运之神自然会降临在你的身上 目录一. &#x1f981; 前言二. &#x1f981; 使用详情Ⅰ. &#x1f407; 使用过程Ⅱ. &#x1f407; 使用感受Ⅲ. &#x1f407; 遇到的问题3.1 我遇…

linux服务器挂载硬盘/磁盘

1. 查看机器所挂硬盘个数及分区情况&#xff1a;fdisk -l可以看出来目前/dev/vda 目前有300G可用.内部有两个分区&#xff08;/dev/vda1,/dev/vda2&#xff09;。2. 格式化磁盘格式化磁盘命令为【mkfs.磁盘类型格式 目录路径组成】查看磁盘文件格式&#xff1a;df -T格式化磁盘…

SharkTeam:Move合约开发与合约安全

近期&#xff0c;围绕 Aptos 和 Sui&#xff0c;新兴的高性能 L1链 以及这些新链背后的 Move 智能合约编程语言引起了很多关注&#xff0c;社区也非常活跃&#xff0c;很多开发者和项目已经开始积极转向 Move。但Move相对Solidity差别较大&#xff0c;即使是相对比较接近的Rust…

数据与C(字符串)

目录 一.概念引入 二.字符串&#xff08;数组存储&#xff0c;必须以\0结尾&#xff09; 三.错误示范 四.strlen&#xff08;&#xff09;和sizeof()相对于字符串的不同 一.概念引入 “a”,a哪个是字符哪个又是字符串&#xff0c;嘿嘿不用猜了 我们在上一章中说过&#x…

服务端开发Java面试复盘篇1

上周投了一些简历&#xff0c;约了8-9家面试&#xff0c;其中完成了3家的第一轮面试&#xff0c;由于面试的是Java 的实习生&#xff0c;感觉问的题目都比较基础&#xff0c;不过有些问题回答的不是很好&#xff0c;在这里对回答的不太好的题目做一下总结和复盘。 目录 一、后…

【数据库】 mysql用户授权详解

目录 MySQL用户授权 一&#xff0c;密码策略 1&#xff0c;查看临时密码 2&#xff0c;查看数据库当前密码策略&#xff1a; 二&#xff0c; 用户授权和撤销授权 1、创建用户 2&#xff0c;删除用户 3&#xff0c;授权和回收权限 MySQL用户授权 一&#xff0c;密码策略…

Https 协议超强讲解(一)

都说Https协议非常安全&#xff0c;那为什么还是会被抓包呢&#xff1f;抓包后会影响什么吗&#xff1f; HTTPS协议 随着 HTTPS 建站的成本下降&#xff0c;现在大部分的网站都已经开始用上 HTTPS 协议。大家都知道 HTTPS 比 HTTP 安全&#xff0c;也听说过与 HTTPS 协议相关…

ChatGPT vscode中文插件

方式一和方式二只需要做一个就行 方式一&#xff1a;直接购买账号&#xff0c;购买渠道请自行寻找。 快捷键打开命令面板(ctrlshiftp 或者 commandshiftp) 输入 ChatGPT 然后选择 ChatGPT: 切换成国内/国外模式(将会重启VSCode) 命令来切换到国外模式此时会弹出一个输入框&am…

12.hadoop系列之MapReduce分区实践

本文我们学习MapReduce默认分区以及自定义分区实践 当我们要求将统计结果按照条件输出到不同文件(分区)&#xff0c;比如按照统计结果将手机归属地不同省份输出到不同文件中(分区) 1.默认Partitioner分区 public class HashPartitioner<K, V> extends Partitioner<…