网络套接字编程(三)

news2024/11/27 0:28:48

网络套接字编程(三)

文章目录

  • 网络套接字编程(三)
    • 简易日志组件
      • 引入日志的原因
      • 日志等级
      • 打印日志函数
      • 将日志组件使用到服务端中
    • 守护进程
      • 概念
      • 进程组、终端、会话
      • 守护进程的实现原理
      • 守护进程化组件
      • 将守护进程化组件使用到服务端中
    • 补充知识
      • 关于inet_ntoa

在上一篇博客 网络套接字编程(二)-CSDN博客中讲解了单执行流、多执行流、线程池版的简易TCP程序的编写,本文将讲解与其有关的组件编写。

简易日志组件

引入日志的原因

在实际开发中,服务端是需要不间断运行来保证无论何时都能给客户端提供网络服务,因此在程序遇到某些不影响程序运行的问题,不会主动终止程序,而是将错误信息以日志的形式打印。服务端维护人员,会通过日志中记录的错误信息,来进行程序错误的定位和解决。

日志等级

在日志系统中,常常使用不同的日志等级来对日志进行分类和标记,以便根据重要性和紧急程度进行过滤、查看和处理。不同的日志等级通常表示了不同的日志消息类型和优先级。

以下是常见的日志等级,按照从高到低的顺序排列:

  1. 致命错误(Fatal):表示严重的错误或故障,导致系统无法正常运行或继续执行。这类错误需要立即解决,并可能需要中断程序的执行。
  2. 错误(Error):表示一些关键操作或功能发生了错误,但系统仍然可以继续运行。这类错误需要进行修复,以确保系统正常运行。
  3. 警告(Warning):表示一些潜在问题或异常情况,可能会影响系统的正常运行或导致错误。这类日志用于提示潜在的风险或不寻常的行为,需要进行检查和调查。
  4. 信息(Info/Information):表示一般的信息性消息,用于记录程序的正常运行状态、关键路径、重要操作等。这类日志用于追踪应用程序的运行情况和关键事件。
  5. 调试(Debug):表示开发过程中的调试信息,用于调试和分析程序的内部逻辑、变量状态等。这类日志通常只在开发和测试阶段启用,可用于排查问题和验证程序行为。

本文采用枚举的方式来表示各个日志等级:

enum
{
    Debug=0,
    Info,
    Warning,
    Error,
    Fatal,
    Unknown
};

打印日志函数

本文想实现的日志组件中,打印的日志格式为[日志等级][时间][进程pid][日志消息体],日志组件的实现中核心部分就是打印日志函数,日志组件的具体实现如下:

#pragma once

#include <cstdio>
#include <cstring>
#include <cstdarg>
#include <iostream>
#include <time.h>
#include <sys/types.h>
#include <unistd.h>

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

enum
{
    Debug = 0,
    Info,
    Warning,
    Error,
    Fatal,
    Unknown
};

static std::string toLevelString(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";
    }
}

static std::string getTime()
{
    const time_t cur = time(nullptr);
    struct tm *tmp = localtime(&cur);
    char buffer[1024];
    snprintf(buffer, sizeof(buffer), "%d-%d-%d %d:%d:%d", (tmp->tm_year) + 1900, tmp->tm_mon+1, tmp->tm_mday,
             tmp->tm_hour, tmp->tm_min, tmp->tm_sec);
    return buffer;
}

// 等级 时间 pid 消息体
void LogMessage(int level, const char *format, ...)
{
    char logLeft[1024];
    std::string level_string = toLevelString(level);
    std::string curr_time = getTime();
    snprintf(logLeft, sizeof(logLeft), "[%s][%s][%d]\n", level_string.c_str(), curr_time.c_str(), getpid());

    char logRight[1024];
    va_list p;
    va_start(p, format);
    vsnprintf(logRight, sizeof(logRight), format, p);
    va_end(p);

    FILE *fp = fopen(filename.c_str(), "a");
    if (fp == nullptr)
        return;
    fprintf(fp, "%s%s\n", logLeft, logRight);
    fflush(fp);
    fclose(fp);
}

识别日志等级函数

我们预期的打印中,日志等级是通过字符串形式打印的,而不是枚举对应的数值,因此需要实现一个识别日志等级函数,将传入的枚举数值转换成字符串形式表示的日志等级。

该函数实现只需要简单的switch case语句将输入的数值对应成的字符串返回即可。

时间获取函数

我们预期的打印中,包含一个以字符串形式打印的时间部分,并且希望这个时间具体到年月日,时分秒,因此我们需要实现一个时间获取函数。

要实现这个时间获取函数,需要用到如下两个函数:

#include <time.h>

time_t time(time_t *t);
  • t参数: 如果t指针不为NULL,则time函数会将计算出的时间值存储在t指向的变量中,并返回该值;否则,time函数直接返回计算出的时间值。
  • time函数返回自1970年1月1日经过的秒数(也称为Unix时间戳),其类型为time_t。
#include <time.h>

struct tm *localtime(const time_t *timep);
  • 该函数能够将传入的time_t类型的时间值转换为本地时间(系统默认时区)的tm结构体类型。
  • 返回值是转换后得到的tm结构体类型。
  • 由于localtime函数返回的tm结构体中有些成员的范围并不完全符合人们常用的表示方式,比如tm_mon表示的月份范围是0到11,因此在使用这些值进行操作和显示时,可能需要进行适当的转换和调整。

tm结构体的定义如下:

struct tm {
  int tm_sec;   // 秒 [0, 60]
  int tm_min;   // 分钟 [0, 59]
  int tm_hour;  // 小时 [0, 23]
  int tm_mday;  // 月份中的日期 [1, 31]
  int tm_mon;   // 月份 [0, 11],0表示一月
  int tm_year;  // 年份,减去1900后的值
  int tm_wday;  // 一周中的星期几 [0, 6],0表示星期日
  int tm_yday;  // 一年中的第几天 [0, 365],0表示1月1日
  int tm_isdst; // 夏令时标识符,负数表示不可确定状态
};

可变参数列表

C/C++语言标准库提供的 <cstdarg> 头文件中提供了可变参数列表的操作方法:

  • va_list 是一个类型,在实际的使用中,它通常被定义为指向变长参数列表的指针。
  • va_start 是一个宏函数,它用于对 va_list 类型的变量进行初始化,将其指向第一个可变参数的位置。在如上代码中,p是一个 va_list 类型的变量,format 是可变参数列表中的第一个参数。
  • vsnprintf 是一个函数,它可以根据提供的格式字符串 formatva_list 类型的变量 p,将可变参数列表中的值按照指定的格式进行格式化输出,并将结果存储到 logRight 字符数组中。
  • va_end 是一个宏函数,它用于结束可变参数的获取,进行必要的清理工作。在这个例子中,它将释放 p 变量所占用的资源。

将日志组件使用到服务端中

我们将日志组件使用在上一篇博客网络套接字编程(二)-CSDN博客中实现的线程池版的服务端中,添加日志组件需要需添加到服务端类内部的函数,添加日志组件后具体的代码实现如下:

enum
{
    SOCKET_ERROR = 1,
    BIND_ERROR,
    LISTEN_ERROR,
    USAGE_ERROR
};
static const uint16_t default_port = 8081;
static const int backlog = 32;
class TcpServer
{
    public:
    TcpServer(uint16_t port = default_port) : _port(port) {}
    void InitServer()
    {
        // 创建套接字
        _listensock = socket(AF_INET, SOCK_STREAM, 0);
        if (_listensock < 0)
        {
            LogMessage(Fatal, "create socket error, %d:%s", errno, strerror(errno)); // 打印日志
            exit(SOCKET_ERROR);
        }
        LogMessage(Info, "create socket success"); // 打印日志
        // 绑定IP地址和端口号
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        local.sin_addr.s_addr = INADDR_ANY;
        local.sin_port = htons(_port);
        if (bind(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            LogMessage(Fatal, "bind socket error, %d:%s", errno, strerror(errno)); // 打印日志
            exit(BIND_ERROR);
        }
        LogMessage(Info, "bind socket success"); // 打印日志
        // 监听
        if (listen(_listensock, backlog) < 0)
        {
            LogMessage(Fatal, "listen socket error, %d:%s", errno, strerror(errno)); // 打印日志
            exit(LISTEN_ERROR);
        }
        LogMessage(Info, "listen socket success"); // 打印日志
    }
    void StartServer()
    {
        ThreadPool<Task> tp;
        tp.Start();
        while (true)
        {
            // 获取连接
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            int sock = accept(_listensock, (struct sockaddr *)&peer, &len);
            if (sock < 0)
            {
                LogMessage(Fatal, "accept error, %d:%s", errno, strerror(errno)); // 打印日志
                continue;
            }
            std::string clientip = inet_ntoa(peer.sin_addr);
            uint16_t clientport = ntohs(peer.sin_port);
            LogMessage(Info, "accept socket success, %s-%d,for%d", clientip.c_str() ,clientport, sock); // 打印日志
            // 网络服务
            Task t(sock, clientip, clientport);
            tp.PushTask(t);
        }
    }

    private:
    uint16_t _port;
    int _listensock;
};

程序测试

启动服务端,并且查看日志文件,可以看到服务端将信息打印到了日志文件中:

image-20231101185408082

使用客户端连接服务端进行网络通信,退出客户端,查看日志内容:

image-20231101185549658

守护进程

概念

守护进程(daemon)是在操作系统后台运行且独立于终端会话的一种特殊进程。它通常用于在系统启动时执行某些长期运行的任务或服务,如网络服务等。守护进程在启动时会脱离当前终端会话,以独立的进程在后台运行,不受终端关闭或用户注销的影响。

将服务端变成守护进程后,即使关闭启动进程的终端,也不会影响守护进程的运行,只要运行守护进程的主机不关闭,并且进程不出错,就可以实现让进程无停止的运行。实现在网络服务端中,就是让该服务端始终能够给客户端提供网络服务。

进程组、终端、会话

指令查看进程相关信息

在Xshell下每打开一个窗口就是建立一个终端,建立两个终端,其中一个启动一个后台运行的sleep进程,另一个使用指令ps axj | head -1 && ps axj | grep sleep 查看这个进程的信息:

image-20231101201647143

其中有如下信息:

  • PGID-进程组ID
  • SID-会话ID
  • TTY-终端文件

其中TTY下为?的进程与终端没有关系,显示为pts/n,即表示该进程打开了n号终端。在操作系统看来,就是该进程打开了该终端对应的终端文件,这些终端文件存在/dev设备文件路径下。如果向终端文件写入数据,就会显示在对应终端上:

image-20231101202749698

会话和进程组的概念

在Xshell下每启动一个终端,Linux操作系统就会为其创建一个会话,会话中会存在若干个包含一个或多个进程的进程组,其中存在bash以及其所在的进程组。而后在终端下所作的所有操作都会在这个会话中进行。

image-20231101204308388

进程组可能不止一个进程,进程组ID是一组进程中的第一个进程的进程ID或者具有”血缘“的进程中”辈分“最大的进程,比如一对父子进程,父进程ID为进程组ID。

启动一组sleep进程,查看它们的进程组ID:

image-20231101211733311

该组进程的进程组ID为第一个进程的进程ID。

进程组的作用是完成任务,其中任务是操作系统分配给进程或线程执行的工作。也就是操作系统会将一项工作交给一个进程组来完成,这个工作可能一个进程就能完成,也可能需要多个进程完成。如果一个进程能完成,该进程就会独立成组,多个进程才能完成,就会让多个进程形成进程组。

在Linux操作系统下,使用jobs指令可以查看该会话下的任务:

image-20231101210057826

使用fg 任务编号指令可以让后台进程变成前台进程:

image-20231101210150361

使用ctrl+z指令可以暂停前台进程并让他变成后台进程:

image-20231101210236435

使用bg 任务编号可以让暂停的后台进程运行:

image-20231101210332884

如果将一个后台任务变成前台任务,之前的前台任务就无法运行了,一个会话下只能有一个前台任务运行。开启一个终端,Linux操作系统就会为了对应创建一个会话,如果关闭这个终端,Linux操作系统就会销毁对应的会话,会话销毁就会影响会话中原有的进程。

守护进程的实现原理

守护进程的实现是让进程脱离启动它的终端对应的会话,自身独自处于一个会话中:

image-20231101211928687

进程独自处于一个会话后,就不会受到其他会话的影响,只要操作系统不停止运行,该进程就能一直运行下去。这就是一些提供网络服务的服务端进程的运行原理。

守护进程化组件

守护进程化组件的功能是让调用它的进程变成守护进程。

为了让进程变成守护进程需要使用Linux操作系统下提供的创建会话让进程独享的setsid函数:

#include <unistd.h>

pid_t setsid(void);
  • 该函数的功能是让调用的进程独自处于一个新的会话中。
  • 返回值: 调用成功,返回新的会话ID(调用该函数的进程的ID)。调用失败,返回-1,错误码被设置。
  • 注意: 调用该函数的进程不能是所处进程组的组长。

进程守护进程化的步骤:

  1. 忽略相关信号
  2. 让进程进程不再是组长
  3. 创建新的会话,让进程成为会话的首个进程
  4. (可选)更改进程的工作路径
  5. 处理进程的0,1,2号文件描述符

守护进程化组件的具体代码实现如下:

void Daemon()
{
    //忽略相关信号
    signal(SIGPIPE, SIG_IGN);
    signal(SIGCHLD, SIG_IGN);
    //让自己不再是组长
    if (fork() > 0) exit(0);
    //创建新的会话,让自己成为首个进程
    pid_t ret = setsid();
    if ((int)ret == -1)
    {
        LogMessage(Fatal, "deamon error, code: %d, string: %s", errno, strerror(errno));//打印日志
        exit(SETSID_ERROR);
    }
    //处理0,1,2文件描述符
    int fd = open("/dev/null", O_RDWR);
    if (fd < 0 )
    {
        LogMessage(Fatal, "open error, code: %d, string: %s", errno, strerror(errno));//打印日志
        exit(OPEN_ERROR);
    }
    dup2(fd, 0);
    dup2(fd, 1);
    dup2(fd, 2);
    close(fd);
}

守护进程是一种特殊的孤儿进程

由于调用setsid函数的进程不能是进程组组长,因此需要创建子进程完成setsid函数的调用,然后终止无用的父进程,因此最终完成任务的子进程,由于父进程终止了,因此守护进程是一种特殊的孤儿进程。执行任务是网络服务时,由于套接字操作中,始终是和相关的套接字文件进行操作,因此拷贝了父进程文件描述符表的子进程可以完成父进程的任务。

处理0,1,2文件描述符

由于守护进程不想受到外部设备输入的影响,也不想向外部设备输出,因此需要关闭0,1,2文件描述符,Linux操作系统中/dev/null是一个不会有任何实质性数据的文件,因此让守护进程从这个文件读取,不会读取到任何数据,让守护进程向这个文件写入,不会写入任何数据到外部设备上。落实到代码上就是让0,1,2文件描述符指向该文件。

将守护进程化组件使用到服务端中

使用守护进程化组件,只需要在创建服务端类并初始化后调用组件即可,具体的代码实现如下:

void Usage(const char *proc)
{
    std::cout << "Usage:\n\t" << proc << " port\n" << std::endl; 
}

int main(int argc, char* argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(USAGE_ERROR);
    }
    uint16_t port = atoi(argv[1]);
    std::unique_ptr<TcpServer> tsvr(new TcpServer(port));
    tsvr->InitServer();
    Daemon();//守护进程化
    tsvr->StartServer();
    return 0;
}

程序测试

在一个终端中启动服务端进程,然后使用指令ps axj | head -1 && ps axj | grep sleep 查看进程信息:

image-20231101221051467

服务端进程独自使用了一个会话(该终端的会话ID和grep进程的一样),并且与终端无关。即使关闭终端也不会影响该服务端的运行了。

关闭该终端,启动一个新的终端运行客户端连接该服务器:

image-20231101221355151

另外还可以查看日志,了解这个服务端的信息:

image-20231102143300668

补充知识

关于inet_ntoa

inet_ntoa函数是系统提供的将四字节整形IP地址转换成char*类型的IP地址。

//inet_ntoa所在的头文件和函数声明
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

char *inet_ntoa(struct in_addr in);

inet_ntoa这个函数返回了一个char*, 很显然是这个函数自己在内部为我们申请了一块内存来保存ip的结果. 那么是否需要调用者手动释放呢?

image-20231102144522988

man手册上说, inet_ntoa函数, 是把这个返回结果放到了静态存储区. 这个时候不需要我们手动进行释放。但man手册也提到了,每次调用该函数都会覆盖该缓冲区,如果您需要在多个地方使用返回的字符串,应该立即将其复制到另一个缓冲区中。如果一个线程一直使用该返回值指向的字符串作为参数进行操作,其他线程再调用该函数就会覆盖这个字符串,导致数据不一致。

因此,inet_ntoa不是线程安全的函数。但是在centos7上测试, 并没有出现问题, 可能内部的实现加了互斥锁。在多线程环境下, 推荐使用inet_ntop, 这个函数由调用者提供一个缓冲区保存结果, 可以规避线程安全问题。

#include <arpa/inet.h>

const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
  • af参数: 指定了地址族,可以取值AF_INET或AF_INET6,分别表示IPv4和IPv6地址族。
  • src参数: 是一个指向待转换的二进制地址的指针。
  • dst参数: 是一个用于存储转换结果的缓冲区指针。
  • size参数: 指定缓冲区的大小。
    串,应该立即将其复制到另一个缓冲区中。如果一个线程一直使用该返回值指向的字符串作为参数进行操作,其他线程再调用该函数就会覆盖这个字符串,导致数据不一致。

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

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

相关文章

Required String parameter ‘name‘ is not present

[org.springframework.web.bind.MissingServletRequestParameterException: Required String parameter name is not present] 服务端有参数name&#xff0c;客户端没有传上来

Java--网络通信

1.端口Port&#xff1a; 通常计算机上提供了HTTP,FTP等多种服务&#xff0c;客户机通过不同的端口来确定连接到服务器的哪项服务上。 2.套接字Socket&#xff1a; 套接字Socket用于将应用程序与端口连接起来。套接字是一个假想的链接装置。 3.InetAddress类 java.net包中的Ine…

一文带你轻松拿下Java中的抽象类

&#x1f937;‍♀️&#x1f937;‍♀️&#x1f937;‍♀️各位看官你们好呀&#xff01;&#xff01;&#xff01; 今天我带大家来深入了解一下Java中的抽象类&#xff0c;相信看完这篇文章&#xff0c;你将会有很大的收获&#xff01; 个人主页 &#x1f302;c/java领域新星…

23种设计模式(创建型、构造型、行为型)

目录 设计模式一、创建型设计模式1.1、简单工厂模式(SimpleFactory)1.2、工厂方法&#xff08;Factory Method&#xff09;1.3、 抽象工厂&#xff08;Abstarct Factory&#xff09;1.4、生成器模式&#xff08;Builder&#xff09;1.5、 原型模式&#xff08;Prototype&#x…

轻量封装WebGPU渲染系统示例<12>- 基础3D对象实体(源码)

当前示例源码github地址: https://github.com/vilyLei/voxwebgpu/blob/main/src/voxgpu/sample/PrimitiveEntityTest.ts 此示例渲染系统实现的特性: 1. 用户态与系统态隔离。 细节请见&#xff1a;引擎系统设计思路 - 用户态与系统态隔离-CSDN博客 2. 高频调用与低频调用隔…

Spark的主要概念

文章目录 &#x1f50a;博主介绍&#x1f964;本文内容&#x1f34a; 1. RDD&#x1f34a; 2. Spark SQL&#x1f34a; 3. Spark Streaming&#x1f34a; 4. MLlib&#x1f34a; 5. GraphX&#x1f34a; 总结 &#x1f4e2;文章总结&#x1f4e5;博主目标 &#x1f50a;博主介绍…

python-根据关键词匹配连续的内容

运用PyQt5生成可执行小程序&#xff1a;匹配起始关键词到截止关键词区间的GGA格式的内容&#xff0c;支持多选文件&#xff0c;并清除过程中产生的复制文件。 GGA文件如下&#xff1a; $GPZDA,063052.00,16,10,2023,,*61 $GPGGA,063052.00,4349.7377413,N,12509.8354912,E,4,…

Swift语言配合HTTP写的一个爬虫程序

下段代码使用Embassy库编写一个Swift爬虫程序来爬取jshk的内容。我会使用proxy_host为duoip&#xff0c;proxy_port为8000的爬虫IP服务器。 使用Embassy库编写一个Swift爬虫程序可以实现从网页上抓取数据的功能。下面是一个简单的步骤&#xff1a; 1、首先&#xff0c;需要在X…

JMM讲解

一&#xff1a;为什么要有JMM&#xff0c;它为什么出现&#xff1f; CPU的运行并不是直接操作内存而是先把内存里面的数据读到缓存&#xff0c;而内存的读和写操作的时候会造成不一致的问题。JVM规范中试图定义一种Java内存模型来屏蔽掉各种硬件和操作系统的内存访问差异&…

MongoDB安装及开发系例全教程

一、系列文章目录 一、MongoDB安装教程—官方原版 二、MongoDB 使用教程(配置、管理、监控)_linux mongodb 监控 三、MongoDB 基于角色的访问控制 四、MongoDB用户管理 五、MongoDB基础知识详解 六、MongoDB—Indexs 七、MongoDB事务详解 八、MongoDB分片教程 九、Mo…

基于nodejs+vue 网上商城系统系统-毕业设计

目 录 摘 要 I ABSTRACT II 目 录 II 第1章 绪论 1 1.1背景及意义 1 1.2 国内外研究概况 1 1.3 研究的内容 1 第2章 相关技术 3 2.1 nodejs简介 4 2.2 express框架介绍 6 2.4 MySQL数据库 4 第3章 系统分析 5 3.1 需求分析 5 3.2 系统可行性分析 5 3.2.1技术可行性&#xff1a;…

mediasoup webrtc音视频会议搭建

环境ubuntu22.10 nvm --version 0.33.11 node -v v16.20.2 npm -v 8.19.4 node-gyp -v v10.0.1 python3 --version Python 3.10.7 python with pip: sudo apt install python3-pip gcc&g version 12.2.0 (Ubuntu 12.2.0-3ubuntu1) Make 4.2.1 npm install mediasoup3 sudo …

pytorch+LSTM实现使用单参数预测,以及多参数预测(代码注释版)

开发前准备&#xff1a; 环境管理&#xff1a;Anaconda python: 3.8 显卡&#xff1a;NVIDIA3060 pytorch: 到官网选择conda版本&#xff0c;使用的是CUDA11.8 编译器&#xff1a; PyCharm 简述&#xff1a; 本次使用seaborn库中的flights数据集来做试验&#xff0c;我们通过…

AI:52-基于深度学习的垃圾分类

🚀 本文选自专栏:AI领域专栏 从基础到实践,深入了解算法、案例和最新趋势。无论你是初学者还是经验丰富的数据科学家,通过案例和项目实践,掌握核心概念和实用技能。每篇案例都包含代码实例,详细讲解供大家学习。 📌📌📌本专栏包含以下学习方向: 机器学习、深度学…

基于单片机控制的GSM短信模块家庭防盗报警系统

博主主页&#xff1a;单片机辅导设计 博主简介&#xff1a;专注单片机技术领域和毕业设计项目。 主要内容&#xff1a;毕业设计、简历模板、学习资料、技术咨询。 文章目录 主要介绍一、内容1 设计任务和要求1 主要内容 二、系统总体方案2.1 系统整体设计思路2.2 系统方案设计 …

抛弃繁琐、提高效率:低代码工具助你飞速开发 | 开源专题 No.42

supabase/supabase Stars: 56.9k License: Apache-2.0 Supabase 是一个开源的 Firebase 替代品&#xff0c;使用企业级开源工具构建了 Firebase 的功能。其主要功能包括&#xff1a; 托管 Postgres 数据库身份验证和授权自动生成 API (支持 REST 和 GraphQL)实时订阅函数 (包…

阿里云盘第三方linux客户端“小白羊”云盘“Aria2本地连接已断开”错误的解决方法

简介 随着数据的不断增长&#xff0c;我们需要更大的存储空间来保存我们的信息。阿里云盘是阿里巴巴推出的一款云存储服务&#xff0c;它提供了大量可扩展的存储空间。然而&#xff0c;阿里云盘官方没有提供Linux操作系统的客户端。 在这种情况下&#xff0c;“小白羊”云盘…

算法通关村第五关-白银挑战队列经典问题

大家好我是苏麟 , 今天带来几道经典小题 . 大纲 两数之和 两数之和 相信大家对这道题还是很眼熟的 , 打开LeetCode第一道题就是它 , 对它可真的又爱又恨 , 很多新手朋友们想刷LeetCode但又不知道从哪开始就打开了第一题 , 结果就对算法失去了信心 . 这道题找对方法还是很容易…

tcp/ip协议2实现的插图,数据结构2 (15 - 章)

(40) 40 十五1 插口层 结构socket,sysent (41) 41 十五2 插口层 实用函数与file结构描述汇总 (42) 42 十五3 插口层 函socket,socreate,pr_usrreq (43)