[计算机网络]---网络编程套接字

前言

作者:小蜗牛向前冲

名言:我可以接受失败,但我不能接受放弃

  如果觉的博主的文章还不错的话,还请点赞,收藏,关注👀支持博主。如果发现有问题的地方欢迎❀大家在评论区指正 

目录

 一、基础知识

1、源IP地址和目的IP地址

 2、端口号

二、网络套接字

1、网络字节序

2、socket编程接口 

三、基于tcp协议的网络通信 

1、服务器的编写 

 2、客户端的编写

3、日志报告的编写 

 四、TCP协议通讯流程

1、通信流程

2、三次握手和四次挥手 


本期学习:网络基础知识,网络套接字,基于tcp协议的网络编程,tcp协议的三次握手和四次挥手。

 一、基础知识

1、源IP地址和目的IP地址

源IP地址:

  • 源IP地址是指发起网络通信的设备或主机的IP地址
  • 在TCP/IP协议中,源IP地址用于标识数据包的来源,使得接收方知道从哪里收到数据
  • 源IP地址包含在网络数据包的IP头部中。

目的IP地址:

  • 目的IP地址是指网络通信的目标设备或主机的IP地址
  • 在TCP/IP协议中,目的IP地址用于指定数据包的目标,确保数据包被传递到正确的位置。
  • 目的IP地址同样包含在网络数据包的IP头部中。

下面我们用唐僧取经的例子来理解:

唐僧到女儿国,那国王问,高僧从那来,到哪里去 ,在网络中就是问源ip和目的ip。

也可能会问高僧上一站从那来,下一站到哪里去。在网络中指的就是MAC地址

MAC地址:

MAC地址(Media Access Control address),也称为物理地址或硬件地址,是网络通讯中用于唯一标识网络接口控制器(NIC,网络接口卡)的一个地址。每个网络设备的NIC都有一个全球唯一的MAC地址,这个地址在生产时被固化在硬件中

MAC地址的长度通常是48位(6个字节),有时也表示为64位以适应某些特定技术标准。

 MAC地址通常以十六进制数表示,每个字节之间用冒号(:)或者破折号(-)分隔,例如00:1A:2B:3C:4D:5E00-1A-2B-3C-4D-5E

 2、端口号

      我们光有IP地址就可以完成通信了嘛? 想象一下发qq消息的例子, 有了IP地址能够把消息发送到对方的机器上, 但是还需要有一个其他的标识来区分出, 这个数据要给哪个程序进行解析。所以就提出用端口号来标识唯一的程序(服务器)。

端口号(port)是传输层协议的内容:

  • 端口号是一个2字节16位的整数;
  • 端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理;
  • IP地址 + 端口号能够标识网络上的某一台主机的某一个进程;
  • 一个端口号只能被一个进程占用

问题1: 我们之前在学习系统编程的时候, 学习了 pid 表示唯一一个进程; 此处我们的端口号也是唯一表示一个进程. 那么这 两者之间是怎样的关系?


a:系统是系统,网络是网络,这里可以达到解耦的效果

b: 需要客户端每次都能找到服务端,而pid是在进程每次生成时随机分配的

c:不是所以的网络进程都需要网络提供网络服务或者请求,但是所以的进程都需要用pid进行标识

所以说:一个进程可以绑定多个端口号; 但是一个端口号不能被多个进程绑定


注意:

  • 端口号范围从0到65535,其中0到1023是被知名服务占用的端口号,称为“系统端口”或“保留端口”,而1024到65535是动态或私有端口,用于一般应用程序或自定义服务(下面我们进行的测试常用8080端口)
  • 传输层协议(TCP和UDP)的数据段中有两个端口号, 分别叫做源端口号和目的端口号. 就是在描述 "数据是谁发的, 要 发给谁"。

二、网络套接字

1、网络字节序

我们已经知道,内存中的多字节数据相对于内存地址有大端和小端之分, 磁盘文件中的多字节数据相对于文件中的偏 移地址也有大端小端之分, 网络数据流同样有大端小端之分. 那么如何定义网络数据流的地址呢? 

在网络中无论是发送主机还是接收主机都是将:缓冲区中的数据按内存地址从低到高的顺序发送或者接收

因此,网络数据流的地址应这样规定:先发出的数据是低地址,后发出的数据是高地址

TCP/IP协议规定,网络数据流应采用大端字节序,即低地址高字节

也就是说不管这台主机是大端机还是小端机,到会按照大端机发送。

库函数做网络 字节序和主机字节序的转换。 

#include<arpa/inet.h>

uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
  •  这些函数名很好记,h表示host,n表示network,l表示32位长整数,s表示16位短整数。
  • 例如htonl表示将32位的长整数从主机字节序转换为网络字节序,例如将IP地址转换后准备发送。
  • 如果主机是小端字节序,这些函数将参数做相应的大小端转换然后返回; 如果主机是大端字节序,这些函数不做转换,将参数原封不动地返回.

2、socket编程接口 

API(Application Programming Interface)是一组定义在软件中不同组件之间交互的规范和工具。API可以看作是一座桥梁,它定义了如何访问或使用软件组件的方法。在软件开发中,API允许不同的程序部分之间进行通信,以便它们能够相互协作而无需详细了解彼此的内部实现。

常见的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);

sockaddr结构

 sockaddr 结构(有时候在不同的系统中会有稍微不同的变体,如 sockaddr_in)是用于表示套接字地址信息的数据结构,在网络编程中经常会遇到。它通常用于指定网络通信中的端点地址,包括 IP 地址和端口号。

struct sockaddr {
    unsigned short sa_family;    // 地址族,如 AF_INET(IPv4)或 AF_INET6(IPv6)
    char sa_data[14];            // 地址数据
};

主要的字段:

  1. sa_family:用于指定地址族,表示地址的类型。例如,AF_INET 表示 IPv4 地址族,AF_INET6 表示 IPv6 地址族等。这个字段是一个无符号短整型(unsigned short)。

  2. sa_data:包含地址的具体数据,通常用于存储 IP 地址和端口号等信息。在不同的地址族下,这个字段的内容会有所不同。

在实际使用中,为了更方便地表示 IPv4 地址,通常会使用更具体的套接字地址结构,如 sockaddr_in,它的定义如下: 

struct sockaddr_in {
    short int sin_family;          // 地址族,如 AF_INET
    unsigned short int sin_port;   // 端口号
    struct in_addr sin_addr;       // IPv4 地址
    unsigned char sin_zero[8];     // 未使用的填充字段
};

三、基于tcp协议的网络通信 

上面我们说了怎么多,下面我们就用起来,虽然我们现在还是那么清楚udp和tcp协议,但是我大概清楚了他是用来通信的。

这里我们要实现一个简单版本的服务器为客户端提供服务,这里我们的服务仅仅需要回显信息。

我们要写一个服务器

tcpServer.hpp
tcpServer.cc

一个客户端

tcpClient.hpp
tcpClinet.cc

一个日志报告

log.hpp

1、服务器的编写 

tcpServer.hpp:这里我们要完成服务器的初始化,启动和销毁。

服务器的初始化是通过socket创建套嵌字,bind绑定网络,在进行listen监听.

启动要完成accept获取新链接,在执行程序任务。

这里我们在执行任务的过程中,我们可以:

  • 让程序自己执行
  • 创建子进程执行
  • 让多线程执行
  • 让线程池执行

这里为了让程序更好的理解,我们就让子进程执行我们serverio。 

#pragma once

#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>

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

namespace server
{
    enum
    {
        USAGE_ERR = 1, // usage_err
        SOCKET_ERR,    // sockft_err
        BIND_ERR,      // bind_err
        LISTEN_ERR     // listen_err
    };

    static const uint16_t gport = 8080;
    static const int gbacklog = 5;
    class TcpServer; // 这是一个前置声明,我们在类tcpServer中定义了ThreadData数据的类防止出现循环依赖

    class ThreadData
    {
    public:
        ThreadData(TcpServer *self, int sock) : _self(self), _sock(sock)
        {
        }

    public:
        TcpServer *_self;
        int _sock;
    };
    class TcpServer
    {
    public:
        TcpServer(const uint16_t &port = gport) : _listensock(-1), _port(port)
        {
        }

        void initServer()
        {
            // 1 创建套接字
            _listensock = socket(AF_INET, SOCK_STREAM, 0); // sock_stream
            if (_listensock < 0)
            {
                logMessage(FATAL, "create error socket");
                exit(SOCKET_ERR);
            }
            logMessage(NORMAL, "create socket success:%d", _listensock);

            // 2 bind自己的网络信息
            struct sockaddr_in local;
            memset(&local, 0, sizeof(local));
            local.sin_family = AF_INET;
            local.sin_port = htons(_port);
            local.sin_addr.s_addr = INADDR_ANY; // inaddr_any

            if (bind(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0)
            {
                logMessage(FATAL, "bind error ");
                exit(BIND_ERR);
            }
            logMessage(NORMAL, "bind socket success");

            // 3 设置socket 为监听状态
            if (listen(_listensock, gbacklog) < 0)
            {
                logMessage(FATAL, "listen socket success");
                exit(LISTEN_ERR);
            }
            logMessage(NORMAL, "listen socket success");
        }

        void start()
        {
            for (;;)
            {
                // 4 server获取链接
                //  sock, 和client进行通信的fd
                struct sockaddr_in peer;
                socklen_t len = sizeof(peer);
                int sock = accept(_listensock, (struct sockaddr *)&peer, &len);
                if (sock < 0)
                {
                    logMessage(ERROR, "accept error, next");
                    continue;
                }
                logMessage(NORMAL, "accept a new link success, get new sock: %d", sock);

                // 5. 这里就是一个sock,未来通信我们就用这个sock,面向字节流的,后续全部都是文件操作!
                // version1
                // serviceIO(sock);
                // close(sock);
                // version2多进程版本
                pid_t id = fork();
                // 我们让子进程执行的io任务,父进程什么都不做,回收子进程就好
                if (id == 0)
                {
                    close(_listensock);
                    serviceIO(sock);
                    close(sock);
                    exit(0);
                }
                close(sock);

                // 父进程
                pid_t ret = waitpid(id, nullptr, 0);
                if (ret > 0)
                {
                    std::cout << "waitsuccess: " << ret << std::endl;
                }
                // version3多线程
                // pthread_t tid;
                // ThreadData *td = new ThreadData(this, sock);
                // pthread_create(&tid, nullptr, threadRoutine, td);
                // pthread_join(tid, nullptr);
                // version4 线程池
                // ThreadPool<Task>::getInstance()->push(Task(sock, serviceIO));
            }
        }
        // static void *threadRoutine(void *args)
        // {
        //     pthread_detach(pthread_self());
        //     ThreadData *td = static_cast<ThreadData *>(args);
        //     serviceIO(td->_sock);
        //     close(td->_sock);
        //     delete td;
        //     return nullptr;
        // }
        ~TcpServer()
        {
        }

    private:
        int _listensock; // 用了监听
        uint16_t _port;
    };
}

下面是一些任务:

#pragma once

#include <iostream>
#include <string>
#include <cstdio>
#include <functional>

using namespace std;

void serviceIO(int sock)
{
    char buffer[1024];
    while (true)
    {
        ssize_t n = read(sock, buffer, sizeof(buffer) - 1);
        if (n > 0)
        {
            // 把读到的数据当做字符串
            buffer[n] = 0;
            cout << "recv message" << buffer << endl;

            // 信息返回
            string outbuffer = buffer;
            outbuffer += buffer;
            outbuffer += "server[echo]";
            // outbuffer.c_str(),将字符串类型的指针,转换为字符类型的指针
            write(sock, outbuffer.c_str(), sizeof(outbuffer) - 1);
        }
        else if (n == 0)
        {
            // client退出
            logMessage(NORMAL, "client quit,me to");
            break;
        }
    }
}

class Task
{
    // using 用于创建类型别名
    using func_t = function<void(int)>;

public:
    Task()
    {
    }
    Task(int sock, func_t func)
        : _sock(sock), _callback(func)
    {
    }
    void operator()()
    {
        _callback(_sock);
    }
    ~Task()
    {
    }

private:
    int _sock;
    func_t _callback;
};

tcpServer.cc:主程序的执行

#include "tcpserver.hpp"
#include "daemon.hpp"
#include <memory>

using namespace server;
using namespace std;

static void Usage(string proc)
{
    cout << "\nUsage:\n\t" << proc << " local_port\n\n";
}

// tcp服务器,启动上和udp server一模一样
// ./tcpserver local_port
int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        Usage(argv[0]);
        exit(USAGE_ERR);
    }
    uint16_t port = atoi(argv[1]);

    unique_ptr<TcpServer> tsvr(new TcpServer(port));
    tsvr->initServer();

    // daemonSelf();
    tsvr->start();
    // daemonSelf();

    return 0;
}

 2、客户端的编写

tcpclient.hpp:对于客户端的初始化,我们仅仅只是需要创建套接字,而bind操作系统会帮助我们完成。

客户端的启动我们要用connect接收链接

#pragma once

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

#define NUM 1024
using namespace std;
class tcpclient
{
public:
    tcpclient(const string &serverip, const uint16_t &serverport)
        : _sock(-1), _serverip(serverip), _serverport(serverport)
    {
    }
    void initclient()
    {
        // 1 创建套接字
        _sock = socket(AF_INET, SOCK_STREAM, 0);
        if (_sock < 0)
        {
            std::cerr << "socket create error" << std::endl;
            exit(2);
        }
    }
    void start()
    {
        struct sockaddr_in server;
        memset(&server, 0, sizeof(server));
        server.sin_family = AF_INET;
        server.sin_port = htons(_serverport);
        server.sin_addr.s_addr = inet_addr(_serverip.c_str());

        if (connect(_sock, (struct sockaddr *)&server, sizeof(server)) != 0)
        {
            std::cerr << "socket connect error" << std::endl;
        }
        else
        {
            string msg;
            while (true)
            {
                cout << "Enter#";
                std::getline(std::cin, msg);
                write(_sock, msg.c_str(), sizeof(msg));

                char buffer[NUM];
                int n = read(_sock, buffer, sizeof(buffer));
                if (n > 0)
                {
                    // 目前我们把读到的数据当成字符串, 截止目前
                    buffer[n] = 0;
                    std::cout << "Server回显# " << buffer << std::endl;
                }
                else
                    break;
            }
        }
    }

    ~tcpclient()
    {
        if (_sock >= 0)
            close(_sock);
    }

private:
    int _sock;
    std::string _serverip;
    uint16_t _serverport;
};

tcpcline.cc:执行程序 

#include "tcpclient.hpp"
#include <memory>

using namespace std;

static void Usage(string proc)
{
    cout << "\nUsage:\n\t" << proc << " serverip serverport\n\n";
}
// ./tcpclient serverip serverport
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    string serverip = argv[1];
    uint16_t serverport = atoi(argv[2]);

    unique_ptr<tcpclient> tcli(new tcpclient(serverip, serverport));
    tcli->initclient();
    tcli->start();
    return 0;
}

3、日志报告的编写 

对于日志报告我们通过对错误进行分类,进行不同等级的日志错误信息输入。

#pragma once

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

// 定义错误类型
#define DEBUG 0   // debug
#define NORMAL 1  // normal
#define WARNING 2 // warning
#define ERROR 3   // error
#define FATAL 4   // fatal致命

#define NUM 1024

// 定于不同类型日常写入的文件
#define LOG_NORMAL "log.txt"
#define LOG_ERR "log.error"
// 标志位
const char *to_levelstr(int level)
{
    switch (level)
    {
    case DEBUG:
        return "DEBUG";
    case NORMAL:
        return "NORMAL";
    case WARNING:
        return "NORMAL";
    case ERROR:
        return "ERROR";
    case FATAL:
        return "FATAL";
    }
}

// 日志信息
void logMessage(int level, const char *format, ...) // 可变参数函数
{
    // [日志等级] [时间戳/时间] [pid] [messge]
    // [WARNING] [2023-05-11 18:09:08] [123] [创建socket失败]
    char logprefix[NUM]; // 存放日志前缀
    snprintf(logprefix, sizeof(logprefix), "[%d][%ld][pid::%d]",
             to_levelstr(level), (long int)time(nullptr), getpid());

    char logcontent[NUM];
    va_list arg;
    snprintf(logprefix, sizeof(logprefix), format, arg);
    FILE *log = fopen(LOG_NORMAL, "a"); // log_normal
    FILE *err = fopen(LOG_ERR, "a");    // log_error

    if (log != nullptr && err != nullptr)
    {
        FILE *curr = nullptr;
        if (level == DEBUG || level == NORMAL || level == WARNING)
            curr = log;
        if (level == ERROR || level == FATAL)
            curr = err;
        if (curr)
            fprintf(curr, "%s%s\n", logprefix, logcontent);

        fclose(log);
        fclose(err);
    }
}

程序运行测试:

 

 四、TCP协议通讯流程

1、通信流程

TCP协议的通讯流程可以分为以下几个步骤:

  1. 创建连接:客户端和服务端都需要创建一个套接字(socket),这通常涉及到系统调用如`socket()`。

  2. 连接请求:客户端向服务端的套接字发送连接请求报文,这一步可以通过`connect()`函数来实现。

  3. 服务器响应:服务器收到客户端的连接请求后,需要进行确认,以确定是否允许连接。这通常涉及三部分握手:

    • 服务器的确认ACK
    • 客户端的确认SYN
    • 服务器的应答ACK
  4. 连接建立:在双方完成了三次握手后,连接正式建立。此时,客户端可以开始发送数据给服务器,而服务器也可以开始接收数据。

  5. 数据传输:在这个阶段,客户端和服务器端可以进行数据的读写操作。这些操作包括读取(`recv()`)、写入(`send()`)以及关闭连接(`close()`)。

  6. 断开连接:当通信结束后,客户端或服务器端可以选择断开当前的连接。这通常涉及到四次挥手:

    • 服务器的释放FIN
    • 客户端的确认ACK
    • 服务器的确认ACK
    • 客户端的释放ACK
  7. 错误处理:在通信过程中,可能会遇到各种错误,如数据包丢失或乱序。TCP提供了自动重传机制来处理这些问题。

2、三次握手和四次挥手 

TCP三次握手(Three-way Handshake):

  1. 客户端发送连接请求(SYN): 客户端向服务器发送一个特殊的TCP报文段,该报文段中设置了SYN(同步)标志位,表明客户端要求建立连接,并指明初始序列号(ISN)

  2. 服务器确认请求并发送自己的连接请求(SYN + ACK): 服务器接收到客户端的连接请求后,向客户端发送一个确认报文段。该报文段中既包含了确认序号(ACK),也设置了SYN标志位,表示服务器同意建立连接,并指明自己的初始序列号。

  3. 客户端确认服务器的连接请求(ACK): 客户端接收到服务器的确认报文后,会再次向服务器发送一个确认报文段,其中确认号字段会加1,表示客户端也同意建立连接。

 TCP四次挥手(Four-way Handshake)

  1. 发起关闭请求: 通信的一方(称为主动关闭方)发送一个FIN报文段,表示它已经完成了数据的发送任务。

  2. 接收关闭请求并发送确认: 接收到关闭请求的一方(被动关闭方)收到FIN报文后,会发送一个确认报文,表明它已经接收到关闭请求。

  3. 发送数据并发起关闭请求: 被动关闭方发送完所有数据后,也会向主动关闭方发送一个FIN报文段,表示它已经完成了数据的发送任务,并且准备关闭连接。

  4. 确认关闭请求并发送关闭确认: 主动关闭方接收到被动关闭方的FIN报文后,会发送一个确认报文,表明它已经接收到了关闭请求。一旦被动关闭方收到了这个确认,连接就会关闭。

在linux下有许多命令,老是忘记,在下面的文章中多会为大家分享一些小命令: 

Linux小命令 :

netstat 是一个用于显示网络状态信息的命令行工具,常用于查看网络连接、路由表、接口统计等信息。在给定的命令中,-nltp 参数的含义如下:

  • -n: 显示数字形式的地址和端口号,而不进行域名和服务名称的解析。
  • -l: 仅显示监听状态的连接。
  • -t: 仅显示TCP协议的连接。
  • -p: 显示与连接相关的进程标识符(PID)及进程名称。

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

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

相关文章

数仓建模—数据网格

数据网格 随着数字化时代的到来,近几年数据领域的新技术概念不断涌现,无论是数据湖、湖仓一体、流批一体、存算一体、数据编织抑或数据网格,很多还爬上了Gartner曲线,其中数据网格备受关注,数据网格从字面意思来看挺抽象的,会劝退很多人,但当你深入去理解这个概念时,才…

数据存储以及内存

数据在内存中的存储是因不同的类型而不同的。 但首先我们需要知道的是&#xff0c;在C语言中&#xff0c;数据在内存中的存储是以变量的形式存储的。每个变量都有一个地址&#xff0c;指向内存中的特定位置。变量的值存储在这个地址对应的内存单元中。不同类型的变量在内存中占…

react【三】受控组件/高阶组件/portals/fragment/严格模式/动画

文章目录 1、受控组件1.1 认识受控组件1.2 checkout1.3 selected1.4 非受控组件 2、高阶组件2.1 认识高阶组件2.2 应用1-props增强的基本使用2.3 对象增强的应用场景-context共享2.4 应用2-鉴权2.5 应用3 – 生命周期劫持2.6、高阶组件的意义 3、Portals4、fragment5、StrictMo…

【无标题】Matlab之annotation函数——创建图形注释(箭头、椭圆、矩形)

应用1&#xff1a;创建文本箭头注释 创建一个简单线图并向图窗添加文本箭头。用归一化的图窗坐标指定文本箭头位置&#xff0c;起点为 (0.3,0.6)&#xff0c;终点为 (0.5,0.5)。通过设置 String 属性指定文本说明。 figure plot(1:10) x [0.3 0.5]; y [0.6 0.5]; annotation…

Linux网络基础1

目录 计算机网络背景协议OSI七层模型TCP/IP五层&#xff08;四层&#xff09;模型网络传输基本流程以太网通信原理IP地址理解 计算机网络背景 到目前为止&#xff0c;我们之前所有的编程都是单机的&#xff0c;不是多机互联。以前计算机被发明的时候是为了军事用途&#xff0…

【C语言】常见字符串函数的功能与模拟实现

目录 1.strlen() 模拟实现strlen() 2.strcpy() 模拟实现strcpy() 3.strcat() 模拟实现strcat() 4.strcmp() 模拟实现strcmp() 5.strncpy() 模拟实现strncpy() 6.strncat() 模拟实现strncat() 7.strncmp() 模拟实现strncmp() 8.strstr() 模拟实现strstr() 9.str…

Midjourney绘图欣赏系列(一)

Midjourney介绍 Midjourney 是生成式人工智能的一个很好的例子&#xff0c;它根据文本提示创建图像。它与 Dall-E 和 Stable Diffusion 一起成为最流行的 AI 艺术创作工具之一。与竞争对手不同&#xff0c;Midjourney 是自筹资金且闭源的&#xff0c;因此确切了解其幕后内容尚不…

Vulhub kali 环境安装教程

进入 root 权限 sudo su 更新软件 apt-get update 安装 HTTPS 协议和 CA 证书 apt-get install -y apt-transport-https ca-certificates 安装 docker apt install docker.io 查看 docker 是否安装完成 docker -v 安装 pip apt-get install python3-pip 安装 docker-compose do…

【python之美】减少人工成本之批量去除首行_3

批量去除首行内容 怎么创建多个文本并写入多行数据呢 上特产&#xff1a; import ospath "C:\\Users\\Administrator\\Desktop\\text_1\\" numb int(input("需要创建几个文件&#xff1a;")) a 1 for i in range(numb):f open(path text _ str(a)…

jmeter-问题四:json断言时,预期结果那里如何不写成固定值?

文章目录 json断言时&#xff0c;预期结果那里如何不写成固定值&#xff1f;定义用户参数&#xff0c;然后在json断言的expected value处引用使用csv数据&#xff0c;然后在json断言的expected value处引用 json断言时&#xff0c;预期结果那里如何不写成固定值&#xff1f; 定…

测试开发-2-概念篇

文章目录 衡量软件测试结果的依据—需求1.需求的概念2.从软件测试人员角度看需求3.为什么需求对软件测试人员如此重要4.如何才可以深入理解被测试软件的需求5.测试用例的概念6.软件错误&#xff08;BUG&#xff09;的概念7.开发模型和测试模型8.软件的生命周期9.瀑布模型&#…

Spring 用法学习总结(三)之 AOP

Spring学习 7 bean的生命周期8 AOP面向切面编程8.1 AOP相关术语8.2 AOP使用 7 bean的生命周期 bean的生命周期主要为bean实例化、bean属性赋值、bean初始化、销毁bean&#xff0c;其中在实例化和初始化前后都使用后置处理器方法&#xff0c;而InstantiationAwareBeanPostProce…

Linux之多线程

目录 一、进程与线程 1.1 进程的概念 1.2 线程的概念 1.3 线程的优点 1.4 线程的缺点 1.5 线程异常 1.6 线程用途 二、线程控制 2.1 POSIX线程库 2.2 创建一个新的线程 2.3 线程ID及进程地址空间布局 2.4 线程终止 2.5 线程等待 2.6 线程分离 一、进程与线程 在…

HDFS的超级用户

一. 解释原因 HDFS(Hadoop Distributed File System)和linux文件系统管理一样&#xff0c;也是存在权限控制的。 但是很不一样的是&#xff0c; 在Linux文件系统中&#xff0c;超级用户Superuser是root而在HDFS中&#xff0c;超级用户Superuser是启动了namenode的用户&#x…

4核16g配置咋样?还不错,阿里云4核16G服务器26元1个月

4核16g配置咋样&#xff1f;还不错&#xff0c;阿里云4核16G服务器26元1个月&#xff0c;阿里云4核16G服务器配置优惠价格ECS云服务器经济型e实例26元1个月、149元半年、79元3个月&#xff0c;4核16G通用算力u1服务器、通用型g7、通用型g8i、AMD通用型g8a、性能增强通用型g8ae、…

html从零开始6:关系选择器、css盒子模型、弹性盒子模型【搬代码】

关系选择器 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevice-width,…

Linux日志管理服务 rsyslogd

文章目录 1. 基本介绍2. 系统常用的日志3. 日志管理服务 rsyslogd 1. 基本介绍 日志文件是重要的系统信息文件&#xff0c;其中记录了许多重要的系统事件&#xff0c;包括用户的登录信息、系统的启动信息、系统的安全信息、邮件相关信息、各种服务相关信息等。日志对于安全来说…

QQ群微信群强制加群/随机小姐姐视频分享打赏裂变PHP源码

很多人再找qq群或者微信分享小姐姐视频打赏的裂变源码&#xff0c;这次就分享出来&#xff01; 下载地址QQ群微信群强制加群.zip官方版下载丨最新版下载丨绿色版下载丨APP下载-123云盘

中国电子学会2023年12月份青少年软件编程Scratch图形化等级考试试卷二级真题(含答案)

2023-12 Scratch二级真题 分数&#xff1a;100 题数&#xff1a;37 测试时长&#xff1a;60min 一、单选题(共25题&#xff0c;共50分) 1.在制作推箱子游戏时&#xff0c;地图是用数字形式储存在电脑里的&#xff0c;下图是一个推箱子地图&#xff0c;地图表示如下&#x…

QGIS教程 加载shape数据 矢量数据(批量)

一. 前言 本篇文章主要介绍QGIS的使用&#xff0c;包括如何加载矢量shape数据、查看数据属性、如何加载txt属性数据、怎么用脚本批量加载矢量数据等内容。 如果想了解QGIS&#xff0c;可以参考博文&#xff1a; QGIS基本介绍 如果想了解shape数据格式详情&#xff0c;可以参考…