Linux网络编程03

select的缺陷
(1)fd,set的本质是一个位图,容量是固定的1024,因此最大只能监听1024个连接 (可以扩容)
(2)监听和就绪用的是同一个数据结构,使用困难
(3)存在多次大量的从用户态到内核态的拷贝,因为我们设置fd_set 都是在用户态,但是要实现监听必须要将fd_set从用户态拷贝到内核态
(4)采用轮询找到就绪的fd,在海量连接少量就绪的情况下,会浪费了大量的时间进行轮询

高并发服务器的基石epoll(最重要的)

epoll是一种高性能的IO多路复用

epoll由监听集合和就绪队列组成

(1)将所有的监听数据放在内核态,看成一个文件对象(避免拷贝问题),epoll是一个文件对象,文件对象中有监听集合监听所有连接,查找的时候使用二分查找,使用红黑树进行存储,因此epoll可以建立大量的连接,实现快速查找
(2)把监听和就绪分离,就绪事件用队列保存,有任何事件处于就绪的时候会把就绪事件队列拷贝到用户态
只有Linux操作系统才可以使用epoll
在这里插入图片描述

使用epoll的步骤
(1)创建一个文件对象
(2)设置监听
(3)陷入阻塞,直到任一监听就绪
(4)遍历就绪事件队列处理事件(和select有区别)

(1)创建文件对象

epoll_create 返回值为一个非负的文件描述符
在这里插入图片描述

(2)设置监听
在这里插入图片描述
epfd:epoll对象的文件描述符,上一个函数的返回值
op的操作
在这里插入图片描述

EPOLL_CTL_ADD增加 监听
EPOLL_CTL_MOD修改 监听
EPOLL_CTL_DEL删除 监听
fd:我们要监听的文件对象的文件描述符
event:描述监听的类型
在这里插入图片描述
epoll_data是一个联合体,里面的数据四选一,一般选择int fd
其中epoll_event中的events是用来指定是读就绪还是写就绪或者异常就绪,我们一般填写的是读就绪EPOLLIN
epoll_event中的data就是上一个结构体

(3)陷入阻塞

在这里插入图片描述
events:用户给就绪事件队列分配的地址,本质上是一个数组,数组中有一个重要成员data.fd,这个成员用来说明是谁就绪了
maxevents:数组的长度,因为数组在传递的过程中会丢失长度信息
timeout:表示等待的毫秒数,精度不高,-1表示无线等待

示例
在这里插入图片描述

在这里插入图片描述
不能使用break,只能使用goto

断线重连

  #include <43func.h>//服务端int main(int argc,char * argv[]) {ARGS_CHECK(argc,3);int sockfd = socket(AF_INET,SOCK_STREAM,0);//创建你IPv4的TCP连接ERROR_CHECK(sockfd,-1,"socket");int optval = 1;int ret = setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&optval,sizeof(int));ERROR_CHECK(ret,-1,"setsockopt");struct sockaddr_in addr;addr.sin_family = AF_INET;addr.sin_port = htons(atoi(argv[2]));addr.sin_addr.s_addr = inet_addr(argv[1]);ret = bind(sockfd,(struct sockaddr*)&addr,sizeof(addr));ERROR_CHECK(ret,-1,"bind");ret = listen(sockfd,10);//更改server端socket的结构,更改半连接队列长度为10ERROR_CHECK(ret,-1,"listen");int netfd = -1;int epofd = epoll_create(1);//创建epoll文件对象ERROR_CHECK(epofd,-1,"epoll_create");struct epoll_event event;//创建监听描述符event.data.fd = STDIN_FILENO;//将文件输入流加入监听event.events = EPOLLIN;//将监听设置为读就绪监听epoll_ctl(epofd,EPOLL_CTL_ADD,STDIN_FILENO,&event);//将读事件stdin加入监听集合event.data.fd = sockfd;//将网络输入流加入监听event.events = EPOLLIN;//将监听设置为读就绪监听epoll_ctl(epofd,EPOLL_CTL_ADD,sockfd,&event);//将读事件stdin加入监听集合char buf[4097] = {0};struct epoll_event readyArr[3];//就绪队列while(1) {int readNum = epoll_wait(epofd,readyArr,3,-1);//开启阻塞,等待就绪队列中的连接puts("epoll_wait returns");for(int i = 0;i < readNum;i++) {if(readyArr[i].data.fd == STDIN_FILENO){//输入流就绪bzero(buf,sizeof(buf));//清空bufret = read(STDIN_FILENO,buf,sizeof(buf));if(ret == 0) {//读取到关闭的消息goto end;}send(netfd,buf,sizeof(buf),0);//往网络文件复制数据}else if(readyArr[i].data.fd == sockfd) {//数据来源于网络文件netfd = accept(sockfd,NULL,NULL);//创建新的套接字ERROR_CHECK(netfd,-1,"accept");event.data.fd = netfd;event.events = EPOLLIN;epoll_ctl(epofd,EPOLL_CTL_ADD,netfd,&event);//将网络文件描述符添加监听}else if(readyArr[i].data.fd == netfd) {//读取网络文件数据bzero(buf,sizeof(buf));ret = recv(netfd,buf,sizeof(buf),0);if(ret == 0) {//将netfd移除监听队列close(netfd);event.data.fd = netfd;event.events = EPOLLIN;epoll_ctl(epofd,EPOLL_CTL_DEL,netfd,&event);}puts(buf);}}}end:close(netfd);}

在这里插入图片描述

recv和read的非阻塞

当缓冲区中有数据,正常读取
当缓冲区中没有数据,直接返回-1.好处进行不会切换状态

如果我们能够预期这个数据很快就能到达,那么我们使用非阻塞效果会更好
非阻塞适用于持续但不连续(网络)的数据传输

异步非阻塞(io_wring–Linux;icop–windows):当线程读取缓冲区的时候没有数据就会立刻返回,去执行其他操作,当缓冲区就绪的某一个时刻在通知线程进行读取
同步阻塞:假如线程执行read操作读取缓冲区,如果缓冲区没有数据线程会陷入阻塞等到缓冲区中有数据读取,并且读取数据之后才会返回
同步非阻塞:线程读取缓冲区,如果缓冲区没有数据,那么线程会立刻返回,然后立刻再调用read区查看缓冲区,直到读取到数据,才执行之后的事件,这样不会导致线程阻塞,会让线程一直运行,但是相比阻塞这样的方式一直在消耗CPU资源
IO多路复用:将全部需要进行等待阻塞的操作统一到一起,既可配阻塞,也可配合非阻塞

给已经打开的文件描述符加上非阻塞属性
在这里插入图片描述
cmd的取值,表示他是获取属性还是修改属性
在这里插入图片描述

epoll的触发方式
在这里插入图片描述
水平触发:只要缓冲区有数据,就会触发读取操作
如果某个用户有大量数据,epoll会重复就绪,好处是这个用户的数据会在有限时间取完,代价就是要循环好多次epoll
边缘触发:只有当新数据进来,数据量增加的时候才会触发读取操作
哪个用户来使用读取操作,就读取哪个用户的数据,但是不会循环的去读取,保证用户使用epoll的公平性,代价是如果有一个数据量大的用户,可能数据永远取不完

采用epoll的边缘触发,配合while+非阻塞recv

水平触发 在这里插入图片描述

在这里插入图片描述

边缘触发 在这里插入图片描述

在这里插入图片描述

使用while+边缘触发读取数据 在这里插入图片描述

在这里插入图片描述

设置socket属性

修改缓冲区大小 在这里插入图片描述
SO_SNDBUF:设置发送缓冲区大小 SO_RCVBUF:设置接收缓冲区大小

在这里插入图片描述
在这里插入图片描述

缓冲区下限

读取缓冲区和发送缓冲区的下限,要达到下限才能发送或者读取数据
在这里插入图片描述
在这里插入图片描述

recv 的MSG_PEEK属性
复制缓冲区的数据,但是不取出

bzero(buf,sizeof(buf));
ret = recv(netfd,buf,sizeof(buf),MSG_PEEK);//打印管道中的数据,将管道中的数据复制一份出来,但是不读取
puts(buf);//打印读取到的管道数据

进程池和线程池

设计架构考量方面
(1)高新能,充分利用操作系统提供的资源,能不浪费就不浪费
(2)可维护性,抽象

设计方案:
(1)基于线程【很高的可维护性{Apache},但是性能差:浪费大量的资源创建和销毁线程】,每当有一个客户端连接时,创建一个线程,断开时销毁
(2)池化的思路:申请的资源用完之后不要马上回归,可以交给另外的事情复用

进程池/线程池设计思路
(1)提前创建好若干个进程
(2)每当有任务到来分配一个进程
(3)任务完成后归还进程
(4)整个进程池关闭的时候在销毁

使用进程池/线程池出现的问题
事件驱动模型(性能更高)---- event-driven
任务太多,使用任务队列解决
事件太多,原本是采用进程来统一某些事件,但是现在需要每个事件拆分开来,每个事件使用一个进程完成,因此会有多个事件,使用IO多路复用机制管理事件

进程池(实现下载服务器)

首先我们会创建多个进程(工作进程),其中有一个是主进程(Master进程)

工作进程就是执行while(1){取任务;完成任务;恢复空闲;}的循环操作,被称为事件循环 — event loop

主线程:
(1)创建子进程
(2)监听子进程
(3)实现TCP,bind/listen,监听sockfd等操作
给工作线程分配工作,并且知道哪些线程是空闲线程,每当有客户端发起连接请求总是和主线程连接在一起,因此主线程需要使用epoll来管理连接

创建子进程
在这里插入图片描述

实现tcp连接/初始化tcp连接
启动之后使用netstat -an|grep 1234命令可以看到网络端口1234处于监听状态

在这里插入图片描述

父进程移交连接

惊群问题”:在前面我们创建子线程的时候都是先使用fork创建子进程,然后再用socket创建网络文件对象,但是父进程没有子进程的网络文件对象。假如我们将socket和fork顺序调换,那么又会出现父子进程共享网络文件对象,当客户端来一个连接时,就会有多个进程同时连接。这种多个进程同时连接的问题就叫做惊群问题选项SO_REWSEPORT可以解决惊群问题,但是对操作系统内核版本有限制,Nginx就是这样实现的

我们实现先fork再socket
如果已经fork过,怎么样让父子进程共享文件对象

在两个进程之间传递文件对象

除了可以传递文本内容,还可以传递控制信息,可以在两个进程之间传递信息
在这里插入图片描述

sockfd:不能是一个管道,必须时socket,因此父子进程传递信息必须要使用socket才能传递
msg:消息头部

msg_iov:io vector(数组),数组里面存储着不连续数据的指针
不连续数据结构体
在这里插入图片描述

在这里插入图片描述

msg_iovlen:数组的长度,如果其数值为1,那么说明msg_iov就是连续数据,
msg_control:控制信息
msg_controllen

控制信息的处理
(1)在堆区为struct cmaghdr申请内存
在这里插入图片描述
使用sendmsg将数据发送给对方,收取使用recvmsg去收
在这里插入图片描述

socketpairpipe使用方法一致

在这里插入图片描述
使用socketpair会在进程中产生一个文件对象sv[0]sv[1],我们使用fork的时候会产生新的进程,此时进程中也会有一个sv[0]sv[1],并且这两个进程中sv[0]是相互连通的,sv[1]是相互连通的
在这里插入图片描述

发送的文件描述符的数值和收到的文件描述符的数值可能是不同的,但是其返回的文件对象是同一个 发送/接收实现
在这里插入图片描述

示例:

在这里插入图片描述

在这里插入图片描述

使用进程池实现服务端服务

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

epoll和select的使用对比 在这里插入图片描述

使用epoll实现聊天室
(1)获取readyNum
(2)遍历readyArr
(3)更局readyArr[i].data.fd数值找到分类,在处理对应事件
在这里插入图片描述

在这里插入图片描述

进程池

池:预先申请好所有资源,有任务到来时可以使用资源,任务完成之后可以留存资源,以供后续任务使用

(1)创建很多个进程分为主进程和工作进程,
主进程负责创建工作进程,一开始时创建,任务完成时不销毁
工作进程:完成具体的工作
(2)主进程的职责
a.创建子进程(使用fork,返回0就是工作进程),管理子进程(有任务到来,就分配给子进程,子进程完成任务通知主进程)主进程用epoll监听子进程的管道,子进昵称完成任务后用管道通知主线程
b.初始化TCP连接,有连接来就要将连接交给子进程,
在这里插入图片描述

代码的组织
在这里插入图片描述

主进程的工作
(1)TCP的初始化
(2)用epoll去管理多个文件描述符(sockfd和每个子进程的管道),封装epoll的操作(增加监听,删除监听)

开始工作
在这里插入图片描述

sendmsg和recvmsg

在这里插入图片描述

发送文本数据
在这里插入图片描述

使用sendmsg传递控制信息
在这里插入图片描述

在这里插入图片描述

给子进程增加下载的任务

TCP是一种字节流协议,消息之间没有边界,因此会导致接收的文件内容也会当作文件名
发送文件的方法------进程池进程调用下载方法-------客户端接收下载文件
在这里插入图片描述

在这里插入图片描述

应用层私有协议实现小文件传输

解决了TCP流中数据没有消息边界的问题,消息边界问题也称为”粘包“问题

解决的具体操作方法就是,我们下载文件之前,服务端将文件的文件名以及文件内容封装在一个结构体里面,这个结构体里面有Length表示数据长度,还有char buf[]来存储数据,客户端每次收到数据会先读取结构体中的Length然后在根据Length的长度来确定后面需要读取多少数据,这样就可以实现消息边界

typedef struct train_s{int length;//记录数据长度char buf[1000];//记录发送的数据,但是客户端只会读取Length长度的数据,同时1000也指定发送的文件大小不能超过1000个字节
} train_t;

在这里插入图片描述

在这里插入图片描述

大文件的传输
如果只是按照下面简单的使用while(1)循环实现大文件的下载,当文件大小过大的时候就会出现下载报错,客户端收到的数据长度过大
出现错误的原因:由于recvlen的长度我们虽然填的是1000个字节,但是不一定能够读到1000个字节的数据,,这个len只是规定接收端最大能接收多少,并不代表他一定能拿到多少,由于网络是不稳定的,并且TCP是流式的可靠协议,假如我们在读到一个struct结构体的头获取长度为1000,但是其数据只是到达900,那么接收端就会读取900个字节的数据,但是剩下的数据不会被丢弃,而且由于我们使用while(1)进行循环,此时就会把剩下的100个字节的头4个字节作为下一个struct结构体的头,这样就会导致读取错误。

在这里插入图片描述

如果我们把服务端的ERROR_CHECK错误检测,主进程就会陷入死循环,并且我们发现会有一个僵尸进程。这是由于客户端先出错,就关闭了读操作,然而服务端不知道客户端已经断开读操作,服务器就会继续写入数据,此时就会触发SIGPIPE信号,导致子进程终止,然而父进程在执行epoll_wait,无法为子进程清空数据,因此就编程僵尸进程
死循环的原因:父子进程之间的管道关闭了,父进程的epoll_wait就一直就绪

生成指定大小的空文件,文件中的内容都为0
truncate -s 100M file1

使用gdb调试多进程
set follow -fork -mode child/parent追踪子进程或父进程

临时屏蔽SIGPIPE信号----保证客户端可以死,但是服务端不能跟着客户端一起死
sendflag参数
在这里插入图片描述

在使用send时操作参数使用MSG_NOSIGNAL确保客户端死,不会导致服务端死
在这里插入图片描述

让每一次接收一定能收完
recv函数操作添加MSG_WAITALL,如果管道没有关闭,recv确保能收到len字节的数据
在这里插入图片描述
在这里插入图片描述
如果文件比较大的时候MSG_WAITALL可能会出错,他可能不会等到对应len长度数据就已经存储了

自己实现MSG_WAITALL

int recvn(int sockFd,void *pstart,int len){int total = 0;//记录获取到的数据总数int ret;char *p = (char *)pstart;//强转为char*while(total < len) {//循环获取数据ret = recv(sockFd,p+total,len-total,0);total += ret;}return 0;
}

在这里插入图片描述

在这里插入图片描述

文件校验

摘要哈希:把任何文本内容经过一i写算法,将其生成一段摘要,如果文件内容一致,则摘要一致,文件内容不一致,则摘要极大可能不一致

MD5(常用):效率比SHA高
SHA:有1—64—128数字越大,效率越低,碰撞越小
CRC:效率高,碰撞几率大,

计算MD5码
使用命令sudo apt install openssl下载相应的包
可以使用sha1sum file1计算出文件file1SHA1的摘要
使用md5sum file1计算出文件file1MD5

显示进度条

函数fstat可以根据文件描述符获取文件大小
在这里插入图片描述
服务端先向客户端发送文件大小,客户端在根据文件大小显示下载百分比
服务端

//服务端
struct stat statbuf;
ret = fstat(fd,&statbuf);//保存文件信息
ERROR_CHECK(ret,-1,"fstat");
train.length = 4;
int fileSize = statbuf.st_size;//长度转换成int
memcpy(train.buf,&fileSize,sizeof(int));//int存入train.buf中
send(netFd,&traiin,train.length + sizeof(train),MSG_NOSIGNAL);//将文件长度发送给客户端

在这里插入图片描述
在这里插入图片描述

零拷贝
我们之前实现文件的下载,是首先在内核态创建对应物理内存的映射,然后在用read将数据拷贝到用户态的train.buf,再通过send将数据从用户态的train.buf拷贝到内核态的socket,这样的来回拷贝就会消耗大量的资源和时间,因此我们下面要使用零拷贝技术来减少这样的内核于用户态之间频繁拷贝的开销。

mmap(文件映射)
(1)使用mmap实现片段发送(效率没有提升)
让用户态空间的一部分和内核态空间的一部分对应同一份物理内存,用户对物理内存更改数据,其实际就是对内核态更改数据,但是这没有对数据进行拷贝,只是使用映射机制将其对应同一个区域。
在这里插入图片描述

在这里插入图片描述
(2)使用mmap实现整个文件发送
首先使用struct发送文件名称和名称大小
在通过结构体发送文件内容,因为TCP是可靠连接,因此只需要确定文件内容大小就可以实现一次性传输
在这里插入图片描述

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

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

相关文章

Zinx框架-游戏服务器开发003:架构搭建-需求分析及TCP通信方式的实现

文章目录 1 项目总体架构2 项目需求2.1 服务器职责2.2 消息的格式和定义 3 基于Tcp连接的通信方式3.1 通道层实现GameChannel类3.1.1 TcpChannel类3.1.2 Tcp工厂类3.1.3 创建主函数&#xff0c;添加Tcp的监听套接字3.1.4 代码测试 3.2 消息类的结构设计和实现3.2.1 消息的定义3…

java基础练习(使用java实现跨库数据调度ETL)

简介 本文写一篇关于java库与库之间的数据传输&#xff0c;现实生产中可能是通过其他方式完成&#xff0c;在没有架构的基础上使用java实现跨库的数据传送&#xff0c;非常不便利。但是作为练习我觉得确实非常有用&#xff0c;涉及的java知识点相对较多。本文以一个实列讲解&am…

C++相关练习及详细讲解

目录 题1&#xff1a;输出数组中第k小的数在数组内找出查找数字在该数组第一次出现的索引 题1&#xff1a;输出数组中第k小的数 题目描述&#xff1a; 给定一个数组arr 输出数组中第k小的数 如果不存在 输出-1 输入格式&#xff1a; 第一行输入一个数字n 代表数组arr大小 第二…

SpringBoot-WebSocket浏览器-服务器双向通信

文章目录 WebSocket 介绍入门案例 WebSocket 介绍 WebSocket 是基于 TCP 的一种新的网络协议。它实现了浏览器与服务器全双工通信——浏览器和服务器只需要完成一次握手&#xff0c;两者之间就可以创建持久性的连接&#xff0c;并进行双向数据传输。 应用场景&#xff1a; 视…

UI设计感大型数据管理仪表盘后台模板源码

大型数据管理仪表盘后台模板是一款适合数据统计管理后台网站模板下载。提示&#xff1a;本模板调用到谷歌字体库&#xff0c;可能会出现页面打开比较缓慢。 演示下载 qnziyw点cn/wysc/qdmb/20838点html

Solidity数据类型之函数类型

solidity中函数的形式 function <function name>(<parameter types>) {internal|external|public|private} [pure|view|payable] [returns (<return types>)]每个关键字的意思&#xff08;方括号里面的写不写都可以&#xff09; function&#xff1a; 声明函…

七月论文审稿GPT第2版:从Meta Nougat、GPT4审稿到Mistral、LongLora

前言 如此前这篇文章《学术论文GPT的源码解读与微调&#xff1a;从chatpaper、gpt_academic到七月论文审稿GPT》中的第三部分所述&#xff0c;对于论文的摘要/总结、对话、翻译、语法检查而言&#xff0c;市面上的学术论文GPT的效果虽暂未有多好&#xff0c;可至少还过得去&am…

基于java+springboot+vue的校园出入管理系统

项目介绍 本论文主要论述了如何使用JAVA语言开发一个校园出入管理系统 &#xff0c;本系统将严格按照软件开发流程进行各个阶段的工作&#xff0c;采用B/S架构&#xff0c;面向对象编程思想进行项目开发。在引言中&#xff0c;作者将论述校园出入管理系统的当前背景以及系统开…

ArcGIS for Android 禁止地图旋转

ArcGIS for Android 禁止地图旋转 话不多说&#xff0c;直接上代码&#xff01;&#xff01;&#xff01; public class LoadMap extends AppCompatActivity {// 地图private MapView mapView;private ArcGISMap map;Overrideprotected void onCreate(Bundle savedInstanceSta…

1.Netty概述

原生NIO存在的问题(Netty要解决的问题) 虽然JAVA NIO 和 JAVA AIO框架提供了多路复用IO/异步IO的支持&#xff0c;但是并没有提供给上层“信息格式”的良好封装。JAVA NIO 的 API 使用麻烦,需要熟练掌握 ByteBuffer、Channel、Selector等 , 所以用这些API实现一款真正的网络应…

逆向学习记录(4)adb

adb用于PC和手机端通讯。 常用命令如下&#xff1a; 如果不是模拟器&#xff08;模拟器一般都有自己的adb&#xff09;&#xff0c;adb会出现在Andirod的SDK中&#xff0c;路径为&#xff1a;Android/SDK/platform-tools。 最好加入环境变量中。

MTK联发科、高通、紫光展锐手机SOC平台型号汇总(含详细参数)

MediaTek联发科手机平台汇总&#xff1a; Qualcomm高通SOC平台汇总&#xff1a; 紫光展锐SOC平台汇总&#xff1a; 新移科技已成功研发手机SOC平台&#xff1a; 联发科平台&#xff1a; MTK6739、MTK6761、MTK6762、MTK6765、MTK8788、MTK6853、MTK6873、MTK6833、MTK6877、…

JVM 内存和 GC 算法

文章目录 内存布局直接内存执行引擎解释器JIT 即时编译器JIT 分类AOT 静态提前编译器&#xff08;Ahead Of Time Compiler&#xff09; GC什么是垃圾为什么要GC垃圾回收行为Java GC 主要关注的区域对象的 finalization 机制GC 相关算法引用计数算法&#xff08;Reference Count…

Proteus仿真--1602LCD显示仿手机键盘按键字符(仿真文件+程序)

本文主要介绍基于51单片机的1602LCD显示仿手机键盘按键字符&#xff08;完整仿真源文件及代码见文末链接&#xff09; 仿真图如下 其中左下角12个按键模拟仿真手机键盘&#xff0c;使用方法同手机键一样&#xff0c;长按自动跳动切换键值&#xff0c;松手后确认选择&#xff…

JavaScript_Date对象_实例方法_get类

计算这一年还剩多少天&#xff1a; <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta name"viewport" content"widthdevice-width, initial-scale1.0"> <title>Document&…

《Pytorch新手入门》第二节-动手搭建神经网络

《Pytorch新手入门》第二节-动手搭建神经网络 一、神经网络介绍二、使用torch.nn搭建神经网络2.1 定义网络2.2 torch.autograd.Variable2.3 损失函数与反向传播2.4 优化器torch.optim 三、实战-实现图像分类(CIFAR-10数据集)3.1 CIFAR-10数据集加载与预处理3.2 定义网络结构3.3…

通过环境变量实现多个JDK切换

前文: 由于jdk版本需要升级为jdk17,因为jdk8比较常用且稳定,本人又不想卸载掉安装的jdk8,在经过查找资料后找到了可以通过修改环境变量在本地任意切换jdk版本 环境变量配置 网上教程一堆,直接跳过了,这里主要说明怎么通过配置环境变量切换 电脑->属性->高级系统设置-&g…

latex自定义缩写

Latex 写文章可能常用到一些缩写&#xff0c;如&#xff1a; .e.g.i.e.cf.etc.w.r.t.i.i.d.et al. 其中有些要斜体&#xff0c;如果每次都要用 \textit{...}、{\it ...} 弄斜&#xff0c;有点麻烦。CVPR 模板中有定义一些命令&#xff0c;可以更方便地输入这些缩写。这里记录…

【Kotlin精简】第7章 泛型

1 泛型 泛型即 “参数化类型”&#xff0c;将类型参数化&#xff0c;可以用在类&#xff0c;接口&#xff0c;函数上。与 Java 一样&#xff0c;Kotlin 也提供泛型&#xff0c;为类型安全提供保证&#xff0c;消除类型强转的烦恼。 1.1 泛型优点 类型安全&#xff1a;通用允许…

【iOS】——知乎日报第三周总结

文章目录 一、获取新闻额外信息二、工具栏按钮的布局三、评论区文字高度四、评论区长评论和短评论的数目显示五、评论区的cell布局问题和评论消息的判断 一、获取新闻额外信息 新闻额外信息的URL需要通过当前新闻的id来获取&#xff0c;所以我将所有的新闻放到一个数组中&…