Linux系统应用编程(六)Linux网络编程(下篇)

news2024/12/29 9:04:24

本篇主要内容:

    • 一、Linux的文件描述符
    • 二、多路IO转接(上)
      • 1.基础版多路IO转接select
        • ▶ 关于select( )函数
        • ▶ select( )改写上篇案例
      • 2.加强版多路IO转接poll
      • 3.顶配版多路IO转接epoll
        • ▶ epoll相关函数
        • (1)创建监听红黑树
        • (2)操作监听红黑树
        • (3)等待监听
        • ▶ epoll改写上篇案例
    • 三、多路IO转接(下)
      • 1.epoll的两种触发模式
      • 2.epoll反应堆设计思想

一、Linux的文件描述符

  • 文件描述符 0:标准输入,代表进程从终端或管道中读取数据;
  • 文件描述符 1:标准输出,代表进程向终端或管道输出数据;
  • 文件描述符 2:标准错误输出,代表进程向终端或管道输出错误信息;
  • 文件描述符 3:代表进程打开的第一个文件,可以通过dup()函数复制到其他文件描述符上使用;
  • 文件描述符 4:代表进程打开的第二个文件,同样可以通过dup()函数复制到其他文件描述符上使用;

这些特殊的文件描述符可以让进程进行更加灵活的 I/O 操作,例如将标准错误输出重定向到文件中,或者在不关闭已打开的文件的情况下执行其他操作等。内核自动从0开始(一般最大默认1024,可以通过命令ulimit -n查看),为进程所打开的文件分配文件描述符

二、多路IO转接(上)

1.基础版多路IO转接select

▶ 关于select( )函数

select( ) 是一种多路 I/O 转接技术,它可以同时监控多个 I/O 状态,能够实现高效的事件驱动(event-driven)程序设计。它灵活性高、跨平台性好:select() 函数是POSIX标准中规定的函数,支持Linux、Unix、MacOS、类Unix等平台。poll()和epoll()两种多路IO转接都只适用于Linux,但是由于每次调用select( )都需要将所有文件描述符在用户态和内核态之间复制拷贝,且select依赖轮询机制监听事件,因此当监听的文件描述符数量过大时效率较低,此外select( )只支持最大文件描述符1024个,可扩展性较差。

在这里插入图片描述

▶ select( )改写上篇案例

PS:服务器绑定监听等代码封装在了头文件中,这里主要体现select()的使用

#include "net.h"

int main() {
    struct ServerSocket ss = {
            .serverListen = serverListen,
            .socketBind = socketBind,
            .serverAccept = serverAccept
    };
    ss.sockfd = socket(AF_INET, SOCK_STREAM, 0);
    ss.socketBind(ss.sockfd, "192.168.35.128", 8880);
    ss.serverListen(ss.sockfd, 128);

    /* socket创建、bind绑定等其它已经封装到了net.h头文件
     * 这里主要体现多路IO转接select()的使用 */

    int maxfd = ss.sockfd;      //最大文件描述符
    int retn = 0;
    fd_set readSet, tempSet;     //读事件集合,临时的集合(临时备份readSet)
    FD_ZERO(&tempSet);      //清0
    FD_SET(ss.sockfd, &tempSet);     //把server用于监听的fd加入tempSet

    int nread, i;
    char *readBuff = (char *) malloc(128);      //读buff
    FILE *buffFile = NULL;          //文件流
    struct Data data;
    char head[8];
    struct ClientSocket cs;

    while (1) {
        readSet = tempSet;
        retn = select(maxfd + 1, &readSet, NULL, NULL, NULL);
        if (retn == -1) {
            perror("select");
            exit(-2);
        }
        if (FD_ISSET(ss.sockfd, &readSet)) { //判断ss.sockfd是否在readSet里(满足读事件则客户端接入)
            /* 调用accept(),并更新maxfd,tempSet后进入下一次循环*/
            cs = ss.serverAccept(ss.sockfd);
            FD_SET(cs.cfd, &tempSet);
            printf("<server> client connected (%s:%d)\n", cs.ip, cs.port);
            if (maxfd < cs.cfd)maxfd = cs.cfd;
            continue;       //这里可if(retn == 1)continue;
        }
        /* 循环遍历符合读事件的fd */
        for (i = ss.sockfd + 1; i <= maxfd; i++) {
            if (FD_ISSET(i, &readSet)) {
                /* 找到满足的读事件fd */
                nread = read(i, readBuff, 128);   //读取客户端发过来的命令
                /* 对read判空,防止客户端退出后一直收空数据的死循环 */
                if (nread == 0) {
                    printf("<server> client disconnected (%s:%d)\n", cs.ip, cs.port);
                    FD_CLR(i, &tempSet);
                    close(i);
                    continue;
                }
                /* 执行客户端发过来的命令 */
                buffFile = popen(readBuff, "r");    //命令执行成功结果读取到writeBuff
                data = dataDealWith(buffFile);
                sprintf(head, "%ld", data.dataLenth);
                write(i, head, 8);
                write(i, data.dataBody, data.dataLenth);
                memset(readBuff, '\0', 128);
                memset(&data, 0, sizeof(data));
                pclose(buffFile);
            }
        }
    }
}

在这里插入图片描述

2.加强版多路IO转接poll

  • poll只在select的基础上有一定的优化,但并未解决其他缺点带来的并发性限制,也不适用于高并发场景

3.顶配版多路IO转接epoll

▶ epoll相关函数

  • epoll是Linux下最常用、最高效的IO多路复用函数,它可以通过内核事件驱动机制实现对大量套接字(上万个)进行监听,底层利用红黑树和链表来存储并快速查找需要监视的文件描述符以及对应的事件,避免了传统系统调用(如 select、poll)中使用轮询方式进行遍历的缺点,提高了效率。(redis、nginx、java NIO底层均使用epoll)
  • epoll相对于select( )和poll( )具有的优势:
  • 支持较大的文件描述符和连接数(上限是整个系统打开文件的最大值)
  • 支持更高效的事件通知处理机制。(时间复杂度为O(1))
  • 支持ET模式和LT模式两种工作模式。目前被广泛地使用于各种高并发服务器程序中,redis、nginx等

(1)创建监听红黑树

在这里插入图片描述

(2)操作监听红黑树

在这里插入图片描述

(3)等待监听

在这里插入图片描述

▶ epoll改写上篇案例

#include "net.h"
#include <sys/epoll.h>

int main() {

    struct ServerSocket ss = {
            .serverListen = serverListen,
            .socketBind = socketBind,
            .serverAccept = serverAccept
    };
    ss.sockfd = socket(AF_INET, SOCK_STREAM, 0);
    ss.socketBind(ss.sockfd, "192.168.35.128", 8880);
    ss.serverListen(ss.sockfd, 128);

    /* socket创建、bind绑定等其它已经封装到了net.h头文件
     * 这里主要体现多路IO转接epoll的使用 */

    struct ClientSocket cs;
    int i, retn;

    int epoll_fd = epoll_create(128);
    if (epoll_fd < 0) {
        perror("epool_create");
        exit(-1);
    }

    struct epoll_event events[16];
    char *readBuff = (char *) malloc(128);      //读buff
    FILE *buffFile = NULL;          //文件流
    struct Data data;
    char head[8];
    int nread = 0;

    /* 创建监听读事件红黑树,并将ss.sockfd添加进去 */
    struct epoll_event event = {
            .data.fd = ss.sockfd,
            .events = EPOLLIN
    };
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, ss.sockfd, &event) < 0) {
        perror("epoll_ctl");
        exit(-1);
    }

    /* 等待读事件发生,判断是客户端接入还是已接入客户端发生数据 */
    while (1) {
        retn = epoll_wait(epoll_fd, events, 16, -1);
        if (retn < 0) {
            perror("epoll_wait");
            exit(-1);
        } else {
            /* 轮询 */
            for (i = 0; i < retn; i++) {
                if (events[i].data.fd == ss.sockfd) {   //客户端接入
                    cs = ss.serverAccept(ss.sockfd);
                    event.data.fd = cs.cfd;
                    event.events = EPOLLIN;
                    epoll_ctl(epoll_fd, EPOLL_CTL_ADD, cs.cfd, &event);
                    printf("<server> client connected (%s:%d)\n", cs.ip, cs.port);
                    continue;
                }else{      //已接入读客户端写数据
                    nread = read(events[i].data.fd, readBuff, 128);   //读取客户端发过来的命令
                    /* 对read判空,防止客户端退出后一直收空数据的死循环 */
                    if (nread == 0) {
                        printf("<server> client disconnected (%s:%d)\n", cs.ip, cs.port);
                        epoll_ctl(epoll_fd, EPOLL_CTL_DEL, events[i].data.fd, &event);
                        close(events[i].data.fd);
                    }
                    /* 执行客户端发过来的命令 */
                    buffFile = popen(readBuff, "r");    //命令执行成功结果读取到writeBuff
                    data = dataDealWith(buffFile);
                    sprintf(head, "%ld", data.dataLenth);
                    write(events[i].data.fd, head, 8);
                    write(events[i].data.fd, data.dataBody, data.dataLenth);
                    memset(readBuff, '\0', strlen(readBuff));
                    memset(&data, 0, sizeof(data));
                    pclose(buffFile);
                }
            }
        }
    }
}

三、多路IO转接(下)

前面只是通过改写案例熟悉epoll的几个相关函数,并初步认识epoll的使用,实际上,epoll相比于select和poll效率更高,可以显著提高服务器的性能,降低系统开销。但是,epoll在应用中使用起来比较复杂,需要合理地设计数据结构和回调机制,才能发挥其最大的优势

1.epoll的两种触发模式

在这里插入图片描述

2.epoll反应堆设计思想

epoll是一种高效的I/O事件通知机制,常用于网络编程中。其设计思想是基于内核态与用户态的交互方式,使用了红黑树(Red-Black Tree)数据结构来维护被监听的描述符集合,进而避免了遍历文件描述符导致的性能问题。当有 I/O 事件发生时,epoll就会通知应用程序进行处理,从而实现了高效地I/O处理。同时,epoll还支持边缘触发模式和水平触发模式,可以根据不同需求进行选择。更多详细,可见Bilibili黑马C++课程

核心:epoll ET模式 + 非阻塞IO + 回调函数

【main.c】

#include "net.h"


/* 文件描述符事件(fd对应事件对应的回调) */
struct Fd_Event {
    int fd;     //lfd或cfd
    int event;  //监听事件
    void *arg;  //回调函数void *参数
    int (*callBack)(ServerSocket ss, int fd, int event, void *arg, void *retnEvents);   /* 回调函数 */
    int epoll_fd;       //红黑树句柄
    int status;     //节点状态(0-不在监听、1-在监听)
};

/* 设置文件描述符为非阻塞并返回 */
int setNonblock(int fd) {
    int flag = fcntl(fd, F_GETFL, 0);   //获取当前flag
    if (flag < 0) {
        perror("fcntl");
        exit(-1);
    }
    flag = flag | O_NONBLOCK;   //获取非阻塞flag
    fcntl(fd, F_SETFL, flag);   //设置非阻塞
    return fd;
}

/* 初始化服务器socket(创建socket、端口复用、绑定、监听) */
int initServerSocket(ServerSocket ss, char *ip, int port) {
    printf("<Server> init...\n");
    ss.sockfd = socket(AF_INET, SOCK_STREAM, 0);
    int optval = 1;
    setsockopt(ss.sockfd, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval));
    ss.socketBind(ss.sockfd, ip, port);
    ss.serverListen(ss.sockfd, 128);
    return ss.sockfd;
}
/***************************************************************
  函数作用:初始化lfd,设置非阻塞、初始化Fd_event结构体、上树
  函数参数:int lfd 监听文件描述符
          int epoll_fd 监听红黑树fd
          struct Fd_Event *lfd_event 自定义的结构体类型(传出参数)
          int (*callBack)() 函数指针(传回调函数)
 ***************************************************************/
int initListenFd(int lfd, int epoll_fd, struct Fd_Event *lfd_event,
                 int (*callBack)(ServerSocket, int, int, void *, void *)) {
    /* 设置lfd非阻塞 */
    lfd = setNonblock(lfd);
    /* 初始化lfd的Fd_event结构体 */
    lfd_event->epoll_fd = epoll_fd;
    lfd_event->fd = lfd;
    lfd_event->event = EPOLLIN | EPOLLET;
    lfd_event->callBack = callBack;
    lfd_event->status = 0;

    /* 添加到监听红黑树 */
    struct epoll_event ev = {
            .data.ptr = lfd_event,  //data.ptr(联合体成员void *)指向Fd_event
            .events = lfd_event->event
    };
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, lfd, &ev) < 0) {
        perror("epoll_ctl");
        return -1;
    }
    lfd_event->status = 1;  
    return lfd;
}

/***************************************************************
  函数作用:初始化lfd(设置非阻塞、初始化Fd_event结构体、上树)
  函数参数:int lfd 监听文件描述符
          int epoll_fd 监听红黑树fd
          struct Fd_Event *cfd_event 自定义的结构体类型(传出参数)
          int (*callBack)() 函数指针(传回调函数)
  PS:同上initListenFd()
 ***************************************************************/
int initConnectFd(int cfd, int epoll_fd, struct Fd_Event *cfd_event, int event,
                  int (*callBack)(ServerSocket, int, int, void *, void *)) {
    cfd = setNonblock(cfd);
    cfd_event->epoll_fd = epoll_fd;
    cfd_event->fd = cfd;
    cfd_event->event = event;
    cfd_event->callBack = callBack;
    cfd_event->status = 0;
    struct epoll_event ev = {
            .data.ptr = cfd_event,
            .events = cfd_event->event
    };
    if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, cfd, &ev) < 0) {
        perror("epoll_ctl");
        return -1;
    }
    cfd_event->status = 1;
    return cfd;
}

/* cfd的回调函数实现(这里主要做收发数据)*/
int cfdEventHandler(ServerSocket ss, int fd, int event, void *arg, void *retnEvent) {
    char *readBuff = (char *) malloc(128);
    char *head = (char *) malloc(8);
    FILE *buffFile = NULL;
    struct Fd_Event *temp = (struct Fd_Event *)arg;

    int nread = read(fd, readBuff, 128);   //读取客户端发过来的命令
    /* 对read判空,防止客户端退出后一直收空数据的死循环 */
    if (nread == 0) {
        printf("<server> client disconnected \n");
        epoll_ctl(temp->epoll_fd,EPOLL_CTL_DEL,fd,NULL);
        close(fd);
    }
    /* 执行客户端发过来的命令 */
    buffFile = popen(readBuff, "r");    //命令执行成功结果读取到writeBuff
    struct Data data = dataDealWith(buffFile);
    sprintf(head, "%ld", data.dataLenth);
    write(fd, head, 8);     //先发8个字节的头部
    write(fd, data.dataBody, data.dataLenth);
    pclose(buffFile);
    free(readBuff);
    free(head);
    return 1;
}

/* lfd回调函数(建立连接获得cfd,再初始化cfd) */
/* retnEvent保存cfd的Fd_Event结构体数组。 */
int lfdEventHandler(ServerSocket ss, int fd, int event, void *arg, void *retnEvent) {
    int i;
    struct Fd_Event *lfd_Event = (struct Fd_Event *) arg;
    ClientSocket cs = ss.serverAccept(ss.sockfd);
    printf("<Server> client connected(%s:%d)\n", cs.ip, cs.port);
    struct Fd_Event *ptemp = (struct Fd_Event *)retnEvent;
    for(i=0;i<1024;i++){
        if(ptemp[i].status == 0)break;
    }

    //printf("epollfd=%d\n",lfd_Event->epoll_fd);
    initConnectFd(cs.cfd, lfd_Event->epoll_fd,&ptemp[i],event,cfdEventHandler);
    return cs.cfd;
}


int main() {
    int lfd;
    int nready, i;
    ServerSocket ss = {
            .serverListen = serverListen,
            .socketBind = socketBind,
            .serverAccept = serverAccept
    };
    lfd = initServerSocket(ss, "192.168.35.128", 8880);
    ss.sockfd = lfd;
    int epoll_fd = epoll_create(128);
    if (epoll_fd < 0) {
        perror("epoll_create");
        exit(-1);
    }
    struct Fd_Event lfd_event;
    struct Fd_Event cfd_retnEvent[1024];    //传出参数:存就绪的cfd
    for(int j=0;j<1024;j++){
        cfd_retnEvent[j].status = 0;
    }

    struct epoll_event events[1024];
    initListenFd(lfd, epoll_fd, &lfd_event, lfdEventHandler);

    while (1) {
        nready = epoll_wait(epoll_fd, events, 1024, 1000);
        if (nready < 0) {
            perror("epoll_wait");
            exit(-1);
        }
        for (i = 0; i < nready; i++) {
            struct Fd_Event *ptemp = (struct Fd_Event *) events[i].data.ptr;
            //printf("ptemp->fd = %d\n", ptemp->fd);
            if (ptemp->event & EPOLLIN) {
                if (ptemp->fd == lfd){
                    ptemp->callBack(ss, lfd, EPOLLIN | EPOLLET, (void *) (&lfd_event), (void *)cfd_retnEvent);
                }else{  //cfd读事件
                    ptemp->callBack(ss, ptemp->fd, EPOLLIN | EPOLLET, (void *) cfd_retnEvent, NULL);
                }
            } else if (ptemp->event & EPOLLOUT) {  //cfd写事件
                ptemp->callBack(ss, ptemp->fd, EPOLLOUT | EPOLLET, (void *) cfd_retnEvent, NULL);
            }
        }
    }
}

/*******************************************************************
* 实际上,程序还存在一些小BUG,这里仅简单使用epoll反应堆的代码设计思想  *
* 关于更多epoll反应堆或回调的巧妙编程设计思想可详细阅读开源的libevent库 *
********************************************************************/


【net.h】

#ifndef _NET_H_
#define _NET_H_

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
#include <sys/shm.h>
#include <signal.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/wait.h>

/* 客户端socket结构体 */
typedef struct {
    int cfd;    //建立连接的socket文件描述符
    char ip[32];    //客户端IP
    int port;   //客户端Port
}ClientSocket ;

/* 服务器socket结构体 */
typedef struct{
    int sockfd;       //服务器socket文件描述符
    void (*socketBind)(int, char *, int);   //给sockfd绑定地址函数
    void (*serverListen)(int, int);       //监听sockfd函数
    ClientSocket (*serverAccept)(int);  //建立连接函数
} ServerSocket ;


/* 服务器socket绑定地址信息函数实现 */
void socketBind(int sockfd, char *ip, int port) {
    int retn;
    /* 初始化地址结构体sockaddr_in */
    struct sockaddr_in serAddr = {
            .sin_family = AF_INET,
            .sin_port = htons(port)
    };
    inet_pton(AF_INET, ip, &serAddr.sin_addr.s_addr);
    /* 调用bind()绑定地址 */
    retn = bind(sockfd, (struct sockaddr *) &serAddr, sizeof(serAddr));
    if (retn == -1) {
        perror("bind");
        exit(-1);
    }
    printf("<Server> bind address.(%s:%d)\n", ip, port);
}

/* 服务器socket监听函数实现 */
void serverListen(int sockfd, int n) {
    int retn;
    retn = listen(sockfd, n);
    if (retn == -1) {
        perror("listen");
        exit(-1);
    }
    printf("<Server> listening...\n");
}

/* 服务器建立连接函数实现,返回值为struct ClientSocket结构体 *
 * (包括建立连接的socket文件描述符、客户端信息) */
ClientSocket serverAccept(int sockfd) {
    struct sockaddr_in clientAddr;
    socklen_t addrLen = sizeof(clientAddr);
    ClientSocket c_socket;
    c_socket.cfd = accept(sockfd, (struct sockaddr *) &clientAddr, &addrLen);
    if (c_socket.cfd == -1) {
        perror("accept");
        exit(-1);
    } else {
        c_socket.port = ntohs(clientAddr.sin_port);
        inet_ntop(AF_INET, &clientAddr.sin_addr.s_addr, c_socket.ip, sizeof(clientAddr));
        return c_socket;
    }
}

/* 数据结构体 */
struct Data {
    int headerLenth;    //数据头部长度
    long dataLenth;    //数据长度(命令执行成功的结果长度)
    char *dataBody;    //数据正文(命令执行成功的结果)
};


/* 处理数据的函数,返回值为struct Data */
struct Data dataDealWith(FILE *file){
    char *tempBuff = (char *)malloc(8192);		//临时buff
    long readBytes = 0;			//读取的字节数
    struct Data data = {
            .dataLenth = 0,
            .dataBody = NULL
    };
    /* 处理数据:计算数据正文大小,并保留管道中的数据到data.dataBody(需要动态调整大小) */
    while(fread(tempBuff,sizeof(char),8192,file) > 0){
        readBytes = strlen(tempBuff)+1;   //读到临时buff的字节数
        data.dataLenth += readBytes;      //数据长度累加readBytes
        if(data.dataLenth <= readBytes){	//如果数据长度小于设置的tempBuff大小,直接拷贝
            data.dataBody = (char *)malloc(readBytes);
            strcpy(data.dataBody,tempBuff);
        }else if(data.dataLenth > readBytes){	//如果数据长度大于设置的tempBuff大小,扩容后拼接到后面
            data.dataBody = realloc(data.dataBody,data.dataLenth);
            strcat(data.dataBody,tempBuff);
        }
        data.dataBody[strlen(data.dataBody)+1] = '\0';
        memset(tempBuff,'\0',8192);
    }
    free(tempBuff); //释放临时buff
    return data;
}

#endif

在这里插入图片描述

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

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

相关文章

重装系统后,MySQL install错误,找不到dll文件,或者应用程序错误

文章目录 1.找不到某某dll文件2.mysqld.exe - 应用程序错误使用DX工具直接修复 1.找不到某某dll文件 由于找不到VCRUNTIME140_1.dll或者MSVCP120.dll&#xff0c;无法继续执行代码&#xff0c;重新安装程序可能会解决此问题。 在使用一台重装系统过的电脑&#xff0c;再次重新…

Yolov8实战:交通roadsign识别,通过加入CVPR203 DCNV3和BiLevelRoutingAttention,暴力涨点

1.roadsign数据集介绍 数据集大小&#xff1a;877张 类别&#xff1a;speedlimit、crosswalk、trafficlight、stop 2.基于YOLOV8的roadsign识别 2.1 原始yolov8性能分析 原始map为0.841 2.1 加入DCNV3 博客地址&#xff1a; https://cv2023.blog.csdn.net/article/detai…

手写 EventBus:从零到一实现自己的事件总线库

简介&#xff1a;在本文中&#xff0c;我们将详细介绍如何从头开始实现一个轻量级的 EventBus 库。我们将以 XEventBus 为例&#xff0c;阐述整个实现过程&#xff0c;以及在实现过程中遇到的关键问题和解决方法。 一 引言 什么是 EventBus&#xff1f; EventBus 是一个基于…

C++ 多线程编程(三) 获取线程的返回值——future

C11标准库增加了获取线程返回值的方法&#xff0c;头文件为<future>&#xff0c;主要包括future、promise、packaged_task、async四个类。 那么&#xff0c;了解一下各个类的构成以及功能。 1 future future是一个模板类&#xff0c;它是传输线程返回值&#xff08;也…

2-Lampiao百个靶机渗透(精写-思路为主)框架漏洞利用2

特别注明&#xff1a;本文章只用于学习交流&#xff0c;不可用来从事违法犯罪活动&#xff0c;如使用者用来从事违法犯罪行为&#xff0c;一切与作者无关。 文章目录 前言一、环境重新部署二、AWVSxray联动和xraybs联动1.安装AWVSxray2.让xray和bs先联动3.AWVS和xray联动 三、p…

【Spring框架全系列】如何创建一个SpringBoot项目

&#x1f307;哈喽&#xff0c;大家好&#xff0c;我是小浪。前几篇博客我们已经介绍了什么是Spring&#xff0c;以及如何创建一个Spring项目&#xff0c;OK&#xff0c;那么单单掌握Spring是完全不够的&#xff0c;Spring的家族体系十分强大&#xff0c;我们还需要深入学习&am…

力扣---LeetCode160. 相交链表(代码详解+流程图)

文章目录 前言160. 相交链表链接&#xff1a;思路&#xff1a;方法一&#xff1a;暴力求解法1.1 时间复杂度&#xff1a;O(M*N)1.2 代码&#xff1a; 方法二&#xff1a;双指针2.1 时间复杂度&#xff1a;O(N)2.2 代码&#xff1a;2. 3流程图&#xff1a; 注意&#xff1a;补充…

13. Transformer(下)

P33 Transformer&#xff08;下&#xff09; 视频链接 P33 Transformer&#xff08;下&#xff09; 1. Decoder: Autoregressive(AT) Decoder原理&#xff1a; Encoder vs Decoder&#xff1a; Masked&#xff1a; how to stop&#xff1a; 2. Decoder: Non-autoregressive(…

网络基础——网络的发展史

作者简介&#xff1a;一名计算机萌新、前来进行学习VUE,让我们一起进步吧。 座右铭&#xff1a;低头赶路&#xff0c;敬事如仪 个人主页&#xff1a;我叫于豆豆吖的主页 目录 前言 一.网络发展史 1. ARPANET 2.TCP/IP协议 3. 互联网 4.Web浏览器 5.搜索引擎 6. 社交网…

如果你访问了某个网站,又不想让人知道怎么办?

问大家一个问题&#xff1a;如果你访问了某个网站&#xff0c;又不想让人知道怎么办&#xff1f; 你可能会说&#xff0c;把浏览器浏览历史记录清除&#xff0c;或者直接用无痕模式。 如果你只能想到这一层&#xff0c;那只能说图young&#xff01; 这么说吧&#xff0c;理论…

操作系统原理 —— 调度的概念、层次(十一)

调度的基本概念 在操作系统中的调度&#xff0c;是指操作系统从就序队列中选择一个作业&#xff0c;或者进程进行执行。 举个例子&#xff1a; 比如我们去银行窗口排队&#xff0c;排队的人就相当于就绪列表&#xff0c;窗口就相当于是操作系统&#xff0c;窗口需要服务排队…

npm的使用和命令

3.0 npm 什么是npm 是node管理包的工具 3.1 初始化包管理描述文件 package.json npm init // 会询问你每次的选项 或 npm init -y // 不询问你选项&#xff0c;默认就是确定 首先建立一个文件在路径里面全选写cmd 然后打开环境 在里面写npm init -y回车 就会在你原来空的文…

编写用户帮助/操作手册指南

背景&#xff1a; 用户操作手册是一份指导用户使用产品或服务的重要手册。 一个新系统&#xff0c;需要写用户操作手册&#xff0c;该从何下笔&#xff1f;本篇是一篇教你编写用户帮助/操作手册的指南&#xff5e; 首先&#xff0c;先来看一个反例 &#xff1a; 这个是我入职…

移动通信(17)预编码

源于某篇学位论文 利用预编码技术可以有效抑制大规模天线传输中的干扰&#xff0c;提高链路的峰值速率。大规模天线技术在提升性能的同时也存在很大的干扰问题。多天线传输中带来的多径干扰不可忽视。通常在接收端抑制干扰算法通常实现起来较为复杂&#xff0c;若采用预编码技…

安装Node.js和cnpm

一、安装Node.js 1.下载 Node.js官网下载 根据自身系统下载对应的安装包&#xff08;我这里为Windows10 64位&#xff0c;故选择下载第一个安装包&#xff09; 2、然后点击安装&#xff0c;选择自己要安装的路径&#xff0c;此处我选择的是&#xff1a;D:\Program Files\node…

中级软件设计师备考---UML

目录 面向对象的基础概念面向对象的设计原则UML的各类图设计模式对比分类 面向对象的基础概念 【只介绍一些我个人不太熟悉的概念】 继承和泛化&#xff1a;泛化和继承可以理解为是一个逆过程&#xff1a;泛化就是有子类抽象出一个父类&#xff0c;而继承就是由父类具体化一个…

程序员崩溃的N个瞬间

说到程序员&#xff0c;在外界眼里&#xff0c;他们是掌控代码的大神&#xff0c;他们是改变世界的王者。其实程序员的工作不容易&#xff0c;不信&#xff0c;就来看看程序员崩溃的各种瞬间—— 01、公司实习生找bug 02、在调试时&#xff0c;将断点设置在错误的位置 03、当我…

eventMesh 本地搭建记录

官方文档: Apache EventMesh (Incubating) | Apache EventMesh (作为整体了解 可以先看看架构) 按照官方文档需要搭建服务 eventmesh-store 文档推荐的是 rocketmq docker pull apache/rocketmq:4.9.4 部署rmq 的过程 1.nameServer docker run -d -p 9876:9876 -v pwd/d…

NECCS|全国大学生英语竞赛C类|词汇和语法|语法题|时态 非谓语动词 |19:00~20:15|完形填空·词性转化

14:35&#xff5e;14:45 15:45&#xff5e;16:2019:00&#xff5e;20:15 http://t.csdn.cn/XbsUy 目录 &#xff08;一&#xff09;时态 7. 将来进行时 8. 过去将来进行时 9. 现在完成时 10. 过去完成时​编辑 11. 将来完成时 12. 现在完成时 13. 过去完成进行时 &#xff08;…

David Silver Lecture 4: Model-Free Prediction

1 Introduction 任务&#xff1a;第三章使用动态规划方法&#xff0c;解决known的MDP问题&#xff0c;这章通过model free prediction对一个unknown的MDP估计他的value function。下一章通过Model free control的方法针对一个unknown的MDP optimise value function。 2 Monte…