C++ TinyWebServer项目总结(5. Linux网络编程基础API)

news2024/11/15 6:45:44

还是给我的语雀文档打个广告: 《5. C++ TinyWebServer项目总结(5. Linux网络编程基础API)》我的文章都是先在语雀里记录的,然后再同步发送到CSDN上,有些格式问题实在是懒得改了,可能会导致大家看的不舒服,建议有需要的大家可以看看我的原文:

语雀icon-default.png?t=N7T8https://www.yuque.com/u39624144/zvaea9/xdkson4kiaas4lx6?singleDoc#%20%E3%80%8AWebServer%E3%80%8B

主机字节序和网络字节序

现代 CPU 的累加器一次都能装载(至少)4个字节(对于 32 位的机器),即一个 int 类型。那么这4个字节在内存中排列的顺序将影响它被累加器装载的整数值,这就是字节序问题。

字节存储顺序主要分为大端序(Big-endian)和小端序(Little-endian),区别如下

  • Big-endian:高位字节存入低地址,低位字节存入高地址
  • Little-endian:低位字节存入低地址,高位字节存入高地址

例如,将12345678h写入1000h开始的内存中,以大端序和小端序模式存放结果如下:

现代 PC 大多采用小端字节序,因此小端字节序又称为主机字节序;

而在两台不同字节序的主机之间传递数据时,发送端总是把要发送的数据转化成大端字节序数据后再发送,因此大端字节序也被称为网络字节序。

网络字节顺序是TCP / IP中规定好的一种数据表示格式,它与具体的CPU类型、操作系统等无关,从而可以保证数据在不同主机之间传输时能够被正确解释,网络字节顺序采用大端排序方式。

“大同小异 ”:大端字节序的 字节顺序 和 内存地址顺序相同;小端字节序则相反。

通用 socket 地址和专用 socket 地址

socket 网络编程接口中表示 socket 地址是结构体 sockaddr,其定义如下:

#include <bits/socket.h>

struct sockaddr{                           
    sa_family_t sa_family;
    char sa_data[14];
};

typedef unsigned short int sa_family_t;

sa_family成员是地址族类型(sa_family_t)的变量。地址族类型通常与协议类型对应。常见的协议族和对应的地址族如下所示:

sa_data成员用于存放socket地址值。但是,不同的协议族的地址值具有不同的含义和长度 :

可以看到,14个字节只能装下 IPv4地址,没办法装下IPv6的地址。因此,该结构体表示方式已经被废掉,Linux定义了下面这个新的通用的socket地址结构体,这个结构体不仅提供了足够大的空间用于存放地址值,而且是内存对齐的(内存对齐可以加快CPU访问速度)

#include <bits/socket.h>
struct sockaddr_storage
{
    sa_family_t sa_family;
    unsigned long int __ss_align; //不用管,用来作内存对齐的
    char __ss_padding[ 128 - sizeof(__ss_align) ];
};
typedef unsigned short int sa_family_t;

Linux 为各个协议族提供了专门的 socket 地址结构体。

UNIX 本地域协议族使用如下专用的 socket 地址结构体:


#include <sys/un.h>
struct sockaddr_un
{
    sa_family_t sin_family; /* 地址族: AF_UNIX */
    char sun_path[108];     /* 文件路径名 */
};

TCP / IP 协议族有 sockaddr_insockaddr_in6 两个专用的 socket 地址结构体,它们分别用于 IPv4 和 IPv6:

#include <netinet/in.h>
struct sockaddr_in
{
    sa_family_t sin_family;         /* 地址族: AF_INET */
    in_port_t sin_port;             /* 端口号: 用网络字节序表示 */
    struct in_addr sin_addr;        /* IPv4地址结构体 */
};

struct in_addr
{
    u_int32_t s_addr;				/* IPv4地址, 用网络字节序表示 */
};

struct sockaddr_in6
{
    sa_family_t sin6_family;		/* 地址族: AF_INET6 */
    in_port_t sin6_port; 			/* 端口号: 用网络字节序表示 */
    uint32_t sin6_flowinfo; 		/* IPv6 flow information */
    struct in6_addr sin6_addr; 		/* IPv6 地址结构体 */
    uint32_t sin6_scope_id; 		/* IPv6 scope-id */
};

struct in6_addr
{
    unsigned char sa_addr[16];		/* IPv6 地址, 用网络字节序表示 */
};

所有专用 socket 地址(以及 sockaddr_storage)类型的变量在实际使用时都需要转化为通用 socket 地址类型 sockaddr(强制转化即可),因为所有 socket 编程接口使用的地址参数类型都是 sockaddr。

IP 地址转换函数

人们习惯用可读性好的字符串来表示IP地址,比如用点分十进制字符串表示IPV4地址,以及用十六进制字符串表示IPv6地址,但编程中我们需要先把他们转化为整数(二进制)方能使用。而记录日志相反,我们需要把整数表示的IP地址转化为可读的字符串。

/*
    #include <arpa/inet.h>
    // p:点分十进制的IP字符串,n:表示network,网络字节序的整数
    int inet_pton(int af, const char *src, void *dst);
        af: 地址族: AF_INET  AF_INET6
        src: 需要转换的点分十进制的IP字符串
        dst: 转换后的结果保存在这个里面

    // 将网络字节序的整数,转换成点分十进制的IP地址字符串
    const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
        af: 地址族: AF_INET  AF_INET6
        src: 要转换的ip的整数的地址
        dst: 转换成IP地址字符串保存的地方
        size:第三个参数的大小(数组的大小)
        返回值:返回转换后的数据的地址(字符串),和 dst 是一样的

*/

#include <stdio.h>
#include <arpa/inet.h>

int main() {

    // 创建一个ip字符串, 点分十进制的IP地址字符串
    char buf[] = "192.168.1.0";
    unsigned int num = 0;

    // 将 点分十进制的IP字符串 转换成 网络字节序的整数
    inet_pton(AF_INET, buf, &num);
    unsigned char *p = (unsigned char *)&num;
    printf("%d %d %d %d\n", *p, *(p+1), *(p+2), *(p+3));


    // 将 网络字节序的IP整数 转换成 点分十进制的IP字符串
    char ip[16] = ""; //字符串 IP 地址四段,每段最多三个字节,加上3个“.”,再加一个字符串结束符
    const char * str =  inet_ntop(AF_INET, &num, ip, 16);
    printf("str : %s\n", str);
    printf("ip : %s\n", ip);
    printf("%d\n", ip == str);

    return 0;
}

一个 IPv4 地址,如果用字符串数组来存储,最多需要多大的字符串数组?

  • IPv4 地址格式:
    • IPv4 地址是由四个八位字节(每个0-255之间)组成,每个字节以 . 分隔。
    • 一个完整的 IPv4 地址的格式示例为 255.255.255.255,这个是最大可能的字符串长度。
  • 字符数计算:
    • 每个字节(0-255)可以用 1 到 3 个字符表示(如 0, 10, 255)。
    • 四个字节之间有三个 . 分隔符。
    • 因此,最大长度的 IPv4 地址字符串是 xxx.xxx.xxx.xxx,总共 15 个字符。
  • 字符串结束符:
    • C 语言中的字符串以 \0 结束符结尾,所以需要额外的 1 个字节空间来存储这个结束符。

因此,字符串数组大小应该为 16 ,才可以存储最完整的 IPv4 地址字符串形式(15个字符 xxx.xxx.xxx.xxx)和一个结束符(\0)。

创建 socket

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>//包含了这个头文件,上面两个就可以省略

int socket(int domain,int type,int protoco1);

功能:创建一个套接字

参数:

domain:底层协议族

    • PF_INET:IPv4
    • PF_INET6:IPv6
    • PF_UNIX:UNIX 本地域协议族

type:指定通信过程中使用的服务类型

    • SOCK_STREAM:流服务(TCP等)
    • SOCK_DGRAM:数据报服务(UDP等)
    • 自 Linux 内核版本 2.6.17 起,type 参数可以接受上述服务类型与下面两个重要的标志相与的值:SOCK_NONBLOCKSOCK_CLOEXEC,前者将 socket 设置为非阻塞的。

protocol:在前两个参数构成的协议集合下,再选择一个具体的协议。一般设置为 0(因为前面两个参数已经完全决定了它的值)

返回值:

成功:返回 socket 文件描述符,操作的就是内核缓冲区

失败:返回-1 并设置 errno

命名 socket

即将一个 socket 与 socket 地址绑定。

int bind(int sockfd,const struct sockaddr* my_addr,socklen_t addrlen);

功能:my_addr 所指的 socket 地址分配给未命名的 sockfd 文件描述符,addrlen 指出 socket 地址长度。

参数:

sockfd:通过socket函数得到的文件描述符

my_addr:需要绑定的 socket 地址,这个地址封装了ip 地址和端口号的信息

addrlen:第二个参数结构体占的内存大小

返回值:

成功:返回 0

失败:返回-1 并设置 errno

监听 socket

socket 被命名后,还不能马上接受客户连接,需要使用如下系统调用,创建一个监听队列,存放待处理的客户连接:

int listen(int sockfd,int backlog);

功能:监听指定 socket上的连接

参数:

sockfd:通过socket()函数得到的文件描述符,指定被监听的 socket

backlog:提示内核监听队列的最大长度,超过该设定的最大值,服务器将不受理新的客户连接。

返回值:

成功:返回 0

失败:返回 -1 并设置 errno

接受连接

int accept(int sockfd,struct sockaddr *addr ,sock1en_t *addrlen);

功能:从 listen 监听队列中接受一个连接。accept 只是从监听队列中取出连接,而不关心连接处于何种状态。

参数:
sockfd:执行过 listen 系统调用的监听 socket
addr:记录了连接成功后客户端的地址信息(socket 地址)
addrlen:指定第二个参数的 socket 地址长度。
返回值:
成功:返回一个新的连接 socket,该 socket 唯一标识了被接受的这个连接,服务器可以通过读写该 socket 来与被接受连接的客户端通信。
失败:返回-1 并设置 errno

发起连接

服务器通过 listen 调用来被动接受连接,而客户端通过 connect 来主动与服务器发起连接。

int connect(int sockfd,const struct sockaddr *serv_addr,socklen_t addr1en);

参数:
sockfd:用于通信的文件描述符,由 socket 系统调用返回。
addr:客户端要连接的服务器的地址信息,即服务器监听的 socket 地址。
addrlenaddr 的地址长度。

返回值:

成功:返回0,连接成功建立,则 sockfd 唯一标识这个连接,客户端可以通过读写 sockfd 来与服务器通信。

失败:返回 -1并设置 errno

关闭连接

int close (int fd);

功能:关闭该连接对应的 socket

参数:

fd:待关闭的 socket。注意,close 调用并非总是立即关闭一个连接,而是将 fd 的引用计数减一。当引用计数为 0 时,才真正关闭这个连接。如果要立即终止连接,可以使用 shutdown 系统调用:

int shutdown(int sockfd, int howto);

功能:立即关闭该连接对应的 socket

参数:

sockfd:待关闭的 socket。

howto:决定了 shutdown 的行为。

    • SHUT_RD:断开输入流。套接字无法接收数据(即使输入缓冲区收到数据也被抹去),无法调用输入相关函数。
    • SHUT_WR:断开输出流。套接字无法发送数据,但如果输出缓冲区中还有未传输的数据,则将传递到目标主机。
    • SHUT_RDWR:同时断开 I/O 流。相当于分两次调用 shutdown(),其中一次以 SHUT_RD 为参数,另一次以 SHUT_WR 为参数。

返回值:

成功:返回0

失败:返回 -1并设置 errno

shutdown 可以分别关闭 socket 上的读和写,而 close 只能同时关闭。

数据读写

TCP 数据读写

用于 TCP 流数据的读写调用:

#include <sys/types.h>
#include <sys/socket.h>

ssize_t recv(int sockfd, void *buf, size_t len, int flags)
ssize_t send(int sockfd, const void *buf, size_t len, int flags)

recv()成功时返回实际读取到的数据长度,可能小于指定读缓冲区的大小 len,因此可能需要多次调用才能读取完整数据。返回 0 说明对方已经关闭连接。返回-1 说明出错;

send()成功时返回实际写入 sockfd 的数据长度

flags 参数为数据收发提供了额外的控制。只对 sendrecv 的当前调用生效。而 setsockopt 可以永久修改 socket 的某些属性。

UDP 数据读写

系统调用是:

#include <sys/types.h>
#include <sys/socket.h>

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                 struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
               const struct sockaddr *dest_addr, socklen_t addrlen);

recvfrom

参数:

sockfd:一个已打开的套接字的描述符。

buf:一个指针,指向读缓冲区。

len:读缓冲区的大小(以字节为单位)。

src_addr:一个指针,指向发送端的 socket 地址。(UDP 是无连接的,每次通信都需要指定通信地址)

addrlen:开始时,它应该设置为 src_addr 缓冲区的大小。当 recvfrom() 返回时,该值会被修改为实际地址的长度(以字节为单位)。

flags:控制接收行为的标志。通常可以设置为0。

返回值:

在成功的情况下,recvfrom() 返回接收到的字节数。

如果没有数据可读或 socket 已经关闭,那么返回值为0。

出错时,返回 -1,并设置全局变量 errno 以指示错误类型。

sendto:与recvfrom类似。

实际上,recvfromsendto也可以用于面向连接的 socket 的数据读写,当连接已经建立时,只需要把recvfrom/sendtosrc_addraddrlen设置为 NULL,即可忽略发送端/接收端的 socket 地址。

通用数据读写

不仅能用于 TCP 流数据,还能用于 UDP 数据报。

#include <sys/socket.h>

ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);
ssize_t recvmsg(int sockfd, const struct msghdr *msg, int flags);

带外标记

Linux内核检测到TCP紧急标志时,将通知应用程序有带外数据需要接收。内核通知应用程序带外数据到达的两种常见方式是:I/O复用产生的异常事件和SIGURG信号。但是,即使应用程序得到了有带外数据需要接收的通知,还需要知道带外数据在数据流中的具体位置,才能准确接收带外数据。这一点可通过如下系统调用实现:

#include <sys/socket.h>
int sockatmark( int sockfd );

sockatmark 判断 sockfd 是否处于带外标记,即下一个被读取到的数据是否是带外数据。

如果是,sockatmark返回1,此时我们就可以利用带MSG_OOB标志的recv调用来接收带外数据。如果不是,则sockatmark返回0。

地址信息函数

在某些情况下,我们想知道一个连接socket的本端socket地址,以及远端的socket地址。下面这两个函数正是用于解决这个问题:

#include <sys/socket.h>

int getsockname( int sockfd,struct sockaddr* address,socklen t* address_len );
int getpeername( int sockfd,struct sockaddr* address, socklen t* address_len );

getsockname获取sockfd 对应的本端socket地址,并将其存储于address参数指定的内存中,该socket地址的长度则存储于address_len参数指向的变量中。如果实际socket地址的长度大于 address所指内存区的大小,那么该socket地址将被截断。getsockname 成功时返回0,失败返回-1并设置 errno。

getpeerame 获取 sockfd对应的远端socket地址,其参数及返回值的含义与 getsockname的参数及返回值相同。

socket 选项

用来读取和设置socket文件描述符属性的方法:

#include <sys/socket.h>

int getsockopt( int sockfd, int level,int option_name, 
                    void* option_value, socklen_t* restrict option_len );
int setsockopt( int sockfd, int level,int option_name, 
                    const void* option_value, socklen_t option_len );

实战:TCP通信实现(服务器端和客户端)

接下来,我们通过一个实战小项目,来更加深刻的理解 TCP 通信和 socket 的使用。项目代码来自:【Linux】socket 编程(socket套接字介绍、字节序、socket地址、IP地址转换函数、套接字函数、TCP通信实现)_linux socket-CSDN博客

整个流程经历了以下阶段:

  1. 在代码中,服务器端先通过socket调用,创建了一个 socket;
  2. 服务器端调用 bind 将 socket 绑定到一个 IP 地址和端口号;
  3. 服务器端调用 listen 使 socket 进入监听状态,准备接收客户端的连接请求;
  4. 客户端调用 connect,发起连接请求;
  5. 服务器端调用 accept,接受客户端的连接请求;
  6. TCP 连接建立,可以通过 read/write 收发数据。

服务器端

#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

/* TCP服务器端实现 */
int main() {

    /*
     * 1.创建socket(用于监听的套接字):
     *  使用 socket 函数创建一个套接字 listen_fd
     *  AF_INET: 表示使用 IPv4 协议
     *  SOCK_STREAM: 表示使用 TCP 连接。
     */  
    int listen_fd = socket(AF_INET, SOCK_STREAM, 0);

    if(listen_fd == -1) {
        perror("socket");
        exit(-1);
    }

    /*
     * 2.绑定/命名 socket: 
     *  struct sockaddr_in 用于指定服务器的地址和端口。
     *  INADDR_ANY 让服务器绑定到所有可用的网络接口(即可以接受任何 IP 地址的连接)。
     *  使用 bind 函数将套接字与指定的 IP 地址和端口绑定。
     *  如果 bind 返回 -1,表示绑定失败,输出错误信息并退出程序。
     */
    struct sockaddr_in saddr;
    saddr.sin_family = AF_INET;
    saddr.sin_addr.s_addr = INADDR_ANY;  // 0.0.0.0
    saddr.sin_port = htons(9999);
    int ret = bind(listen_fd, (struct sockaddr *)&saddr, sizeof(saddr));

    if(ret == -1) {
        perror("bind");
        exit(-1);
    }

    /*
     * 3.监听连接
     *  listen 函数使套接字进入监听状态,准备接收来自客户端的连接请求。
     *  第二个参数 8 指定了连接队列的最大长度,即最多可以有 8 个连接请求排队等待处理。
     *  如果 listen 返回 -1,表示监听失败,输出错误信息并退出程序。
     */ 
    ret = listen(listen_fd, 8);
    if(ret == -1) {
        perror("listen");
        exit(-1);
    }

    /*
     * 4.接收客户端连接
     *  使用 accept 函数接受客户端的连接请求,accept 返回一个新的套接字 cfd,用于与客户端通信。
     *  clientaddr 用于存储客户端的地址信息。
     *  如果 accept 返回 -1,表示接收连接失败,输出错误信息并退出程序。
    */
    struct sockaddr_in clientaddr;
    socklen_t len = sizeof(clientaddr);
    int cfd = accept(listen_fd, (struct sockaddr *)&clientaddr, &len);
    
    if(cfd == -1) {
        perror("accept");
        exit(-1);
    }

    /*
     * 5. 输出客户端的信息
     *  inet_ntop 函数将客户端的 IP 地址从网络字节序转换为点分十进制字符串格式,并输出。
     *  ntohs 函数将客户端的端口号从网络字节序转换为主机字节序,并输出。
     */
    char clientIP[16];
    inet_ntop(AF_INET, &clientaddr.sin_addr.s_addr, clientIP, sizeof(clientIP));
    unsigned short clientPort = ntohs(clientaddr.sin_port);
    printf("client ip is %s, port is %d\n", clientIP, clientPort);

    // 6.通信
    char recvBuf[1024] = {0};
    while(1) {
        
        // 获取客户端的数据
        int num = read(cfd, recvBuf, sizeof(recvBuf));
        if(num == -1) {
            perror("read");
            exit(-1);
        } else if(num > 0) {
            printf("recv client data : %s\n", recvBuf);
        } else if(num == 0) {
            // 表示客户端断开连接
            printf("clinet closed...");
            break;
        }

        char *data = "hello,i am server";
        // 给客户端发送数据
        write(cfd, data, strlen(data));
    }
   
    // 7. 关闭文件描述符
    close(cfd);
    close(listen_fd);

    return 0;
}

客户端

#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

/* TCP客户端实现 */
int main() {

    // 1.创建套接字: AF_INET 表示使用 IPv4 地址,SOCK_STREAM 表示使用 TCP 连接。
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    if(fd == -1) {
        perror("socket");
        exit(-1);
    }

    // 2.连接服务器端: 设定服务器的 IP 地址和端口号(此处为 "10.1.1.161" 和 9999)。
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    inet_pton(AF_INET, "10.1.1.161", &serveraddr.sin_addr.s_addr);
    serveraddr.sin_port = htons(9999);
    int ret = connect(fd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));

    if(ret == -1) {
        perror("connect");
        exit(-1);
    }

    
    // 3.通信
    char recvBuf[1024] = {0};
    while(1) {

        char *data = "hello,i am client";
        // 通过 write 函数发送消息 "hello,i am client" 到服务器。
        write(fd, data , strlen(data));

        sleep(1);
        
        int len = read(fd, recvBuf, sizeof(recvBuf));
        if(len == -1) {
            perror("read");
            exit(-1);
        } else if(len > 0) {
            printf("recv server data : %s\n", recvBuf);
        } else if(len == 0) {
            // 如果 read 返回 0,表示服务器关闭了连接,客户端程序将结束循环并关闭连接。
            printf("server closed...");
            break;
        }
    }
    
    // 4. 关闭连接
    close(fd);

    return 0;
}

效果

编译运行上述代码,在终端中可以看单到:

参考

  1. 大端序和小端序_大端序和小端序的区别-CSDN博客
  2. C++的socket地址详解_如何定义c socket ip地址-CSDN博客
  3. 深入理解TCP协议及其源代码-send和recv背后数据的收发过程 - zhqian - 博客园
  4. Linux网络编程- recvfrom() & sendto()-CSDN博客
  5. recvmsg 和 sendmsg 函数详解 以及如何进行多进程之间文件描述符的发送与接收-CSDN博客
  6. 【Linux】socket 编程(socket套接字介绍、字节序、socket地址、IP地址转换函数、套接字函数、TCP通信实现)_linux socket-CSDN博客

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

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

相关文章

自行车制造5G智能工厂工业物联数字孪生平台,推进制造业数字化

在当今这个日新月异的数字化时代&#xff0c;制造业正经历着前所未有的变革&#xff0c;自行车制造5G智能工厂工业物联数字孪生平台的兴起&#xff0c;无疑是这场转型浪潮中一股强劲力量。自行车制造5G智能工厂工业物联数字孪生平台的成功应用&#xff0c;不仅仅是技术上的突破…

代码随想录DAY21 - 二叉树 - 08/20

目录 修建二叉搜索树 题干 思路和代码 递归法 迭代法 将有序数组转化为平衡二叉搜索树 题干 思路和代码 递归法 递归优化 迭代法 把二叉搜索树转换为累加树 题干 思路和代码 递归法 迭代法 修建二叉搜索树 题干 题目&#xff1a;给你二叉搜索树的根节点 root …

数据结构【顺序结构二叉树:堆】(1)

​​​​​​​ &#x1f31f;个人主页&#xff1a;落叶 目录 ​ &#x1f525;树的概念与结构​​​​​​​ &#x1f525;树的表⽰ &#x1f525;孩⼦兄弟表⽰法&#xff1a; &#x1f525;树形结构实际运⽤场景 &#x1f525;⼆叉树 &#x1f525;概念与结构 &…

day4JS-数组

1. 什么是数组 数组是值的有序集合。每个值叫做一个元素。每个元素在数组中有一个位置, 以数字表示,称为索引 (有时也称为下标)。数组的元素可以是任何类型。数组索引从 0 开始,数组最大能容纳 4294967295 个元素。 2. 数组的声明与使用 2.1 使用字面量的方式声明数组 语法…

Minio web控制台实现授权管理

启动minio ./minio server /data01/aidacp/apps/minio/data --config-dir /data01/aidacp/apps/minio/conf --address 127.0.0.1:19090 --console-address 127.0.0.1:19091 &WARNING: Detected Linux kernel version older than 4.0.0 release, there are some known pote…

建设项目跟踪与展示系统

这是在翻旧文件时翻到的16年写的一个项目 建设项目跟踪与展示系统 建设方案 一、系统建设目的及意义 建设工程项目进度控制的最终目的是确保建设项目按预定的时间完成。能否在预定的时间内交付使用&#xff0c;直接影响到投资效益。为解决施工组织过程中存在问题&#xff0c;…

PyTorch深度学习实战(25)—— 使用向量化思想解决实际问题

本文将实际应用向量化思想解决深度学习中的几个经典问题,读者可以在这些示例中进一步领悟向量化思想和高级索引思想。 1. Box_IoU Box_IoU是目标检测任务中最基本的评价指标。简单来说,Box_IoU就是模型预测的检测结果框(predicted bbox)与原始标记框(ground truth)之间的…

企业级Nginx源码安装及其实战讲解

一&#xff1a;web服务基础介绍 1.1Web服务介绍 Apache 经典的 Web 服务端 Apache起初由美国的伊利诺伊大学香槟分校的国家超级计算机应用中心开发 目前经历了两大版本分别是1.X和2.X 其可以通过编译安装实现特定的功能 Apache prefork 模型 预派生模式&#xff0c;有一个…

[C++进阶]map和set

一、关联式容器 STL中的部分容器&#xff0c;比如&#xff1a;vector、list、deque、forward_list(C11)等&#xff0c;这些容器统称为序列式容器&#xff0c;因为其底层为线性序列的数据结构&#xff0c;里面存储的是元素本身。 那什么是关联式容器&#xff1f;它与序列式容器…

2.复杂度分析

2.1 算法效率评估 在算法设计中&#xff0c;我们先后追求以下两个层面的目标。 找到问题解法&#xff1a;算法需要在规定的输入范围内可靠地求得问题的正确解。寻求最优解法&#xff1a;同一个问题可能存在多种解法&#xff0c;我们希望找到尽可能高效的算法。 也就是说&a…

JavaScript_7_练习:随机抽奖案例

效果图 代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>练习&#xff1a;随机抽奖案例</tit…

做谷歌seo如何选择好的服务器?

做谷歌seo如何选择好的服务器&#xff0c;如果你选择自托管平台&#xff0c;那么服务器的选择就非常关键了&#xff0c;服务器的好与坏影响着你的网站的表现&#xff0c;所以选择服务器要慎重。以下是一些建议&#xff0c;帮你做出明智的选择&#xff1a; 安全性&#xff1a;安…

Python Web框架 Django学习记录:1 项目安装,启动

Windows上学习Django # 创建一个虚拟环境 python -m venv tutorial-env# 激活虚拟环境 tutorial-env\Scripts\activate# 安装Django py -m pip install Django# 查看Django版本 py -m django --version# 使用脚手架创建一个项目 django-admin startproject mysite# 启动项目 cd…

linux出现sql密码被忘记的解决方法

目录 前言正文 前言 此处放置在运维篇章&#xff0c;对应sql的修改密码&#xff0c;推荐阅读&#xff1a;修改sql密码&#xff08;涵盖多个版本&#xff09; 如果补充Sql的基本知识&#xff0c;推荐阅读&#xff1a;Mysql底层原理详细剖析常见面试题&#xff08;全&#xff0…

git本地仓库同步到远程仓库

整个过程分为如下几步&#xff1a; 1、本地仓库的创建 2、远程仓库的创建 3、远程仓库添加key 4、同步本地仓库到远程仓库 1、本地仓库的创建&#xff1a; 使用如下代码创建本地仓库&#xff1a; echo "# test" >> README.md git init git add README.md …

shell脚本的编写规范和变量类型

1.shell的作用 shell是Linux系统中后台运行的一种特殊程序也可以理解 成一种特殊的软件&#xff0c;提供了用户与内核进行 交互操作的 一种接口。(简单的说就是shell把人类的高级语言转换成二进制数据&#xff0c;让机器明白你的指令) 过程&#xff1a;用户发出指令&#xff0c…

图像数据处理19

四、形态学图像处理 4.6 灰度图像的形态学处理 4.6.1灰度图像的腐蚀操作 灰度图像的腐蚀处理会让图像整体变暗&#xff0c;增强较暗的细节&#xff0c;抑制较亮的细节。其有助于分割图像、平滑图像边缘。 import cv2 import numpy as np# 读取图像 image cv2.imread(fu.jp…

魔珐科技出席WWEC教育者大会,给出AI时代教培行业精细化运营赋能方案

AI与教育的结合&#xff0c;已经成为教育行业发展的关键增长点。头部机构纷纷寻求AI技术与产品融合&#xff0c;以增强市场竞争力&#xff0c;希望在这场技术引发的行业洗牌中保持领先。 喜忧之中&#xff0c;展望未来&#xff0c;教培机构如何继续找准航向&#xff0c;贴近政…

表格解析调研

表格解析调研 TextInTools TextInTools&#xff1a;https://tools.textin.com/table 可以将表格图片解析成可编辑的表格/json&#xff0c;效果不错 白描 地址&#xff1a;https://web.baimiaoapp.com/image-to-excel 可以将表格图片识别成可编辑的表格&#xff0c;可复制、…

OpenCV4特征匹配

目录 一.特征检测的基本概念二.Harris角点检测三.Shi-Tomasi角点检测四.SIFT关键点检测五.SURF特征检测&#xff08;属于opencv_contrib&#xff09;六.ORB特征检测七.特征匹配方法八.FLANN特征匹配 流程梳理 一.特征检测的基本概念 OpenCV特征的场景 1.图像搜索&#xff0c;…