当前位置: 首页 > news >正文

C++网络编程(三)IO复用

C++网络编程(三)IO复用

前言

多进程/多线程网络服务端在创建进程/线程时,CPU和内存开销很大。因为多线程/进程并发模型,为每个socket分配一个线程/进程。而IO复用采用单个的进程/线程就可以管理多个socket。

select

系统调用原型:

#include <sys/select.h>
int select(int nfds,fd_set*readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout);

ndfs参数指定被监听的文件描述符总数,通常设置为select监听的所有文件描述符中最大值+1,因为文件描述符从0开始编号。

readfds,writefds,exceptfds参数指向可读,可写,异常等事件对应的文件描述符集合。他们将各自感兴趣的文件描述符传入函数,当select调用返回时,内核将修改他们来通知程序哪些文件描述符已经就绪。

#include <typesizes.h>
#define __FD_SETSIZE 1024
#include <sys/select.h>
#define FD_SETSIZE __FD_SETSIZE
typedef long int __fd_mask;
#undef __NFDBITS
#define __NFDBITS (8*(int)sizeof(__fd_mask))
typedef struct
{
#ifdef __USE_XOPEN__fd_mask fds_bits[__FD_SETSIZE/__NFDBITS];#define __FDS_BITS (set)((set)->fds_bits)
#else__fd_mask __fds_bits[__FD_SETSIZE/__NFDBITS];#define __FDS_BITS (set)((set)->__fds_bits)
#endif
}fd_set;

fd_set结构体仅包含一个整型数组,该数组的每个元素的每一位(bit)标记一个文件描述符。fd_set能容纳的文件描述符数量由FD_SETSIZE指定。

对于fd_set结构体中的位操作,有设置对应的宏进行处理:

#include <sys/select.h>
FD_ZERO(fd_set* fdset);/*清除fdset的所有位*/
FD_SET(int fd, fd_set* fdset);/*设置fdset的位fd*/
FD_CLR(int fd, fd_set* fdset);/*清除fdset的位fd*/
int FD_ISSET(int fd, fd_set* fdset);/*测试fdset的位fd是否被设置*/

timeout参数用来设置select的超时时间。它是一个timeval结构类型的指针,采用指针参数是因为内核将修改它以告诉应用程序select等待了多久。不过我们不能完全信任select调用返回后的timeout值,比如调用失败时timeout值是不确定的。timeval结构体的定义如下:

struct timeval
{long tv_sec;/*秒数*/long tv_usec;/*微秒数*/
};

如果给timeout变量的tv_sec成员和tv_usec成员都传递0,则select将立即返回。如果给timeout传递NULL,则select将一直阻塞,直到某个文件描述符就绪。

select成功时返回就绪文件描述符的总数。失败返回-1并设置errno。如果是在select等待期间,程序接收到信号,则select立即返回-1,并设置errno为EINTR。

select流程

请添加图片描述

文件描述符就绪条件

在网络编程中,下列情况下socket可读:

  • socket内核接收缓存区中的字节数大于或等于其低水位标记SO_RCVLOWAT。此时我们可以无阻塞地读该socket,并且读操作返回的字节数大于0。
  • socket通信的对方关闭连接。此时对该socket的读操作将返回0。
  • 监听socket上有新的连接请求。
  • socket上有未处理的错误。此时我们可以使用getsockopt来读取和清除该错误。

下列情况下socket可写:

  • socket内核发送缓存区中的可用字节数大于或等于其低水位标记SO_SNDLOWAT。此时我们可以无阻塞地写该socket,并且写操作返回的字节数大于0。
  • socket的写操作被关闭。对写操作被关闭的socket执行写操作将触发一个SIGPIPE信号。
  • socket使用非阻塞connect连接成功或者失败(超时)之后。
  • socket上有未处理的错误。此时我们可以使用getsockopt来读取和清除该错误。

网络程序中,select能处理的异常情况只有一种:socket上接收到带外数据。

位图

select中对文件描述符是否有改动是用位图进行标记的,即用一位表示一个文件描述符,当其中某一位发生改变时,说明对应的文件描述符有相应的事件发生。但值得注意的是,这一次改变后,在下一轮的select调用前需要先将其重置。

示例代码

//server.cpp
#include <bits/stdc++.h>
#include <cstring>
#include <errno.h>
#include <netinet/in.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/types.h>using namespace std;int main()
{int sock = socket(AF_INET, SOCK_STREAM, 0);if (sock == -1){cout << "创建socket失败:" << strerror(errno) << endl;exit(0);}sockaddr_in addr;bzero(&addr, 0);addr.sin_family = AF_INET;addr.sin_addr.s_addr = htonl(INADDR_ANY); // 自动获取主机ipaddr.sin_port = htons(8000);int ret;ret = bind(sock, (struct sockaddr *)&addr, sizeof(addr));if (ret == -1){cout << "绑定socket失败:" << strerror(errno) << endl;exit(0);}ret = listen(sock, 10);if (ret == -1){cout << "监听socket失败:" << strerror(errno) << endl;exit(0);}cout << "初始化完成" << endl;fd_set readset;char buff[4096];list<int> sock_list;while (1){FD_ZERO(&readset);FD_SET(sock, &readset);//设置监听socketint nfds = sock;for (int it : sock_list) //计算最大的fd值{nfds = max(nfds, it);FD_SET(it, &readset);//}ret = select(nfds + 1, &readset, NULL, NULL, NULL);if (ret == -1){cout << strerror(errno) << endl;break;}else{if (FD_ISSET(sock, &readset)) // 有新的连接请求{int clientfd = accept(sock, NULL, 0);cout << "new connect: " << clientfd << endl;sock_list.push_back(clientfd);}else{for (auto fd : sock_list) // 轮询所有的连接{if (FD_ISSET(fd, &readset)){int len = recv(fd, buff, 4096, 0);if (len <= 0){sock_list.remove(fd);cout << fd << " exit" << endl;}else{buff[len] = 0;cout << "receive message:" << buff << endl;}//break;//注释掉break即一轮select中可能不止一个socket有事件发生,但即是不予注释,数据仍不会丢失,select采用的是电平触发的方式。}}}}}return 0;
}
//client.cpp
#include <arpa/inet.h>
#include <bits/stdc++.h>
#include <cstring>
#include <errno.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/types.h>using namespace std;int main()
{int sock = socket(AF_INET, SOCK_STREAM, 0);if (sock == -1){cout << "创建socket失败:" << strerror(errno) << endl;exit(0);}sockaddr_in addr;bzero(&addr, 0);addr.sin_family = AF_INET;addr.sin_addr.s_addr = inet_addr("127.0.0.1");addr.sin_port = htons(8000);if (connect(sock, (sockaddr *)&addr, sizeof(addr)) == -1){cout << "连接失败:" << strerror(errno) << endl;exit(0);}cout << "连接成功" << endl;char buff[4096];while (1){cin >> buff;if (send(sock, buff, strlen(buff), 0) <= 0){cout << "发送失败" << endl;break;}cout << "发送成功" << endl;}close(sock);return 0;
}

poll

poll系统调用和select极其类似,也是在指定时间内轮询一定数量的文件描述符,以测试其中是否有就绪者。poll的原型如下:

#include <poll.h>
int poll(struct pollfd* fds, nfds_t nfds, int timeout);
  1. fds参数是一个pollfd结构体的数组,指定所有我们感兴趣的文件描述符上发生的可读、可写和异常等事件。pollfd结构体的定义如下:
struct pollfd
{int fd;/*文件描述符*/short events;/*注册的事件*/short revents;/*实际发生的事件,由内核填充*/
};

fd成员指定文件描述符;

events成员告诉poll监听fd上的哪些事件,它是一系列事件的按位或;

revents成员则由内核修改,以通知应用程序fd上实际发生了哪些事件。

请添加图片描述

  1. nfds参数指定被监听事件集合fds的大小。其类型nfds_t的定义如下:
typedef unsigned long int nfds_t;
  1. timeout参数指定poll的超时值,单位是毫秒。当timeout为-1时,poll调用将永远阻塞,直到某个事件发生;当timeout为0时,poll调用将立即返回。

poll系统调用的返回值的含义与select相同。

示例

//server.cpp
#include <bits/stdc++.h>
#include <cstring>
#include <errno.h>
#include <netinet/in.h>
#include <poll.h>
#include <sys/socket.h>
#include <sys/types.h>using namespace std;int main()
{int sock = socket(AF_INET, SOCK_STREAM, 0);if (sock == -1){cout << "创建socket失败:" << strerror(errno) << endl;exit(0);}sockaddr_in addr;bzero(&addr, 0);addr.sin_family = AF_INET;addr.sin_addr.s_addr = htonl(INADDR_ANY); // 自动获取主机ipaddr.sin_port = htons(8000);int ret;ret = bind(sock, (struct sockaddr *)&addr, sizeof(addr));if (ret == -1){cout << "绑定socket失败:" << strerror(errno) << endl;exit(0);}ret = listen(sock, 10);if (ret == -1){cout << "监听socket失败:" << strerror(errno) << endl;exit(0);}cout << "初始化完成" << endl;pollfd pfd[64];int nfds = 1;pfd[0].fd = sock;pfd[0].events = POLLIN;char buff[4096];while (1){int ret = poll(pfd, nfds, -1);if (ret == -1){cout << strerror(errno) << endl;break;}else{if (pfd[0].revents & (POLLIN)) // 有新的连接请求{int clientfd = accept(sock, NULL, 0);if (nfds < 64){cout << "new connect: " << clientfd << endl;pfd[nfds].fd = clientfd;pfd[nfds].events = POLLIN | POLLRDHUP;nfds++;}elseclose(clientfd);}else{for (int i = 1; i < nfds; i++){if (pfd[i].revents & POLLRDHUP) // 断开连接{cout << pfd[i].fd << " exit." << endl;pfd[i] = pfd[nfds - 1];nfds--;break;}else if (pfd[i].revents & POLLIN) // 消息可读{int len = recv(pfd[i].fd, buff, 4096, 0);if (len <= 0){cout << pfd[i].fd << " exit." << endl;pfd[i] = pfd[nfds - 1];nfds--;}else{buff[len] = 0;cout << "receive message:" << buff << endl;}break;}}}}}return 0;
}

epoll

epoll是Linux特有的IO复用函数,与前面提到的select和poll在实现上有较大的差异。

  • epoll使用一组函数来完成任务,而非单个函数
  • epoll把用户关心的事件放进内核的一个事件表中,不用像select、poll那样每次都要重置,但epoll需要一个额外的文件描述符来标识内核中的事件表
#include <sys/epoll.h>
//创建指向内核事件表的文件描述符,size参数指示需要多大的事件表
int epoll_create(int size);
/*操作epoll的内核事件表
epfd参数: epoll_create返回的内核事件表描述符
fd参数: 要操作的文件描述符
op参数: 指定操作类型
event参数: 指定事件
*/
int epoll_ctl(int epfd,int op,int fd,struct epoll_event*event);

op参数的类型:

  • EPOLL_CTL_ADD: 往事件表里注册fd上的事件
  • EPOLL_CTL_MOD: 修改fd上的注册事件
  • EPOLL_CTL_DEL: 删除fd上的注册事件

event参数类型:

struct epoll_event
{__uint32_t events;/*epoll事件*/epoll_data_t data;/*用户数据*/
};

epoll支持的事件类型和poll基本相同。表示epoll事件类型的宏是在poll对应的宏前加上“E”,比如epoll的数据可读事件是EPOLLIN。但epoll有两个额外的事件类型——EPOLLET和EPOLLONESHOT。

data用于存储用户数据,其定义如下:

typedef union epoll_data
{void* ptr;int fd;uint32_t u32;uint64_t u64;
}epoll_data_t;

epoll_data_t是一个联合体,其4个成员中使用最多的是fd,它指定事件所从属的目标文件描述符。ptr成员可用来指定与fd相关的用户数据。

epoll_ctl成功时返回0,失败则返回-1并设置errno。

epoll系列系统调用的主要接口是epoll_wait函数。它在一段超时时间内等待一组文件描述符上的事件,其原型如下:

#include <sys/epoll.h>
/*
maxevents参数指定最多监听多少个事件,它必须大于0。
timeout参数的含义与poll接口的timeout参数相同。
*/
int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);

该函数成功时返回就绪的文件描述符的个数,失败时返回-1并设置errno.

epoll_wait函数如果检测到事件,就将所有就绪的事件从内核事件表(由epfd参数指定)中复制到它的第二个参数events指向的数组中。这个数组只用于输出epoll_wait检测到的就绪事件,而不像select和poll的数组参数那样既用于传入用户注册的事件,又用于输出内核检测到的就绪事件。这就极大地提高了应用程序索引就绪文件描述符的效率。

EPOLL 的ET和LT模式

epoll对文件描述符的操作有两种模式:LT(Level Trigger,电平触发)模式和ET(Edge Trigger,边沿触发)模式。当然这是与硬件无关的,只是模拟效果。

默认工作模式是LT,与poll和select一致,这种情况下相当于一个更高效的poll。当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序可以不立即处理该事件。这样,当应用程序下一次调用epoll_wait时,epoll_wait还会再次向应用程序通告此事件,直到该事件被处理。

采用ET模式,当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序必须立即处理该事件,因为后续的epoll_wait调用将不再向应用程序通知这一事件。可见,ET模式在很大程度上降低了同一个epoll事件被重复触发的次数,因此效率要比LT模式高。

注:每个使用ET模式的文件描述符都应该是非阻塞的。如果文件描述符是阻塞的,那么读或写操作将会因为没有后续的事件而一直处于阻塞状态。

EPOLLONESHOT事件

EPOLLONESHOT事件实现的是socket连接在任一时刻都只被一个线程处理。处理方式类似于加锁操作。

对于注册了EPOLLONESHOT事件的文件描述符,操作系统最多触发其上注册的一个可读、可写或者异常事件,且只触发一次,除非我们使用epoll_ctl函数重置该文件描述符上注册的EPOLLONESHOT事件。当一个线程在处理某个socket时,其他线程是不可能有机会操作该socket的。同样的,在线程处理完socket后,应该立即重置其EPOLLONESHOT事件,保证下次socket可读时,其EPOLLIN事件能被触发。

示例

#include <bits/stdc++.h>
#include <cstring>
#include <errno.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <sys/types.h>using namespace std;int main()
{int sock = socket(AF_INET, SOCK_STREAM, 0);if (sock == -1){cout << "创建socket失败:" << strerror(errno) << endl;exit(0);}sockaddr_in addr;bzero(&addr, 0);addr.sin_family = AF_INET;addr.sin_addr.s_addr = htonl(INADDR_ANY); // 自动获取主机ipaddr.sin_port = htons(8000);int ret;ret = bind(sock, (struct sockaddr *)&addr, sizeof(addr));if (ret == -1){cout << "绑定socket失败:" << strerror(errno) << endl;exit(0);}ret = listen(sock, 10);if (ret == -1){cout << "监听socket失败:" << strerror(errno) << endl;exit(0);}cout << "初始化完成" << endl;char buff[4096];int epfd = epoll_create(64);epoll_event events[64], tmpevent;tmpevent.events = EPOLLIN;tmpevent.data.fd = sock;epoll_ctl(epfd, EPOLL_CTL_ADD, sock, &tmpevent);//添加监听socket可读事件while (1){int ret = epoll_wait(epfd, events, 64, -1);if (ret == -1){cout << strerror(errno) << endl;exit(0);}else{//遍历就绪的事件for (int i = 0; i < ret; i++){if (events[i].data.fd == sock) //新的连接{int clientfd = accept(sock, NULL, 0);tmpevent.events = EPOLLIN | EPOLLRDHUP | EPOLLET | EPOLLONESHOT;tmpevent.data.fd = clientfd;epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &tmpevent);cout << "new connect: " << clientfd << endl;}else{//断开连接时会同时触发EPOLLRDHUP和EPOLLIN,因此先判断EPOLLRDHUPif (events[i].events & EPOLLRDHUP){cout << events[i].data.fd << " exit" << endl;close(events[i].data.fd);epoll_ctl(epfd, EPOLL_CTL_DEL, events[i].data.fd, NULL);}else if (events[i].events & EPOLLIN){while (1){int len = recv(events[i].data.fd, buff, 4096, MSG_DONTWAIT);if (len <= 0) break;buff[len] = 0;cout << "receive message:" << buff << endl;}//EPOLLONESHOT模式下处理完后必须重置,否则下次不能触发tmpevent.events = EPOLLIN | EPOLLRDHUP | EPOLLET | EPOLLONESHOT;tmpevent.data.fd = events[i].data.fd;epoll_ctl(epfd, EPOLL_CTL_MOD, events[i].data.fd, &tmpevent);}}}}}return 0;
}

应用:非阻塞connect

将socket设置成非阻塞:

int setnonblocking(int fd)
{int old_option = fcntl(fd, F_GETFL);//获取文件状态标记int new_option = old_option | O_NONBLOCK;//加上非阻塞状态fcntl(fd, F_SETFL, new_option);//设置文件状态标记return old_option;
}

使用非阻塞的socket进行connect操作时,如果连接没有立即建立,会返回EINPROGRESS的错误。在这种情况下,我们可以调用select、poll等函数来监听这个连接失败的socket上的可写事件。当select、poll等函数返回后,再利用getsockopt来读取错误码并清除该socket上的错误。如果错误码是0,表示连接成功建立,否则连接失败。

使用非阻塞的socket可以同时对多个socket进行connect操作,然后对没有立即建立连接的socket使用select、poll等函数来监听,提高连接的效率。

不过,该方法仍存在某些移植性问题,比如connect始终失败,select对EINPROGRESS状态下的socket可能不起作用,以及某些版本的getsockopt出现的返回值并不相同。

相关文章:

C++网络编程(三)IO复用

C网络编程(三)IO复用 前言 多进程/多线程网络服务端在创建进程/线程时&#xff0c;CPU和内存开销很大。因为多线程/进程并发模型&#xff0c;为每个socket分配一个线程/进程。而IO复用采用单个的进程/线程就可以管理多个socket。 select 系统调用原型&#xff1a; #includ…...

第十四届蓝桥杯(第三期)模拟赛试题与题解 C++

第十四届蓝桥杯&#xff08;第三期&#xff09;模拟赛试题与题解 C 试题 A 【问题描述】 请找到一个大于 2022 的最小数&#xff0c;这个数转换成十六进制之后&#xff0c;所有的数位&#xff08;不含前导 0&#xff09;都为字母&#xff08;A 到 F&#xff09;。  请将这个…...

【Hive 基础】-- 数据倾斜

1.什么是数据倾斜&#xff1f;由于数据分布不均匀&#xff0c;导致大量数据集中到一点&#xff0c;造成数据热点。常见现象&#xff1a;一个 hive sql 有100个 map/reducer task&#xff0c; 有一个运行了 20分钟&#xff0c;其他99个 task 只运行了 1分钟。2.产生数据倾斜的原…...

计算机网络笔记——物理层

计算机网络笔记——物理层2. 物理层2.1 通信基础2.1.1 信号2.1.2 信源、信道及信宿2.1.3 速率、波特及码元2.1.4 带宽2.1.5 奈奎斯特定理采样定理奈奎斯特定理2.1.6 香农定理2.1.7 编码与调制调制数字信号调制为模拟信号模拟数据调制为模拟信号编码数字数据编码为数字信号模拟数…...

算法第十七期——状态规划(DP)之动态压缩

一、总述 状态压缩动态规划&#xff0c;就是我们俗称的状压DP&#xff0c;是利用计算机二进制的性质来描述状态的一种DP方式。 应用背景&#xff1a;以集合为状态&#xff0c;且集合可以用二进制来表示&#xff0c;用二进制的位运算来处理。集合问题一般是指数复杂度的&#x…...

2022年全国职业院校技能大赛(中职组)网络安全竞赛试题A模块第八套解析(详细)

2022年全国职业院校技能大赛(中职组) 网络安全竞赛试题 (8) (总分100分) 赛题说明 一、竞赛项目简介 “网络安全”竞赛共分A.基础设施设置与安全加固;B.网络安全事件响应、数字取证调查和应用安全;C.CTF夺旗-攻击;D.CTF夺旗-防御等四个模块。根据比赛实际情况,竞…...

【华为OD机试真题 JAVA】数组中是否存在满足规则的数字组合

标题:数组中是否存在满足规则的数字组合 | 时间限制:1秒 | 内存限制:262144K | 语言限制:不限 给定一个正整数数组,检查数组中是否存在满足规则的数字组合 * 规则: * A = B + 2C 输入描述: * 第一行输出数组的元素个数。 * 接下来一行输出所有数组元素,用空格…...

【OpenCV技能树】——OpenCV基础

前言&#xff1a; &#x1f60a;&#x1f60a;&#x1f60a;欢迎来到本博客&#x1f60a;&#x1f60a;&#x1f60a; 目前正在进行 OpenCV技能树的学习&#xff0c;OpenCV是学习图像处理理论知识比较好的一个途径&#xff0c;至少比看书本来得实在。本专栏文章主要记录学习Op…...

人体姿态识别

自留记录论文阅读,希望能了解我方向的邻域前沿吧 粗读,持续更新 第一篇 ATTEND TO WHO YOU ARE: SUPERVISING SELF-ATTENTION FOR KEYPOINT DETECTION AND INSTANCE-AWARE ASSOCIATION 翻译:https://editor.csdn.net/md?not_checkout=1&spm=1001.2014.3001.5352&…...

ubuntu下调试驱动

使用 Ubuntu Linux 测试 Linux 驱动 1. 测试 Linux 驱动准备工作 ​ 对于一个 Linux 驱动程序&#xff0c;一开始可以在 Ubuntu Linux 上做前期开发和测试。对于访问硬件部分也可以在 Ubuntu Linux 用软件进行模拟,切记不能代替真实的环境&#xff01;当基本开发完成后&#…...

第十四届蓝桥杯三月真题刷题训练——第 9 天

第 1 题&#xff1a;找素数 题目描述 本题为填空题&#xff0c;只需要算出结果后&#xff0c;在代码中使用输出语句将所填结果输出即可。 素数就是不能再进行等分的整数。比如&#xff1a;7&#xff0c;11。而 9 不是素数&#xff0c;因为它可以平分为 3 等份。一般认为最小的…...

操作系统复习

熟练掌握操作系统的定义&#xff0c;操作系统的特征&#xff0c;操作系统的功能熟练掌握多道程序设计的概念&#xff0c;单道程序设计和多道程序设计的区别&#xff0c;多道程序设计的优点熟悉操作系统接口的主要功能&#xff0c;系统调用的基本概念、类型、实现。操作系统接口…...

springboot健身房管理系统

springboot健身房管理系统 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 一、项目背景介绍&#xf…...

C语言学习笔记——数组

前言 数组是C语言中的一种自定义数据类型&#xff0c;它的使用非常广泛。但是很多新手在使用数组时&#xff0c;经常在一些细节上出问题&#xff0c;导致程序崩溃或者无法编译。今天&#xff0c;我就来详细聊聊数组的使用和我注意到的一些细节。 一、常见的数组类型与数组的创建…...

类和对象 - 中

本文已收录至《C语言》专栏&#xff01; 作者&#xff1a;ARMCSKGT 目录 前言 正文 构造函数 对比C和C的初始化 构造函数的使用与特性 默认构造函数 C11关于默认构造缺陷的补丁 析构函数 析构函数特性 默认析构和自定义析构 拷贝构造函数 问题聚焦 拷贝构造的定…...

Android之屏幕适配方案

在说明适配方案之前&#xff0c;我们需要对如下几个概念有所了解&#xff1a;屏幕尺寸&#xff0c;屏幕分辨率&#xff0c;屏幕像素密度。 屏幕尺寸 屏幕尺寸指屏幕的对角线的物理长度&#xff0c;单位是英寸&#xff0c;1英寸2.54厘米。 比如常见的屏幕尺寸&#xff1a;5.0、5…...

SpringBoot+jersey跨域文件上传

一、配置tomcat服务器 1.1、添加upload文件夹 在webapps\Root文件夹下创建用于接收上传文件的upload文件夹 1.2、修改conf\web.xml设置允许上传文件 <init-param><param-name>readonly</param-name><param-value>false</param-value></ini…...

数据结构One——绪论

本喵是FW视频封面最终版宝子&#xff0c;你不点个赞吗&#xff1f;不评个论吗&#xff1f;不收个藏吗&#xff1f; 最后的最后&#xff0c;关注我&#xff0c;关注我&#xff0c;关注我&#xff0c;你会看到更多有趣的博客哦&#xff01;&#xff01;&#xff01; 喵喵喵&#…...

JVM篇之内存及GC

目录一、JVM内存区域1.1程序计数器1.2虚拟机栈1.3本地方法栈1.4堆1.5方法区二、JVM运行时内存2.1新生代(轻量级GC)2.2老年代&#xff08;重量级GC&#xff09;一、JVM内存区域 JVM 内存区域主要分为线程私有区域【程序计数器、虚拟机栈、本地方法栈】、线程共享区域【JAVA 堆、…...

Linux驱动操作地址(寄存器)的一些方式

Linux驱动操作地址(寄存器&#xff09;的一些方式 文章目录Linux驱动操作地址(寄存器&#xff09;的一些方式1.对绝对地址赋值操作2. ioremap2.1 void __iomem *地址2.2 volatile unsigned int *地址2.3 structioremap1.对绝对地址赋值操作 对绝对地址0x100000赋值操作 *&…...

Java日志框架介绍

Log4j Apache Log4j是一个基于Java的日志记录工具。它是由Ceki Glc首创的&#xff0c;现在则是Apache软件基金会的一个项目。 Log4j是几种Java日志框架之一。 Log4j 2 Apache Log4j 2是apache开发的一款Log4j的升级产品。 Commons Logging Apache基金会所属的项目&#xff0c;是…...

编程中遇到的计算机大小端概念

概念大小端&#xff08;Endian&#xff09;是指在一个多字节的数据中&#xff0c;字节的存储顺序的规定。通俗来说&#xff0c;就是指数据在计算机内部存储时的顺序问题。在计算机系统中&#xff0c;一个数据项可能占据多个存储单元。在这种情况下&#xff0c;这个数据项的存储…...

日志与可视化方案:从ELK到EFK,再到ClickHouse

EFK方案 从ELK谈起 ELK是三个开源软件的缩写&#xff0c;分别表示&#xff1a;Elasticsearch&#xff0c;Logstash&#xff0c;Kibana。新增了一个FlieBeat&#xff0c;它是一个轻量级的日志收集处理工具&#xff0c;FlieBeat占用资源少&#xff0c;适用于在各个服务器上搜集…...

字符函数和字符串函数(上)——“C”

各位CSDN的uu们你们好呀&#xff0c;今天小雅兰来给大家介绍一个全新的知识点&#xff0c;就是字符函数和字符串函数啦&#xff0c;其实其中有些函数我之前已经学习过了&#xff0c;比如strlen、strcpy&#xff1b;也有一些之前不是很熟悉的函数&#xff0c;比如strstr、strtok…...

九龙证券|下周解禁市值超400亿元,3股解禁压力较大

下周3股解禁比例超50%。 百利电气昨日盘中直线拉升封板&#xff0c;至此&#xff0c;百利电气两连板&#xff0c;累计涨幅20.85%。 昨日晚间&#xff0c;百利电气发布股票交易反常动摇公告称&#xff0c;公司不触及“室温超导”相关业务&#xff0c;也未打开相关研发和投入。公…...

一个大型网站架构的演变历程

正序&#xff1a; Rome was not built in a day&#xff08;罗马不是一天建成的。&#xff09;一个成熟的大型网站从来都不是一蹴而就的&#xff0c;需要经过多次架构的调整和升级&#xff0c;我们熟知的大型网站比如京东、淘宝、亚马逊&#xff0c;它们每天都有巨大的用户访问…...

前端前沿web 3d可视化技术 ThreeJS学习全记录

前端前沿web 3d可视化技术 随着浏览器性能和网络带宽的提升 使得3D技术不再是桌面的专利 打破传统平面展示模式 前端方向主要流向的3D图形库包括Three.js和WebGL WebGL灵活高性能&#xff0c;但代码量大&#xff0c;难度大&#xff0c;需要掌握很多底层知识和数学知识 Threej…...

链表经典笔试题(LeetCode刷题)

本篇文章主要是对力扣和牛客网上一些经典的和链表有关的笔试题的总结归纳&#xff0c;希望对你有所帮助。 目录 一、移除链表元素 1.1 问题描述 1.2 思路一 1.2.1 分析 1.2.2 代码 1.3 思路二 1.3.1 分析 1.2.3 思路三 1.3 代码实现 1.3.1 思路1的代码 1.3.2 思路2的…...

SpringCloud五大组件

微服务SpringCloud整合技术组件基本流程&#xff1a; 引入组件启动器依赖坐标覆盖默认配置即application.properties配置文件(每个微服务只有一个并且服务启动默认加载)引导类(微服务入口即main方法)自定义开启组件注解 SpringCloudEureka 服务注册中心&#xff0c;分为Eure…...

Echart的使用初体验,Echarts的基本使用及语法格式,简单图表绘制和使用及图例添加【学习笔记】

Echart&#xff1f; ECharts 是一个使用 JavaScript 实现的开源可视化库&#xff0c;涵盖各行业图表&#xff0c;满足各种需求。 ECharts 遵循 Apache-2.0 开源协议&#xff0c;免费商用。 ECharts 兼容当前绝大部分浏览器&#xff08;IE8/9/10/11&#xff0c;Chrome&#xf…...

wordpress输出文章/贵港seo关键词整站优化

python教室图书馆座位预约 django教室图书馆座位预约 python毕业设计作品成品 django毕业设计作品成品 整个项目包含了&#xff1a;开题报告 开题报告PPT 任务书 中期报告 论文模板 答辩PPT等 项目源码 主要安介绍了系统在开发过程中所应用到的一些关键的技术 主要pyth…...

天长做网站的/2345网址导航删除办法

「2019 Python开发者日」全日程揭晓&#xff0c;请扫码咨询 ↑↑↑作者 | 伊凡伊德里斯&#xff08;Ivan Idris&#xff09;&#xff0c;曾是Java和数据库应用开发者&#xff0c;后专注于Python和数据分析领域&#xff0c;致力于编写干净、可测试的代码。他还是《Python Machin…...

十个实用网站网址/哪里有网络推广

题目描述 Description【问题描述】C 国有n 个大城市和m 条道路&#xff0c;每条道路连接这n 个城市中的某两个城市。任意两个城市之间最多只有一条道路直接相连。这m 条道路中有一部分为单向通行的道路&#xff0c;一部分为双向通行的道路&#xff0c;双向通行的道路在统计条数…...

贵阳疫情最新政策/结构优化

第一个问题:“==”与equals的区别 1. ==可以用来比较基本类型和引用类型,判断内容和内存地址 2. equals只能用来比较引用类型,它只判断内容。该函数存在于老祖宗类 java.lang.Object java中的数据类型,可分为两类: 1.基本数据类型,也称原始数据类型。byte,short,char,…...

web网站开发适合女生嘛/网站内部seo优化包括

导读&#xff1a;因为教程详细&#xff0c;所以行文有些长&#xff0c;新手边看边操作效果出乎你的预料。GitHub虽然有些许改版&#xff0c;但并无大碍。 一、Git是什么&#xff1f; Git是目前世界上最先进的分布式版本控制系统。 工作原理 / 流程&#xff1a; Workspace&…...

网站视频插件/2023今日新闻头条

资源描述&#xff1a;习题答案及解析 第1章 1.1.1 1. 单项选择题 1)A2)C3)B4)A5)C6)A 2.多项选择题 1)AB2)AB3)ABCD4)ABD5)ABCD6)ABC 3.判断题 1)F2)F 1.1.2 1. 单项选择题 1)A2)B3)A4)C5)D6)D7)A8)A9)C 2. 多项选择题 1)ABCDE2)ABC3)ABCD4)AB5)ABCD6)ABCD 3. 判断题 1)F2)T3)F…...