【网络编程开发】11.IO模型 12.IO多路复用
11.IO模型
什么是IO:
IO 是 Input/Output 的缩写,指的是输入和输出。在计算机当中,IO 操作通常指将数据从一个设备或文件中读取到计算机内存中,或将内存中的数据写入设备或文件中。这些设备可以包括硬盘驱动器、网卡、键盘、屏幕等。
通常用户进程中的一个完整I/O分为两个阶段:
用户进程空间→内核空间
内核空间→设备空间
I/O分为内存I/O、网络I/O和磁盘I/O三种
IO操作的两个阶段
Linux中进程无法直接操作I/O设备,其必须通过系统调用请求内核来协助完成I/O操作。
内核会为每个I/O设备维护一个缓冲区。
对于一个输入操作来说,进程I/O系统调用后,内核会先看缓冲区中有没有相应的缓存数据,没有的话再到设备(比如网卡设备)中读取(因为设备I/O一般速度较慢,需要等待);
内核缓冲区有数据则直接复制到用户进程空间。
所以,对于一个网络输入操作通常包括两个不同阶段:
- 等待网络数据到达网卡,把数据从网卡读取到内核缓冲区,准备好数据。
- 从内核缓冲区复制数据到用户进程空间。
网络I/O的本质是对socket的读取,socket在Linux系统中被抽象为流,I/O可以理解为对流的操作。
网络I/O的模型可分为两种:
- 异步I/O(asynchronous I/O)
- 同步I/O(synchronous I/O)
同步I/O又包括
- 阻塞I/O(blocking I/O)
- 非阻塞I/O(non-blocking I/O)
- 多路复用I/O(multiplexing I/O)
- 信号驱动I/O(signal-driven I/O)
强调一下:信号驱动I/O属于同步I/O,原因往后看。
信号驱动I/O和异步I/O只作概念性的讲解,不作为学习重点。
五种I/O模型
阻塞I/O(blocking I/O)
对于一个套接字上的输入操作,第一步通常涉及等待数据从网络中到达,当所有等待分组到达时,它被复制到内核中的某个缓冲区。第二步是把数据从内核缓冲区复制到应用程序缓冲区。
同步阻塞I/O模型是最常用、最简单的模型。在Linux中,默认情况下,所有套接字都是阻塞的。下面我们以阻塞套接字的recvfrom的调用图来说明阻塞,如图所示
非阻塞I/O(non-blocking I/O)
非阻塞的recvform系统调用之后,进程并没有被阻塞,内核马上返回给进程,如果数据还没准备好,此时会返回一个error
(EAGAIN
或EWOULDBLOCK
)。
进程在返回之后,可以先处理其他的业务逻辑,稍后再发起recvform系统调用。
采用轮询的方式检查内核数据,直到数据准备好。再拷贝数据到进程,进行数据处理。
在Linux下,可以通过设置套接字选项使其变为非阻塞。非阻塞的套接字的recvfrom操作如图所示
可以看到前三次调用recvfrom请求时,并没有数据返回,内核返回errno
(EWOULDBLOCK
),并不会阻塞进程。
当第四次调用recvfrom时,数据已经准备好了,于是将它从内核空间拷贝到程序空间,处理数据。
在非阻塞状态下,I/O执行的等待阶段并不是完全阻塞的,但是第二个阶段依然处于一个阻塞状态(调用者将数据从内核拷贝到用户空间,这个阶段阻塞)。
多路复用I/O(multiplexing I/O)
I/O多路复用的好处在于单个进程就可以同时处理多个网络连接的I/O。它的基本原理是不再由应用程序自己监视连接,而由内核替应用程序监视文件描述符。
以select函数为例,当用户进程调用了select,那么整个进程会被阻塞,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好,select就会返回。
这个时候用户进程再调用read操作,将数据从内核拷贝到用户进程,如下图所示。
信号驱动I/O(signal-driven I/O)
该模型允许socket进行信号驱动I/O,并注册一个信号处理函数,进程继续运行并不阻塞。当数据准备好时,进程会收到一个SIGIO信号,可以在信号处理函数中调用I/O操作函数处理数据,如图所示
注意:虽然信号驱动IO在注册完信号处理函数以后,就可以做其他事情了。但是第二阶段拷贝数据的过程当中进程依然是被阻塞的,而后要介绍的异步IO是完全不会阻塞进程的,所以信号驱动虽然具有异步的特点,但依然属于异步IO
异步I/O(asynchronous I/O)
相对于同步I/O,异步I/O不是按顺序执行。用户进程进行aio_read
系统调用之后,就可以去处理其他逻辑了,无论内核数据是否准备好,都会直接返回给用户进程,不会对进程造成阻塞。这是因为aio_read
只向内核递交申请,并不关心有没有数据。
等到数据准备好了,内核直接复制数据到进程空间,然后内核向进程发送通知,此时数据已经在用户空间了,可以对数据进行处理。
五种I/O模型比较
前四种I/O模型都是同步I/O操作,它们的区别在于第一阶段,而第二阶段是一样的:在数据从内核复制到应用缓冲区期间(用户空间),进程阻塞于recvfrom调用。
相反,异步I/O模型在等待数据和接收数据的这两个阶段都是非阻塞的,可以处理其他的逻辑,用户进程将整个I/O操作交由内核完成,内核完成后会发送通知。在此期间,用户进程不需要检查I/O操作的状态,也不需要主动拷贝数据。
在了解了Linux的I/O模型之后,我们就可以进行服务器设计了。
12.IO多路复用
I/O多路复用的好处在于单个进程就可以同时处理多个网络连接的I/O。它的基本原理是不再由应用程序自己监视连接,而由内核替应用程序监视文件描述符。
在Linux系统中,select、poll和epoll是三种常用的I/O多路复用技术,它们用于处理多个I/O流,以实现高效的并发服务器设计。
特性 | select函数 | poll函数 | epoll函数族 |
---|---|---|---|
支持平台 | 几乎所有类Unix系统 | Unix及类Unix系统 | 主要为Linux系统 |
数据结构 | 位图(限制文件描述符数量) | 链表(无文件描述符数量限制) | 红黑树(高效管理事件) |
文件描述符限制 | 通常最多1024个 | 无限制 | 无限制 |
拷贝开销 | 每次调用时拷贝整个集合 | 每次调用时拷贝整个集合 | 通过回调机制,避免拷贝 |
返回就绪描述符 | 需要遍历所有描述符识别就绪状态 | 直接返回就绪状态的描述符 | 高效返回只包含就绪事件的描述符 |
并发性能 | 低至中等(因位图扫描) | 中等(因链表扫描) | 高(高效的事件通知和数据结构) |
触发模式 | 不支持 | 不支持 | 支持水平触发和边缘触发 |
API复杂度 | 简单直观 | 类似select但更灵活 | 功能丰富但使用相对复杂 |
适用场景 | 小规模并发服务器或客户端 | 中规模并发服务器 | 大规模并发服务器,尤其是网络服务 |
select函数
原型:
#include <sys/select.h> int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
功能:监视文件描述符集合,等待其中任意一个文件描述符准备好进行I/O操作。
参数:
nfds
:文件描述符集合中最大的文件描述符值加1。readfds
:需要监视可读状态的文件描述符集合。writefds
:需要监视可写状态的文件描述符集合。exceptfds
:需要监视异常状态的文件描述符集合。timeout
:设置select函数的超时时间。
- NULL,永久阻塞。
- 0,非阻塞。
返回值:
- 成功:返回准备好的文件描述符个数。
- 超时:返回0。
- 出错:返回-1,并设置errno。
fd_set
:
- 表示文件描述符集合的数据结构
- 在fd_set中,每个文件描述符都对应一个位,如果该位为1,则表示对应的文件描述符处于准备好的状态;如果该位为0,则表示对应的文件描述符未准备好。
- fd_set提供了一些宏操作来方便地对文件描述符集合进行操作:
FD_ZERO(fd_set *set)
:清空文件描述符集合。FD_SET(int fd, fd_set *set)
:将指定的文件描述符添加到集合中。FD_CLR(int fd, fd_set *set)
:从集合中移除指定的文件描述符。FD_ISSET(int fd, fd_set *set)
:检查指定的文件描述符是否在集合中。FD_COPY(fd_set *src, fd_set *dst)
:复制源文件描述符集合到目标文件描述符集合。
struct timeval
:
struct timeval {long tv_sec; /* 秒 */long tv_usec; /* 微秒 */ };
select实现多路复用
sever.c
#include "net.h"
#include <sys/select.h>
#define MAX_SOCK_FD 1024 // 定义最大文件描述符数量为1024int main(int argc, char *argv[])
{int i, ret, fd, newfd; fd_set set, tmpset; Addr_in clientaddr;socklen_t clientlen = sizeof(Addr_in); // 定义客户端地址长度clientlen/*检查参数,小于3个 直接退出进程*/Argment(argc, argv);/*创建已设置监听模式的套接字*/fd = CreateSocket(argv);FD_ZERO(&set);FD_ZERO(&tmpset);FD_SET(fd, &set);while(1){ tmpset = set;if( (ret = select(MAX_SOCK_FD, &tmpset, NULL, NULL, NULL)) < 0) // 调用select函数监听文件描述符集合tmpset中的文件描述符ErrExit("select"); if(FD_ISSET(fd, &tmpset) ){/*接收客户端连接,并生成新的文件描述符*/if( (newfd = accept(fd, (Addr *)&clientaddr, &clientlen) ) < 0) perror("accept");printf("[%s:%d]已建立连接\n", inet_ntoa(clientaddr.sin_addr), ntohs(clientaddr.sin_port)); // 输出客户端地址和端口号FD_SET(newfd, &set); // 将新的文件描述符加入文件描述符集合set}else{ for(i = fd + 1; i < MAX_SOCK_FD; i++){ if(FD_ISSET(i, &tmpset)){ if( DataHandle(i) <= 0){ if( getpeername(i, (Addr *)&clientaddr, &clientlen) )perror("getpeername"); printf("[%s:%d]断开连接\n", inet_ntoa(clientaddr.sin_addr),ntohs(clientaddr.sin_port)); // 输出客户端地址和端口号FD_CLR(i, &set); // 从文件描述符集合set中移除文件描述符i}}}}}return 0; // 程序正常结束,返回0
}
socket.c
#include "net.h"void Argment(int argc, char *argv[]){if(argc < 3){fprintf(stderr, "%s<addr><port>\n", argv[0]);exit(0);}
}
int CreateSocket(char *argv[]){/*创建套接字*/int fd = socket(AF_INET, SOCK_STREAM, 0);if(fd < 0)ErrExit("socket");/*允许地址快速重用*/int flag = 1;if( setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag) ) )perror("setsockopt");/*设置通信结构体*/Addr_in addr;bzero(&addr, sizeof(addr) );addr.sin_family = AF_INET;addr.sin_port = htons( atoi(argv[2]) );/*绑定通信结构体*/if( bind(fd, (Addr *)&addr, sizeof(Addr_in) ) )ErrExit("bind");/*设置套接字为监听模式*/if( listen(fd, BACKLOG) )ErrExit("listen");return fd;
}
int DataHandle(int fd){char buf[BUFSIZ] = {};Addr_in peeraddr;socklen_t peerlen = sizeof(Addr_in);if( getpeername(fd, (Addr *)&peeraddr, &peerlen) )perror("getpeername");int ret = recv(fd, buf, BUFSIZ, 0);if(ret < 0)perror("recv");if(ret > 0){printf("[%s:%d]data: %s\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port), buf);}return ret;
}
net.h
#ifndef _NET_H_
#define _NET_H_#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <strings.h>
#include <errno.h>typedef struct sockaddr Addr;
typedef struct sockaddr_in Addr_in;
#define BACKLOG 5
#define ErrExit(msg) do { perror(msg); exit(EXIT_FAILURE); } while(0)void Argment(int argc, char *argv[]);
int CreateSocket(char *argv[]);
int DataHandle(int fd);#endif
成功建立连接
poll 函数
与select函数的功能类似
原型:
#include <poll.h> int poll(struct pollfd *fds, nfds_t nfds, int timeout);
功能:监视一组文件描述符的I/O状态,等待它们中的一个或多个变为可读、可写或异常状态。
参数:
fds
:一个指向pollfd
结构体数组的指针,该数组包含了需要监视的文件描述符及其对应的事件。nfds
:fds
数组中的元素个数。timeout
:等待的最长时间(以毫秒为单位),如果设置为0,则表示立即返回;如果设置为负数,则表示无限期等待。返回值:
- 如果成功,返回发生事件的文件描述符个数;
- 如果超时,返回0;
- 如果出错,返回-1。
struct pollfd
:
struct pollfd {int fd; // 文件描述符short events; // 注册的事件short revents; // 返回的事件 };
fd
:这是文件描述符,即需要被监视的句柄。
events
:这是一个位掩码,定义了我们关心的文件描述符的事件类型。常用的事件类型有:
POLLIN
:表示文件描述符可读。POLLOUT
:表示文件描述符可写。POLLPRI
:表示文件描述符有紧急数据(带外数据)可读。POLLERR
:表示文件描述符发生错误。POLLHUP
:表示文件描述符挂起。
revents
:这是一个输出参数,当poll返回时,它指出了文件描述符上实际发生了哪些事件。
nfds_t
:
typedef unsigned long int nfds_t;
poll实现多路复用
sever.c
#include "net.h"
#include <poll.h>#define MAX_SOCK_FD 1024
int main(int argc, char *argv[])
{int i, j, fd, newfd;nfds_t nfds = 1;struct pollfd fds[MAX_SOCK_FD] = {};Addr_in addr;socklen_t addrlen = sizeof(Addr_in);/*检查参数,小于3个 直接退出进程*/Argment(argc, argv);/*创建已设置监听模式的套接字*/fd = CreateSocket(argv);fds[0].fd = fd;fds[0].events = POLLIN;while(1){if( poll(fds, nfds, -1) < 0)ErrExit("poll");for(i = 0; i < nfds; i++){/*接收客户端连接,并生成新的文件描述符*/if(fds[i].fd == fd && fds[i].revents & POLLIN){if( (newfd = accept(fd, (Addr *)&addr, &addrlen) ) < 0)perror("accept");fds[nfds].fd = newfd;fds[nfds++].events = POLLIN;printf("[%s:%d][nfds=%lu] connection successful.\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port), nfds);}/*处理客户端数据*/if(i > 0 && fds[i].revents & POLLIN){if(DataHandle(fds[i].fd) <= 0){if( getpeername(fds[i].fd, (Addr *)&addr, &addrlen) < 0)perror("getpeername");printf("[%s:%d][fd=%d] exited.\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port), fds[i].fd);close(fds[i].fd);for(j=i; j<nfds-1; j++)fds[j] = fds[j+1];nfds--;i--;}}}}close(fd);return 0;
}
socket.c
#include "net.h"void Argment(int argc, char *argv[]){if(argc < 3){fprintf(stderr, "%s<addr><port>\n", argv[0]);exit(0);}
}
int CreateSocket(char *argv[]){/*创建套接字*/int fd = socket(AF_INET, SOCK_STREAM, 0);if(fd < 0)ErrExit("socket");/*允许地址快速重用*/int flag = 1;if( setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag) ) )perror("setsockopt");/*设置通信结构体*/Addr_in addr;bzero(&addr, sizeof(addr) );addr.sin_family = AF_INET;addr.sin_port = htons( atoi(argv[2]) );/*绑定通信结构体*/if( bind(fd, (Addr *)&addr, sizeof(Addr_in) ) )ErrExit("bind");/*设置套接字为监听模式*/if( listen(fd, BACKLOG) )ErrExit("listen");return fd;
}
int DataHandle(int fd){char buf[BUFSIZ] = {};Addr_in peeraddr;socklen_t peerlen = sizeof(Addr_in);if( getpeername(fd, (Addr *)&peeraddr, &peerlen) )perror("getpeername");int ret = recv(fd, buf, BUFSIZ, 0);if(ret < 0)perror("recv");if(ret > 0){printf("[%s:%d]data: %s\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port), buf);}return ret;
}
net.h
#ifndef _NET_H_
#define _NET_H_#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <strings.h>
#include <errno.h>typedef struct sockaddr Addr;
typedef struct sockaddr_in Addr_in;
#define BACKLOG 5
#define ErrExit(msg) do { perror(msg); exit(EXIT_FAILURE); } while(0)void Argment(int argc, char *argv[]);
int CreateSocket(char *argv[]);
int DataHandle(int fd);#endif
成功实现多路复用
epoll函数族
epoll
函数族用于高效的I/O事件管理,特别适用于高并发服务器应用
头文件: #include <sys/epoll.h>
-
epoll_create:
-
int epoll_create(int size);
-
功能:创建一个epoll实例,并返回一个文件描述符作为该epoll实例的标识。
-
参数
size
:之前用于定义事件队列的大小,但在Linux 2.6以后的版本中已被忽略。通常设置为0。 -
返回值:
- 成功:返回一个非负的文件描述符。
- 失败:返回-1,并设置
errno
。
-
-
epoll_ctl:
-
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
-
功能:向epoll实例中添加、修改或删除文件描述符及其相关事件。
-
参数:
epfd
:epoll实例的文件描述符。op
:操作类型,可以是EPOLL_CTL_ADD
(添加新的文件描述符)、EPOLL_CTL_MOD
(修改已注册的文件描述符的事件)或EPOLL_CTL_DEL
(删除一个文件描述符)。fd
:要操作的文件描述符。event
:指向epoll_event
结构的指针,用于指定事件类型和文件描述符的数据。
-
返回值:
- 成功:返回0。
- 失败:返回-1,并设置
errno
。
-
-
epoll_wait:
-
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
-
功能:阻塞等待已注册的文件描述符上的事件发生。
-
参数:
epfd
:epoll实例的文件描述符。events
:指向epoll_event
结构体数组的指针,用于存储发生的事件。maxevents
:可以接收的事件数量的最大值。timeout
:超时时间(以毫秒为单位),决定函数的阻塞行为。设置为0立即返回,设置为-1则无限期阻塞。
-
返回值:
- 成功:返回就绪事件的个数。
- 超时:返回0。
- 失败:返回-1,并设置
errno
。
-
epoll实现多路复用
sever.c
#include "net.h"
#include <sys/epoll.h>#define MAX_SOCK_FD 1024int main(int argc, char *argv[])
{int i, nfds, fd, epfd, newfd;Addr_in addr;socklen_t addrlen = sizeof(Addr_in);struct epoll_event tmp, events[MAX_SOCK_FD] = {};/*检查参数,小于3个 直接退出进程*/Argment(argc, argv);/*创建已设置监听模式的套接字*/fd = CreateSocket(argv);if( (epfd = epoll_create(1)) < 0)ErrExit("epoll_create");tmp.events = EPOLLIN;tmp.data.fd = fd;if( epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &tmp) )ErrExit("epoll_ctl");while(1) {if( (nfds = epoll_wait(epfd, events, MAX_SOCK_FD, -1) ) < 0)ErrExit("epoll_wait");printf("nfds = %d\n", nfds);for(i = 0; i < nfds; i++) {if(events[i].data.fd == fd){/*接收客户端连接,并生成新的文件描述符*/if( (newfd = accept(fd, (Addr *)&addr, &addrlen) ) < 0)perror("accept");printf("[%s:%d] connection.\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port) );tmp.events = EPOLLIN;tmp.data.fd = newfd;if( epoll_ctl(epfd, EPOLL_CTL_ADD, newfd, &tmp) )ErrExit("epoll_ctl");}else{/*处理客户端数据*/if(DataHandle(events[i].data.fd) <= 0){if( epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, NULL) )ErrExit("epoll_ctl");if( getpeername(events[i].data.fd, (Addr *)&addr, &addrlen) )perror("getpeername");printf("[%s:%d] exited.\n", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port) );close(events[i].data.fd);}}}}close(epfd);close(fd);return 0;
}
socket.c
#include "net.h"void Argment(int argc, char *argv[]){if(argc < 3){fprintf(stderr, "%s<addr><port>\n", argv[0]);exit(0);}
}
int CreateSocket(char *argv[]){/*创建套接字*/int fd = socket(AF_INET, SOCK_STREAM, 0);if(fd < 0)ErrExit("socket");/*允许地址快速重用*/int flag = 1;if( setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag) ) )perror("setsockopt");/*设置通信结构体*/Addr_in addr;bzero(&addr, sizeof(addr) );addr.sin_family = AF_INET;addr.sin_port = htons( atoi(argv[2]) );/*绑定通信结构体*/if( bind(fd, (Addr *)&addr, sizeof(Addr_in) ) )ErrExit("bind");/*设置套接字为监听模式*/if( listen(fd, BACKLOG) )ErrExit("listen");return fd;
}
int DataHandle(int fd){char buf[BUFSIZ] = {};Addr_in peeraddr;socklen_t peerlen = sizeof(Addr_in);if( getpeername(fd, (Addr *)&peeraddr, &peerlen) )perror("getpeername");int ret = recv(fd, buf, BUFSIZ, 0);if(ret < 0)perror("recv");if(ret > 0){printf("[%s:%d]data: %s\n", inet_ntoa(peeraddr.sin_addr), ntohs(peeraddr.sin_port), buf);}return ret;
}
net.h
#ifndef _NET_H_
#define _NET_H_#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <strings.h>
#include <errno.h>typedef struct sockaddr Addr;
typedef struct sockaddr_in Addr_in;
#define BACKLOG 5
#define ErrExit(msg) do { perror(msg); exit(EXIT_FAILURE); } while(0)void Argment(int argc, char *argv[]);
int CreateSocket(char *argv[]);
int DataHandle(int fd);#endif
相关文章:

【网络编程开发】11.IO模型 12.IO多路复用
11.IO模型 什么是IO: IO 是 Input/Output 的缩写,指的是输入和输出。在计算机当中,IO 操作通常指将数据从一个设备或文件中读取到计算机内存中,或将内存中的数据写入设备或文件中。这些设备可以包括硬盘驱动器、网卡、键盘、屏幕等。 通常用…...

elementui Menu 二级菜单 min-width修改无效
原因:可能是生成的二级菜单样式里面没有带特定的hash属性 而vue代码里面样式里带了 scoped生成的样式有改样式选择器 从而无法成功选择 解决:让样式可以全局选择 不带属性选择器 单文件组件 CSS 功能 | Vue.js :global(.el-menu--vertical .el-menu--p…...

字符串拼接之char实现
目录 一、前言 二、memcpy函数用法 三、代码实现 一、前言 c中想到字符串拼接,我们都知道可以用c库中std::string的字符串中的简单加法进行拼接。示例: int main() {std::string str1 "hello";std::string str2 "World";std::…...

教育的数字化转型——Kompas.ai如何变革学习体验
引言 在现代教育中,数字化转型逐渐成为提升学习效果的重要手段。随着科技的进步,人工智能(AI)在教育领域的应用越来越广泛。本文将探讨教育数字化转型的发展趋势,并介绍Kompas.ai如何通过AI技术变革学习体验。 教育数…...

域内攻击 ----> DCSync
其实严格意义上来说DCSync这个技术,并不是一种横向得技术,而是更偏向于权限维持吧! 但是其实也是可以用来横向(配合NTLM Realy),如果不牵强说得话! 那么下面,我们就来看看这个DCSyn…...

前端 JS 经典:动态执行 JS
前言:怎么将字符串当代码执行。有 4 中方式实现 eval、setTimeout、创建 script 标签、new Function 1. eval 特点:同步执行,当前作用域 var name "yq"; function exec(string) {var name "yqcoder";eval(string); …...
Laravel学习-模型注入
一. 在定义路由的时候,可以在闭包函数里引入模型, Route.get(/api/user/{user:id}, function(\App\Model\UserModel $user) {return value; }) 其中:id可以省略不写,这个意思是,请求该接口时,会UserMode…...

Django模板的使用(详细版)
1、配置 在工程中创建模板目录templates(这个名字可以变!!) 在settings.py配置文件中修改TEMPLATES配置项的DIRS值 2、定义模板 在templates目录中新建一个模板文件,如index.html 3、模板渲染 Django提供了一个函数…...
正则表达式基础
正则表达式基础 一、初识正则表达式 思考–问题答案在文末 问题1:text1 ‘height:178,weight:168,sid:123456,passwd:9527’,如何快速找出密码?问题2:text2 ‘他的电话…...

【Linux】进程间通信之命名管道
👦个人主页:Weraphael ✍🏻作者简介:目前正在学习c和算法 ✈️专栏:Linux 🐋 希望大家多多支持,咱一起进步!😁 如果文章有啥瑕疵,希望大佬指点一二 如果文章对…...

【内存管理】页表映射
页表的一些术语 现在Linux内核中支持四级页表的映射,我们先看下内核中关于页表的一些术语: 全局目录项,PGD(Page Global Directory) 上级目录项,PUD(Page Upper Directory) 中间目…...

Cloudpods 强大的多云管理平台部署
简介 Cloudpods 是一款简单、可靠的企业IaaS资源管理软件。帮助未云化企业全面云化IDC物理资源,提升企业IT管理效率。 Cloudpods 帮助客户在一个地方管理所有云计算资源。统一管理异构IT基础设施资源,极大简化多云架构复杂度和难度,帮助企业…...
深度学习的可微渲染
深度学习的可微渲染 可微渲染(Differentiable Rendering)是深度学习领域的一个重要概念,它将传统的计算机图形学与深度学习结合起来,通过使渲染过程可微分(differentiable),以便于在深度学习模…...

CAS Server Restful接口实现后台认证
背景 对于一些比较复杂定制化登录页的情况下,之前提到过可以自定义修改使用CAS Server提供的登录页这种操作已经明显跟不上复杂定制场景了,所以CAS Server也提供了支持Restful接口,支持服务端后台登陆,对于复杂登陆场景时&#x…...

Linux shell编程学习笔记58:cat /proc/mem 获取系统内存信息
0 前言 在开展系统安全检查的过程中,除了收集cpu信息,我们还需要收集内存信息。在Linux中,获取内存信息的命令很多,这里我们着重研究 cat /proc/mem命令。 1 cat /proc/mem命令 /proc/meminfo 文件提供了有关系统内存的使用情况…...

【InternLM实战营第二期笔记】07:OpenCompass :是骡子是马,拉出来溜溜
文章目录 课程实操 课程 评测的意义是什么呢?我最近也在想。看到这节开头的内容后忽然有个顿悟:如果大模型最终也会变成一种基础工具(类比软件),稳定或可预期的效果需要先于用户感知构建出来,评测 case 就…...

matlab演示银河系转动动画
代码 function GalaxyRotationSimulation()% 参数设置num_stars 1000; % 恒星数量galaxy_radius 1; % 银河系半径rotation_speed 0.05; % 旋转速度% 生成银河系中的恒星分布theta 2 * pi * rand(num_stars, 1); % 角度r galaxy_radius * sqrt(rand(num_stars, 1)); % 半径…...

备战 清华大学 上机编程考试-冲刺前50%,倒数第5天
T1:多项式求和 小K最近刚刚习得了一种非常酷炫的多项式求和技巧,可以对某几类特殊的多项式进行运算。非常不幸的是,小K发现老师在布置作业时抄错了数据,导致一道题并不能用刚学的方法来解,于是希望你能帮忙写一个程序…...
leetCode127. 单词接龙
leetCode127. 单词接龙 // bfs 剪枝 class Solution { public:int ladderLength(string beginWord, string endWord, vector<string>& wordList) {// 1.将所有的单词放在set字段中unordered_set<string> s;for (auto & ele : wordList) s.insert(ele);//…...

进程概念(二)
目录 进程优先级基本概念查看系统进程PRI and NIPRI vs NI修改进程优先级的命令renice修改优先级进程其他概念 环境变量基本概念查看环境变量方法常见环境变量PATHHOMESHELL 查看环境变量环境变量相关的命令 环境变量特征命令行参数main函数中的俩个参数 argc argvmain函数的第…...

7.4.分块查找
一.分块查找的算法思想: 1.实例: 以上述图片的顺序表为例, 该顺序表的数据元素从整体来看是乱序的,但如果把这些数据元素分成一块一块的小区间, 第一个区间[0,1]索引上的数据元素都是小于等于10的, 第二…...
电脑插入多块移动硬盘后经常出现卡顿和蓝屏
当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时,可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案: 1. 检查电源供电问题 问题原因:多块移动硬盘同时运行可能导致USB接口供电不足&#x…...
【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具
第2章 虚拟机性能监控,故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令:jps [options] [hostid] 功能:本地虚拟机进程显示进程ID(与ps相同),可同时显示主类&#x…...
JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案
JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停 1. 安全点(Safepoint)阻塞 现象:JVM暂停但无GC日志,日志显示No GCs detected。原因:JVM等待所有线程进入安全点(如…...
Swagger和OpenApi的前世今生
Swagger与OpenAPI的关系演进是API标准化进程中的重要篇章,二者共同塑造了现代RESTful API的开发范式。 本期就扒一扒其技术演进的关键节点与核心逻辑: 🔄 一、起源与初创期:Swagger的诞生(2010-2014) 核心…...
Rapidio门铃消息FIFO溢出机制
关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系,以下是深入解析: 门铃FIFO溢出的本质 在RapidIO系统中,门铃消息FIFO是硬件控制器内部的缓冲区,用于临时存储接收到的门铃消息(Doorbell Message)。…...

Chromium 136 编译指南 Windows篇:depot_tools 配置与源码获取(二)
引言 工欲善其事,必先利其器。在完成了 Visual Studio 2022 和 Windows SDK 的安装后,我们即将接触到 Chromium 开发生态中最核心的工具——depot_tools。这个由 Google 精心打造的工具集,就像是连接开发者与 Chromium 庞大代码库的智能桥梁…...
前端高频面试题2:浏览器/计算机网络
本专栏相关链接 前端高频面试题1:HTML/CSS 前端高频面试题2:浏览器/计算机网络 前端高频面试题3:JavaScript 1.什么是强缓存、协商缓存? 强缓存: 当浏览器请求资源时,首先检查本地缓存是否命中。如果命…...

针对药品仓库的效期管理问题,如何利用WMS系统“破局”
案例: 某医药分销企业,主要经营各类药品的批发与零售。由于药品的特殊性,效期管理至关重要,但该企业一直面临效期问题的困扰。在未使用WMS系统之前,其药品入库、存储、出库等环节的效期管理主要依赖人工记录与检查。库…...

基于开源AI智能名片链动2 + 1模式S2B2C商城小程序的沉浸式体验营销研究
摘要:在消费市场竞争日益激烈的当下,传统体验营销方式存在诸多局限。本文聚焦开源AI智能名片链动2 1模式S2B2C商城小程序,探讨其在沉浸式体验营销中的应用。通过对比传统品鉴、工厂参观等初级体验方式,分析沉浸式体验的优势与价值…...