【网络编程】利用套接字实现一个简单的网络通信(UDP实现聊天室 附上源码)

news2024/11/23 3:01:30

00

网络编程套接字

  • 🐛预备知识
    • 🦋理解源IP地址和目的IP地址
    • 🐌认识端口号
    • 🐞 理解 "端口号" 和 "进程ID"
    • 🐜简单认识TCP协议
    • 🦟简单认识UDP协议
    • 🦗 什么是网络字节序
  • 🕷相关函数端口介绍
    • 🕸 socket相关API介绍
    • 🦂sockaddr结构
    • 🐢 sockaddr_in结构
  • 🐍 简单的UDP网络程序
    • 🦎log.hpp 日志文件
    • 🦖 udpClient.cc 客户端
    • 🦕 udpServer.cc 服务器
    • 🐙 makefile文件
    • 运行:

🐛预备知识

🦋理解源IP地址和目的IP地址

源IP地址(Source IP Address):

源IP地址是数据包发送方(或数据流出发点)的唯一标识符。它用于在互联网或本地网络中定位发送数据包的设备或主机。源IP地址是数据包的出发点,即数据从这个地址开始传送,向目的IP地址指示的设备发送。
在TCP/IP协议中,源IP地址通常由发送方的操作系统或网络栈分配,并在数据包的IP首部中进行标记。

目的IP地址(Destination IP Address):

目的IP地址是数据包的接收方(或数据流的目标点)的唯一标识符。它用于在互联网或本地网络中定位接收数据包的设备或主机。目的IP地址是数据包的终点,即数据传输的目标地址,数据包应该传输到这个地址。
在TCP/IP协议中,目的IP地址通常由应用程序或网络栈设置,并在数据包的IP首部中进行标记。
这两个地址在数据包传输过程中起着非常重要的作用,确保数据从源设备正确地传递到目标设备,实现网络通信。IP地址是一个32位的二进制数,通常用点分十进制表示(例如,192.168.0.1),其中前24位表示网络地址,后8位表示主机地址。

🐌认识端口号

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

🐞 理解 “端口号” 和 “进程ID”

  • 端口号(Port Number)和进程ID(Process ID)是在计算机网络和操作系统中用于不同目的的标识符。

端口号:

端口号是在计算机网络中用于标识特定进程或服务的数字。 它是一个16位的无符号整数,取值范围是0到65535。
在TCP/IP网络中,每个传输控制协议(TCP)和用户数据报协议(UDP)的通信端点都与一个端口号相关联。 这样,计算机上的不同进程或服务可以通过不同的端口号进行通信,从而实现多个应用程序的并发运行。
例如,HTTP服务通常使用端口号80,HTTPS使用端口号443,SMTP使用端口号25等。

进程ID:

进程ID是操作系统中用于标识运行中进程(或线程)的唯一标识符。 它是一个非负整数,通常由操作系统在进程创建时分配。
在多任务操作系统中,每个运行中的进程都有一个唯一的进程ID,操作系统通过进程ID来跟踪和管理不同的进程。
进程ID的范围通常由操作系统定义,可以是一个较小的数值范围,也可以是一个较大的范围。

区别:

端口号是在计算机网络中用于标识不同进程或服务的通信端点,用于实现多个应用程序的并发运行。 进程ID是操作系统中用于标识运行中进程的唯一标识符,用于跟踪和管理不同的进程。
端口号是在网络通信中使用的,而进程ID是在操作系统层级使用的。
端口号是一个16位的数字,进程ID是一个非负整数。

🐜简单认识TCP协议

传输层协议
有连接
可靠传输
面向字节流

🦟简单认识UDP协议

传输层协议
无连接
不可靠传输
面向数据报

🦗 什么是网络字节序

网络字节序是一种在计算机网络中使用的固定字节顺序,用于在不同计算机体系结构和操作系统之间传递数据。 在计算机内部,不同的体系结构(例如x86、ARM、SPARC等)和操作系统(例如Windows、Linux、iOS等)可能使用不同的字节顺序,这可能导致在网络通信中出现问题。

为了解决这个问题,网络通信中使用了统一的字节顺序,即网络字节序。 网络字节序采用大端字节序(Big-Endian)表示法,其中较高的字节位于较低的内存地址上,较低的字节位于较高的内存地址上。

举例说明:

假设一个16位整数0x1234(十进制为4660)在内存中存储为两个字节:0x12和0x34。

在大端字节序中,较高的字节(0x12)存储在较低的内存地址,较低的字节(0x34)存储在较高的内存地址。 即内存地址由高到低,数据依次为0x12 0x34。
在小端字节序中,较低的字节(0x34)存储在较低的内存地址,较高的字节(0x12)存储在较高的内存地址。 即内存地址由高到低,数据依次为0x34 0x12。

在网络通信中,发送方将数据转换为网络字节序后发送,接收方收到数据后将其转换为本地字节序进行处理,以保证在不同计算机体系结构和操作系统之间正确地传递数据。 常用的网络编程库(例如Socket编程)通常会自动处理字节序的转换。

🕷相关函数端口介绍

🕸 socket相关API介绍

  • man手册
    在这里插入图片描述

Socket(套接字)是一种用于网络通信的编程接口,它允许计算机之间通过网络传输数据。 在Socket编程中,我们可以使用一组API函数来创建、绑定、连接、发送和接收数据等操作。

  • 以下是常用的Socket API函数及其参数:
  1. socket()函数:
  • 描述:创建一个新的套接字。
  • 参数:int socket(int domain, int type, int protocol)
  • domain:套接字的地址族,常用的有AF_INET(IPv4)和AF_INET6(IPv6)。
  • type:套接字的类型,常用的有SOCK_STREAM(流式套接字,用于TCP)和SOCK_DGRAM(数据报套接字,用于UDP)。
  • protocol:使用的协议,通常为0(自动选择与type相关的默认协议)。
  1. bind()函数:
  • 描述:将套接字绑定到一个特定的地址和端口。
  • 参数:int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
  • sockfd:套接字描述符。
  • addr:指向要绑定的地址结构的指针,通常是struct sockaddr_in(IPv4)或struct sockaddr_in6(IPv6)。
  • addrlen:地址结构的长度。
  1. connect()函数:
  • 描述:建立与远程服务器的连接。
  • 参数:int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
  • sockfd:套接字描述符。
  • addr:指向目标服务器地址的指针,通常是struct sockaddr_in(IPv4)或struct sockaddr_in6(IPv6)。
  • addrlen:地址结构的长度。
  1. listen()函数:
  • 描述:将套接字设置为监听模式,准备接受连接请求。
  • 参数:int listen(int sockfd, int backlog)
  • sockfd:套接字描述符。
  • backlog:请求队列的最大长度,即等待接受连接的连接数。
  1. accept()函数:
  • 描述:接受连接请求,创建一个新的套接字用于与客户端通信。
  • 参数:int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
  • sockfd:监听套接字描述符。
  • addr:指向客户端地址的指针,用于存储客户端的信息。
  • addrlen:地址结构的长度。
  1. send()函数:
  • 描述:发送数据。
  • 参数:ssize_t send(int sockfd, const void *buf, size_t len, int flags)
  • sockfd:套接字描述符。
  • buf:要发送的数据缓冲区。
  • len:要发送的数据长度。
  • flags:发送标志,通常为0。
  1. recv()函数:
  • 描述:接收数据。
  • 参数:ssize_t recv(int sockfd, void *buf, size_t len, int flags)
  • sockfd:套接字描述符。
  • buf:接收数据的缓冲区。
  • len:要接收的数据长度。
  • flags:接收标志,通常为0。
  1. close()函数:
  • 描述:关闭套接字。
  • 参数:int close(int sockfd)
  • sockfd:套接字描述符。

使用Socket API时,通常的流程是创建一个套接字、绑定到一个地址和端口(可选)、监听连接请求(可选)、接受连接请求、发送和接收数据,最后关闭套接字。 服务器端和客户端使用不同的套接字操作,服务器端用于监听连接请求和处理客户端请求,而客户端用于建立连接和发送请求给服务器端。

🦂sockaddr结构

sockaddr 是一个通用的套接字地址结构,在Socket编程中经常用于存储网络地址信息。由于不同的协议族(IPv4、IPv6等)具有不同的地址结构,因此 sockaddr 用作地址结构的基类,而实际使用时通常会使用具体的子结构 sockaddr_in(IPv4)或 sockaddr_in6(IPv6)。

struct sockaddr {
    sa_family_t sa_family;      // 地址族,通常为 AF_INET 或 AF_INET6
    char sa_data[14];           // 存放地址信息的缓冲区,不同协议族具有不同的结构
};

在实际使用时,通常会将 sockaddr 结构转换为适合当前协议族的具体地址结构。例如,在IPv4协议中,使用 sockaddr_in 结构,其定义如下:

struct sockaddr_in {
    sa_family_t sin_family;     // 地址族,固定为 AF_INET
    in_port_t sin_port;         // 16位端口号,使用网络字节序
    struct in_addr sin_addr;    // 32位IPv4地址,使用网络字节序
    char sin_zero[8];           // 不使用,填充字段
};

在IPv6协议中,使用 sockaddr_in6 结构,其定义如下:

struct sockaddr_in6 {
    sa_family_t sin6_family;     // 地址族,固定为 AF_INET6
    in_port_t sin6_port;         // 16位端口号,使用网络字节序
    uint32_t sin6_flowinfo;      // 流标识,通常为0
    struct in6_addr sin6_addr;   // 128位IPv6地址,使用网络字节序
    uint32_t sin6_scope_id;      // 接口范围标识
};

在使用 结构时,通常需要进行类型转换,将其转换为适用于当前协议族的地址结构,并根据需要填充具体的地址信息,然后传递给套接字相关的函数使用。

🐢 sockaddr_in结构

sockaddr_in 是用于存储IPv4地址的套接字地址结构,是在网络编程中非常常用的结构。它用于在IPv4协议族中表示网络地址和端口号。下面是 sockaddr_in 结构的定义:

struct sockaddr_in {
    sa_family_t sin_family;     // 地址族,固定为 AF_INET
    in_port_t sin_port;         // 16位端口号,使用网络字节序
    struct in_addr sin_addr;    // 32位IPv4地址,使用网络字节序
    char sin_zero[8];           // 不使用,填充字段
};

其中,各字段的含义如下:

sin_family:地址族,固定为 AF_INET,表示IPv4地址族。
sin_port:16位端口号,使用网络字节序,需要使用 htons 函数进行字节序转换。
sin_addr:32位IPv4地址,使用网络字节序,需要使用 inet_pton 函数将点分十进制形式的IPv4地址转换为网络字节序。
sin_zero:不使用,填充字段,通常设置为0。
使用 结构时,通常先将IPv4地址和端口号填充到该结构中,然后将其转换为通用的 sockaddr_insockaddr 结构,在套接字相关的函数中使用。

例如,在服务器端绑定一个IPv4地址和端口号,可以这样做:

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

int main() {
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) {
        perror("socket");
        return -1;
    }

    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8080); // 将端口号转换为网络字节序
    inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr); // 将IPv4地址转换为网络字节序
    memset(server_addr.sin_zero, 0, sizeof(server_addr.sin_zero));

    if (bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
        perror("bind");
        close(sockfd);
        return -1;
    }

    // 其他操作...
    close(sockfd);
    return 0;
}

注意,在使用 sockaddr_in 结构时,需要包含 <netinet/in.h> 头文件,并且要进行网络字节序的转换。

🐍 简单的UDP网络程序

🦎log.hpp 日志文件

#pragma once

#include <cstdio>
#include <ctime>
#include <cstdarg>
#include <cassert>
#include <cstring>
#include <cerrno>
#include <stdlib.h>

#define DEBUG 0
#define NOTICE 1
#define WARINING 2
#define FATAL 3

const char *log_level[]={"DEBUG", "NOTICE", "WARINING", "FATAL"};

// logMessage(DEBUG, "%d", 10);
void logMessage(int level, const char *format, ...)
{
    assert(level >= DEBUG);
    assert(level <= FATAL);

    char *name = getenv("USER");

    char logInfo[1024];
    va_list ap; // ap -> char*
    va_start(ap, format);

    vsnprintf(logInfo, sizeof(logInfo)-1, format, ap);

    va_end(ap); // ap = NULL


    FILE *out = (level == FATAL) ? stderr:stdout;

    fprintf(out, "%s | %u | %s | %s\n", \
        log_level[level], \
        (unsigned int)time(nullptr),\
        name == nullptr ? "unknow":name,\
        logInfo);

    // char *s = format;
    // while(s){
    //     case '%':
    //         if(*(s+1) == 'd')  int x = va_arg(ap, int);
    //     break;
    // }
}

🦖 udpClient.cc 客户端

#include <iostream>
#include <string>
#include <cstdlib>
#include <cassert>
#include <unistd.h>
#include <strings.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <pthread.h>

struct sockaddr_in server;

static void Usage(std::string name)
{
    std::cout << "Usage:\n\t" << name << " server_ip server_port" << std::endl;
}

void *recverAndPrint(void *args)
{
    while (true)
    {
        int sockfd = *(int *)args;
        char buffer[1024];
        struct sockaddr_in temp;
        socklen_t len = sizeof(temp);
        ssize_t s = recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr *)&temp, &len);
        if (s > 0)
        {
            buffer[s] = 0;
            std::cout << "server echo# " << buffer << std::endl;
        }
    }
}

// ./udpClient server_ip server_port
// 如果一个客户端要连接server必须知道server对应的ip和port
int main(int argc, char *argv[])
{
    if (argc != 3)
    {
        Usage(argv[0]);
        exit(1);
    }
    // 1. 根据命令行,设置要访问的服务器IP
    std::string server_ip = argv[1];
    uint16_t server_port = atoi(argv[2]);

    // 2. 创建客户端
    // 2.1 创建socket
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    assert(sockfd > 0);

    // 2.2 client 需不需要bind??? 需要bind,但是不需要用户自己bind,而是os自动给你bind
    // 所谓的"不需要",指的是: 不需要用户自己bind端口信息!因为OS会自动给你绑定,你也最好这么做!
    // 如果我非要自己bind呢?可以!严重不推荐!
    // 所有的客户端软件 <-> 服务器 通信的时候,必须得有 client[ip:port] <-> server[ip:port]
    // 为什么呢??client很多,不能给客户端bind指定的port,port可能被别的client使用了,你的client就无法启动了
    // 那么server凭什么要bind呢??server提供的服务,必须被所有人知道!server不能随便改变!
    // 2.2 填写服务器对应的信息

    bzero(&server, sizeof server);
    server.sin_family = AF_INET;
    server.sin_port = htons(server_port);
    server.sin_addr.s_addr = inet_addr(server_ip.c_str());

    pthread_t t;
    pthread_create(&t, nullptr, recverAndPrint, (void *)&sockfd);
    // 3. 通讯过程
    std::string buffer;
    while (true)
    {
        std::cerr << "Please Enter# ";
        std::getline(std::cin, buffer);
        // 发送消息给server
        sendto(sockfd, buffer.c_str(), buffer.size(), 0,
               (const struct sockaddr *)&server, sizeof(server)); // 首次调用sendto函数的时候,我们的client会自动bind自己的ip和port
    }

    close(sockfd);

    return 0;
}

🦕 udpServer.cc 服务器

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

#include "log.hpp"
static void Usage(const std::string porc)
{
    std::cout << "Usage:\n\t" << porc << " port [ip]" << std::endl;
}

class udpServer
{
    public:
    udpServer(int port, std::string ip = "") : port_((uint16_t)port), ip_(ip), sockfd_(-1)
    {
    }
    ~udpServer()
    {
    }

    void init()//初始化函数
    {
        //1.创建套接字
        sockfd_=socket(AF_INET,SOCK_DGRAM,0);
        //  参数1:套接字的协议族     `AF_INET`: IPv4协议族,用于Internet地址。
        //参数2:套接字类型    `SOCK_DGRAM`: 面向消息的套接字,用于不可靠的、固定长度的数据传输(如UDP)。
        //参数3:套接字的协议    0,让系统自动选择合适的协议。
        if(sockfd_<0) //创建失败 打印日志信息
        {
              logMessage(FATAL, "socket:%s:%d", strerror(errno), sockfd_);
            exit(1);
        }

        //到这说明 创建成功
         logMessage(DEBUG, "socket create success: %d", sockfd_);
         struct sockaddr_in local;  // local在哪里开辟的空间? 用户栈 -> 临时变量 -> 写入内核中
            bzero(&local, sizeof(local)); // memset
        // 填充协议家族,域
        local.sin_family = AF_INET;
        // 填充服务器对应的端口号信息,一定是会发给对方的,port_一定会到网络中
        local.sin_port = htons(port_);
        // 服务器都必须具有IP地址,"xx.yy.zz.aaa",字符串风格点分十进制 -> 4字节IP -> uint32_t ip
        // INADDR_ANY(0): 程序员不关心会bind到哪一个ip, 任意地址bind,强烈推荐的做法,所有服务器一般的做法
        // inet_addr: 指定填充确定的IP,特殊用途,或者测试时使用,除了做转化,还会自动给我们进行 h—>n
        local.sin_addr.s_addr = ip_.empty() ? htonl(INADDR_ANY) : inet_addr(ip_.c_str());
        // 2.2 bind 网络信息
        if (bind(sockfd_, (const struct sockaddr *)&local, sizeof(local)) == -1)
        {
            logMessage(FATAL, "bind: %s:%d", strerror(errno), sockfd_);
            exit(2);
        }
        logMessage(DEBUG, "socket bind success: %d", sockfd_);
        // done
    }

     void start()
    {
        // 服务器设计的时候,服务器都是死循环
        char inbuffer[1024];  //将来读取到的数据,都放在这里
        char outbuffer[1024]; //将来发送的数据,都放在这里
        while (true)
        {
            struct sockaddr_in peer;      //输出型参数
            socklen_t len = sizeof(peer); //输入输出型参数

            // demo2
            //  UDP无连接的
            //  对方给你发了消息,你想不想给对方回消息?要的!后面的两个参数是输出型参数
            ssize_t s = recvfrom(sockfd_, inbuffer, sizeof(inbuffer) - 1, 0,
                                 (struct sockaddr *)&peer, &len);
            if (s > 0)
            {
                inbuffer[s] = 0; //当做字符串
            }
            else if (s == -1)
            {
                logMessage(WARINING, "recvfrom: %s:%d", strerror(errno), sockfd_);
                continue;
            }
            // 读取成功的,除了读取到对方的数据,你还要读取到对方的网络地址[ip:port]
            std::string peerIp = inet_ntoa(peer.sin_addr);       //拿到了对方的IP
            uint32_t peerPort = ntohs(peer.sin_port); // 拿到了对方的port

            checkOnlineUser(peerIp, peerPort, peer); //如果存在,什么都不做,如果不存在,就添加

            // 打印出来客户端给服务器发送过来的消息
            logMessage(NOTICE, "[%s:%d]# %s", peerIp.c_str(), peerPort, inbuffer);

            // for(int i = 0; i < strlen(inbuffer); i++)
            // {
            //     if(isalpha(inbuffer[i]) && islower(inbuffer[i])) outbuffer[i] = toupper(inbuffer[i]);
            //     else outbuffer[i] = toupper(inbuffer[i]);
            // }
            messageRoute(peerIp, peerPort,inbuffer); //消息路由

            // 线程池!

            // sendto(sockfd_, outbuffer, strlen(outbuffer), 0, (struct sockaddr*)&peer, len);

            // demo1
            // logMessage(NOTICE, "server 提供 service 中....");
            // sleep(1);
        }
    }

    void checkOnlineUser(std::string &ip, uint32_t port, struct sockaddr_in &peer)
    {
        std::string key = ip;
        key += ":";
        key += std::to_string(port);
        auto iter = users.find(key);
        if(iter == users.end())
        {
            users.insert({key, peer});
        }
        else
        {
            // iter->first, iter->second->
            // do nothing
        }
    }

    void messageRoute(std::string ip, uint32_t port, std::string info)
    {

        std::string message = "[";
        message += ip;
        message += ":";
        message += std::to_string(port);
        message += "]# ";
        message += info;

        for(auto &user : users)
        {
            sendto(sockfd_, message.c_str(), message.size(), 0, (struct sockaddr*)&(user.second), sizeof(user.second));
        }
    }


    private:
    // 服务器必须得有端口号信息
    uint16_t port_;
    // 服务器必须得有ip地址
    std::string ip_;
    // 服务器的socket fd信息
    int sockfd_;
    // onlineuser
    std::unordered_map<std::string, struct sockaddr_in> users;

};
int main(int argc, char *argv[])
{
    if (argc != 2 && argc != 3) //反面:argc == 2 || argc == 3
    {
        Usage(argv[0]);
        exit(3);
    }
    uint16_t port = atoi(argv[1]);
    std::string ip;
    if (argc == 3)
    {
        ip = argv[2];
    }

    udpServer svr(port, ip);
    svr.init();
    svr.start();

    return 0;
}

🐙 makefile文件

.PHONY:all
all:udpClient udpServer

udpClient: UCPClient.cc
	g++ -o $@ $^ -std=c++11 -lpthread
udpServer:UDPServer.cc
	g++ -o $@ $^ -std=c++11

.PHONY:clean
clean:
	rm -f udpClient udpServer

运行:

000
设置对对应的端口号就行

🦑 🦐 🦞 🦀 🐡 🐠 🐟 🐬 🐳 🐋 🦈 🐊 🐅 🐆 🦓 🦍 🦧 🦣 🐘 🦛

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

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

相关文章

MySQL的关键指标及采集方法

MySQL 是个服务&#xff0c;所以我们可以借用 Google 四个黄金指标的思路来解决问题。 1、延迟 应用程序会向 MySQL 发起 SELECT、UPDATE 等操作&#xff0c;处理这些请求花费了多久&#xff0c;是非常关键的&#xff0c;甚至我们还想知道具体是哪个 SQL 最慢&#xff0c;这样…

【Linux】进程间通信——System V信号量

目录 写在前面的话 一些概念的理解 信号量的引入 信号量的概念及使用 写在前面的话 System V信号量是一种较低级的IPC机制&#xff0c;使用的时候需要手动进行操作和同步。在现代操作系统中&#xff0c;更常用的是POSIX信号量&#xff08;通过sem_*系列的函数进行操作&…

linux系统虚拟主机开启支持Swoole Loader扩展

特别说明&#xff1a;只是安装支持Swoole扩展&#xff0c;主机并没有安装服务端。目前支持版本php5.4-php7.2。 1、登陆主机控制面板&#xff0c;找到【远程文件下载】这个功能。 2、远程下载文件填写http://download.myhostadmin.net/vps/SwooleLoader_linux.zip 下载保存的路…

SpringBoot 升级内嵌Tomcat

SpringBoot 更新 Tomcat 最近公司的一个老项目需要升级下Tomcat&#xff0c;由于这个项目我完全没有参与&#xff0c;所以一开始我以为是一个老的Tomcat项目&#xff0c;升级它的Tomcat依赖或者是Tomcat容器镜像&#xff0c;后面发现是一个SpringBoot项目&#xff0c;升级的是…

加速中产 “返贫” 的4个迹象

没有消息&#xff0c;就是好消息。这话放在现在的朋友圈子里&#xff0c;似乎很合适。最近接到两个朋友的电话&#xff0c;一个是朋友的诉苦电话&#xff0c;这位朋友曾是某大厂的高管&#xff0c;被裁后失业近1年&#xff0c;虽然当初赔了N1&#xff0c;但架不住这位朋友“房贷…

Maven 打包生成Windows和Liunx启动文件

新建一个springboot项目。 1、项目结构 2、pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocati…

ASP.NET Core MVC -- 将视图添加到 ASP.NET Core MVC 应用

Index页 右键单击“视图”文件夹&#xff0c;然后单击“添加”>>“新文件夹”&#xff0c;并将文件夹命名为“HelloWorld”。 右键单击“Views/HelloWorld”文件夹&#xff0c;然后单击“添加”>“新项”。 在“添加新项 - MvcMovie”对话框中&#xff1a; 在右上…

Misc取证学习

文章目录 Misc取证学习磁盘取证工具veracryto挂载fat文件DiskGenius 磁盘取证例题[RCTF2019]disk 磁盘[](https://ciphersaw.me/ctf-wiki/misc/disk-memory/introduction/#_2)内存取证工具volatility 内存取证例题数字取证赛题0x01.从内存中获取到用户admin的密码并且破解密码 …

gin和gorm框架安装

理论上只要这两句命令 go get -u gorm.io/gorm go get -u github.com/gin-gonic/gin然而却出现了问题 貌似是代理问题&#xff0c;加上一条命令 go env -w GOPROXYhttps://goproxy.cn,direct 可以成功安装 安装gorm的数据库驱动程序 go get -u gorm.io/driver/mysql

香港人力资源服务商迦里仕人才,申请纳斯达克IPO上市

来源&#xff1a;猛兽财经 作者&#xff1a;猛兽财经 猛兽财经获悉&#xff0c;总部位于香港的人力资源服务商迦里仕人才&#xff08;Galaxy Payroll Group&#xff09;近期已向美国证券交易委员会&#xff08;SEC&#xff09;提交招股书&#xff0c;申请在纳斯达克IPO上市&am…

VUE框架:vue2转vue3全面细节总结(4)滚动行为

大家好&#xff0c;我是csdn的博主&#xff1a;lqj_本人 这是我的个人博客主页&#xff1a; lqj_本人_python人工智能视觉&#xff08;opencv&#xff09;从入门到实战,前端,微信小程序-CSDN博客 最新的uniapp毕业设计专栏也放在下方了&#xff1a; https://blog.csdn.net/lbcy…

Redis可视化工具

Redis可视化工具 1、RedisInsight 下载地址&#xff1a;https://redis.com/redis-enterprise/redis-insight/ 双击软件进行安装&#xff0c;安装完后弹出如下界面&#xff1a; 安装完成后在主界面选择添加Redis数据库&#xff1b; 选择手动添加数据库&#xff0c;输入Redis…

GD32F103VE独立看门狗

GD32F103VE独立看门狗 看门狗定时器(WDGT)有两个&#xff1a; 1&#xff0c;独立看门狗定时器(FWDGT) 2&#xff0c;窗口看门狗定时器(WWDGT) 独立看门狗定时器(FWDGT)有一个独立的时钟源(IRC40K). 独立看门狗定时器有一个向下计数器,当计数器到达0&#xff0c;会让CPU复位。…

二叉树的前序遍历、中序遍历、后序遍历、层次遍历的实现

DLR–前序遍历&#xff08;根在前&#xff0c;从左往右&#xff0c;一棵树的根永远在左子树前面&#xff0c;左子树又永远在右子树前面 &#xff09; LDR–中序遍历&#xff08;根在中&#xff0c;从左往右&#xff0c;一棵树的左子树永远在根前面&#xff0c;根永远在右子树前…

从0到1开发go-tcp框架【4实战片— — 开发MMO之玩家聊天篇】

从0到1开发go-tcp框架【实战片— — 开发MMO】 MMO&#xff08;MassiveMultiplayerOnlineGame&#xff09;&#xff1a;大型多人在线游戏&#xff08;多人在线网游&#xff09; 1 AOI兴趣点的算法 游戏中的坐标模型&#xff1a; 场景相关数值计算 ● 场景大小&#xff1a; 250…

Vue2:组件基础(下)

Vue2&#xff1a;组件基础&#xff08;下&#xff09; Date: April 12, 2023 Sum: props验证、计算属性、自定义时间、组件上的v-model、任务列表案例 Tags: * 目标&#xff1a; 能够知道如何对 props 进行验证 能够知道如何使用计算属性 令能够知道如何为组件自定义事件 …

K8S系列文章 之 容器网络基础 Docker0

什么是Docker0 使用ip addr命令看一下网卡&#xff1a; rootKitDevVps:~# ip addr 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00inet 127.0.0.1/8 scope host…

火车头标题伪原创【php源码】

大家好&#xff0c;给大家分享一下python怎么读取文件中的数据&#xff0c;很多人还不知道这一点。下面详细解释一下。现在让我们来看看&#xff01; 火车头采集ai伪原创插件截图&#xff1a; python是一门非常火爆且相对易学的编程语言&#xff0c;应用在各种场景。许多人想学…

VS code:Task

Task 微软官方连接&#xff1a; https://code.visualstudio.com/docs/editor/tasks what is Task 我们知道&#xff0c;vscode可以支持许多编程语言&#xff0c;很多语言是需要进行编译的&#xff0c;打包&#xff0c;测试… 有许多已有的工具支持这些流程&#xff0c;例如A…

Nginx(1)

目录 1.Nginx概述2.Nginx的特点3.Nginx主要功能1.反向代理2.负载均衡 1.Nginx概述 Nginx (engine x) 是一个自由的、开源的、高性能的HTTP服务器和反向代理服务器&#xff0c;也是一个IMAP、POP3、SMTP代理服务器。 Nginx是一个强大的web服务器软件&#xff0c;用于处理高并发…