python网络编程项目_python网络编程详解

最近在看《UNIX网络编程 卷1》和《FREEBSD操作系统设计与实现》这两本书,我重点关注了TCP协议相关的内容,结合自己后台开发的经验,写下这篇文章,一方面是为了帮助有需要的人,更重要的是方便自己整理思路,加深理解。

理论基础

OSI网络模型

1240

OSI模型是一个七层模型,实际工程中,层次的划分没有这么细致。一般来说,物理层和数据层对应着硬件和设备驱动程序,例如网卡和网卡驱动。传输层和网络层由操作系统内核实现,当用户进程需要通过网络传输数据,通过系统调用的方式让内核将数据封装为相应的协议格式,进而调用网卡驱动传输数据。顶上三层对应具体的网络应用协议:FTP、HTTP等,这些应用层协议不需要知道具体的通信细节。

传输层

在实际工程中,我们常用的应用层服务(例如:HTTP服务、数据库服务、缓存服务)通信的直接底层就是传输层,下图是一些常用命令涉及的通信协议。

1240

IPv4(Internet Protocol version 4)全称是网际协议版本4,它使用32地址,平时常说的IP协议就是指IPv4,类似于192.168.99.100的地址可以看成4位256进制数据,也就是32网络地址。但随着网络设备爆炸式增长,32地址面临这用完的风险,IPv6(Internet Protocol version 6)应运而生。IPv6使用128位地址,但IPv4地址耗尽的问题有了新的解决方案,目前普遍使用的还是IPv4,IPv6全面取代IPv4还有很长的距离。

UDP (User Datagram Protocol),全称用户数据报协议。UDP提供面向无连接的服务,客户端和服务端不存在任何长期的关系。UDP不提供可靠的通信,它不保证数据报一定送达,也不保证数据包送达的先后顺序,也不保证每份数据报只送达一次。虽然UDP可靠性差,但是消耗资源少,适用在网络环境较好的局域网中,例如不需要精确统计的监控服务(eg: Statsd)。由于使用了UDP,客户端每次打点统计只需要一次发送UDP数据报的IO开销,服务性能损失很小,而且在内网环境数据包一般都能正常到达服务端,也能保证较高的可行度。

TCP(Transmission Control Protocl),全称传输控制协议。和UDP相反,TCP提供了面向连接的服务,而且提供了可靠性保障。平常我们使用的应用层协议,例如HTTP,FTP等,几乎都是建立在TCP协议之上,深入了解TCP的细节对于开发高质量的后台开发和客户端开发都有很好的借鉴意义。下面开始重点介绍TCP协议的细节。

TCP协议

状态转换

为了提供可靠的通信服务,TCP通过三次分节建立连接,四次分节关闭连接,心跳检查判断连接是否正常,因此需要记录连接的状态,TCP一共定义了11种不同的状态。

1240

通过netstat命令可以查看所有的tcp状态。

1240

三路握手

1240

在三路握手之前,服务器必须准备好接收外来的连接。这通常通过调用bind和listen完成被动打开,此时服务进程有一个套接字处于LISTEN状态。在客户端发通过调用connect送一个SYN分节后,服务进程必须确认(ACK)此分节,同时也发送一个SYN分节,这两步在同一分节中完成,通过上面的转台扭转图,可以知道服务进程中会生成一个处于SYN_RCVD状态的套接字。当再次收到客户端的ACK分节后,服务端的套接字状态转变为ESTABLISHED。

客户端通过connect函数发起主动打开,在此之前客户端套接字状态为CLOSED。调用connect导致客户TCP发送一个SYN分节,此时套接字状态有CLOSED变为SYN_SENT,在收到服务器的SYN和ACK后,客户端socket再发送ACK分节,套接字状态变为ESTABLISHED,此时connect返回。

备注:SYN分节中除了有序列号之外,还会有最大分节大小、窗口规模选项、时间戳等TCP参数,具体可以参考协议详细规定。

终止连接

1240

上图展示了客户端执行主动关闭的情形,实际上无论客户端还是服务器,都可以执行主动关闭。一般情况下客户端执行主动关闭较多,所以使用客户端主动关闭为例讲解。

客户端调用close,执行主动关闭时,发送FIN分节,此时客户端套接字状态由ESTABLISED变为FIN_WAIT_1。服务器收到这个FIN,会执行被动关闭,并向客户端发送ACK,FIN的接受也作为一个文件结束符传递给服务进程,如果此时服务进程调用套接字的方法,无论缓存区是否有数据都会返回EOF,服务端套接字状态由ESTABLISED变为为CLOSE_WAIT。客户端接收到ACK后,客户端套接字状态由FIN_WAIT_1变为FIN_WAIT_2。

一段时间后,当服务进程调用close或者shutdown时,也会发生送FIN分节,服务端套接字状态由CLOSE_WAIT变为LAST_ACK。客户端在接收到FIN分节后,发送ACK分节,客户端套接字状态由FIN_WAIT_2变为TIME_WAIT。服务器段接收到客户端的ACK分节,状态变成CLOSED。

1240

在某些情况下,第二和第三分节可能会合并发送。调用close可能会触发主动关闭,当进程正常或者非正常退出时,内核会将该进程所使用的文件描述符对应的打开次数执行减一操作,当某个文件打开次数为0时,也就是说所有的进程都没有使用此文件时,也会触发TCP的主动关闭操作。

TIME_WAIT状态

在终止连接的过程中,主动关闭方套接字最终的状态是TIME_WAIT,在经过2MSL(maximun segment lifetime,每个IP数据报都包含一个跳限的字段,表明数据报能经过的路由最大个数,因此默认每个数据报在因特网中有一个最大存活时间)时间后状态才变为CLOSED,为什么这样设计呢?

这样的设计出于两个考虑:

可靠地实现TCP全双工连接的终止。上图的四次分节关闭连接是在正常流程,实际情况中,任何一次分节都可能出现发送失败的情况。主动关闭方最后的一个ACK分节可能会因为路由问题发送失败,为了保证可靠性,需要重新发送保证另一方正确关闭套接字,因此此时的状态不能为CLOSED。

允许老的重复分界在网络中消失。加入10.10.89.9的3400端口和206.168.12.12的80端口建立了一个TCP连接,此连接中断后,之前发送的TCP分节可能因为路由循环的问题还在因特网中游荡,而此时这两个机器相同的端口再建立起新的连接后,原来在网络中游荡的分解会对新的连接造成干扰。为了避免这种情况,设置一个2MSL的超时时间,保证之前还在网络中游荡的数据包完全消失。

套接字编程

下图是C语言的套接字函数,考虑Python的socket库只是底层C库的简单封装,接口参数大同小异,而且Python方便上手调试,语法上也更通俗易懂,所以本文使用Python的socket库作为讲解实例。

1240

socket

socket是python套接字类,通过构造函数生成套接字对象,构造函数签名如下

1240

其中family参数指协议族;type参数指套接字类型;protocol值协议类型,或者设置为0,以选择所给定family和type组合的系统默认值;fileno指文件描述符(我从来没用过)。

family

说明

AF_INET

IPv4协议

AF_INET6

IPv6协议

AF_LOCAL

Unix域协议

AF_ROUTE

路由套接字

AF_KEY

密钥套接字

type

说明

SOCK_STREAM

字节流套接字

SOCK_DGRAM

数据包套接字

SOCK_SEQPACKET

有序分组套接字

SOCK_RAW

原始套接字

protocol

说明

IPPROTO_TCP

TCP传输协议

IPPROTO_UDP

UDP传输协议

并非所有套接字family和type的组合都是有效的,下表给出了一些有效的组合和对应的协议,其中标是的项也是有效的,但是没有找到便捷的缩略词,而空白项是无效组合。

1240

connect

connect用于客户端和服务器建立连接,函数签名如下:

1240

客户端在调用connect之前不必非得调用bind函数,内核会确定源IP地址,并选择一个临时端口作为源端口。如果使用TCP协议,connect将激发TCP的三路握手过程,TCP状态由CLOSED变为SYN_SENT,最终变为ESTABLISHED,在三路握手的过程中,可能会出现下面几种情况导致connect报错。connect失败则套接字不可用,必须关闭,不能对这样的套接字再次调connect函数。

TCP客户端没有是收到SYN分节响应,一般发生在服务端backlog队列已满的情况下,服务器会对收到的SYN分节不做任何处理。客户端等待一段时间后会重新发送SYN分节,直到等待时间超过上限,才会抛出ETIMEDOUT错误(对应的python异常是TimeoutError)。

对客户端SYN的响应是RST,表明服务端在指定的端口上没有进程在等待与之连接,客户端马上会抛出ECONNRFUSED错误。下图是用python连接一个未使用的端口,抛出异常ConnectionRefusedError,该异常错误号码111,errno中查找正是ECONNRFUSED对应的错误码。

1240

如果发出的SYN在中间的吗某个路由器上引发了目的地不可达错误,客户端会等待一段时间后重新发送,直到等待时间超过上限(和第一种情况类似),此时会抛出ENETUNREACH或者EHOSTUNREACH错误。下图为关闭本机网络后,用python调用connect,由于网络不可达,异常的错误码为101,errno中查找正是ENETUNREACH错误码。

1240

bind

bind方法把一个本地协议地址赋予给一个套接字,方法签名如下:

1240

在不调用bind的情况下,内核会确定IP地址,并分配临时端口,这种情况很适合客户端,因此客户端在调用connect之前不调用bind方法。而服务端需要一个确定的ip和端口,因此需要调用bind指定地址和端口。一般情况下,服务器都有多个ip地址,除了环路地址127.0.0.1外,还有局域网和公网地址,如果bind绑定的是环路地址127.0.0.1,则只有本机通过环路地址才能访问,如果需要通过任一ip地址都能访问到,可以绑定通配地址0.0.0.0。当指定的端口为0时,内核会分配一个临时端口。

如果端口已经在使用,会抛出EADDRINUSE(errno对应错误码是98)异常,可以通过设置SO_REUSEADDR和SO_REUSEPORT这两个套接字参数让多个进程使用同一个TCP连接。

1240

listen

当创建一个套接字时,默认为主动套接字,也就是说,是一个将调用connect发起连接的客户套接字。listen方法把一个未连接的套接字转换为一个被动套接字,指示内核应接受指向该套接字的状态请求。根据TCP状态转换图,调用listen导致套接字从CLOSED状态转换到LISTEN状态。此方法参数规定了内核应该为相应套接字排队的最大连接个数,在bind之后,并在accept之前调用。

1240

为了理解backlog参数,我们必须认识到内核为其中任何一个给定的监听套接字维护两个队列:

未完成连接队列,每个这样的SYN分节对应其中一项:已由某个客户发出并到达服务器,而服务器正在等待完成相应的TCP三路握手过程,这些套接字处于SYN_RCVD状态。

已完成连接队列,每个已完成TCP三路握手过程的客户对应其中一项,这些套接字处于ESTABLISHED状态。

1240

RTT指的是未连接队列中的任何一项在队列中的存活时间。linux下的backlog指的是已完成连接队列的容量,如果服务器长时间未调用accept从此队列中取走数据,当新的客户端通过三路握手重新建立连接时,服务器不会处理收到的SYN分节,而客户端会一直等待并不断重试直到超时。在服务器负载很大的情况下,就会造成客户端连接时间长,所以需要合理设置backlog大小。

accept

accept用于从已完成连接队列头返回下一个已完成连接,如果已完成连接队列为空,那么进程会被投入睡眠(套接字为阻塞方式)。

1240

accept会自动生成一个全新的文件描述符,代表与所返回客户的TCP连接。需要注意的是,此处有两个套接字对象,一个是监听套接字,一个返回的已连接套接字。区分这两个套接字很重要,一个服务器通常仅仅创建一个监听套接字,它在该服务器的生命周期内一直存在,内核为每个由服务器进程接受的客户连接创建一个已连接套接字(也就是说TCP三路握手已经完成),当服务器完成对某个给定客户的服务时,相应的已连接套接字会被关闭。

close

close方法用来关闭套接字,方法签名如下:

1240

需要注意的是,close方法并不一定会触发TCP的四分组连接终止序列,当一个已连接套接字被多个进程打开时,关闭套接字只会导致此进程相应描述符的计数值减1,只有所有进程都将该套接字关闭后,套接字的引用计数值小于1以后,系统内核才会开始终止连接操作,这一点在多进程开发过程中需要格外注意。如果确实想在某个TCP连接上发送FIN触发主动关闭,可以调用shutdown方法。

send

send方法用于TCP发送数据,方法签名如下:

1240

每一个TCP套接字都有一个发送缓冲区,默认大小通过socket.SO_SNDBUF查看,当某个进程调用send时,内核从该应用进程的缓冲区复制所有数据到所写套接字的发送缓冲区,如果该套接字的发送缓冲区容不下该应用进程的所有数据(或是应用进程的缓冲区大小大于套接字的发送缓冲区,或是套接字的发送缓冲区已有其他数据),该应用进程将被投入睡眠(套接字阻塞的情况),内核将不从系统调用返回,直到应用进程缓冲区的所有数据都复制到套接字发送缓存区。当对端确认收到数据后,会发送ACK分节,随着对端ACK的不断到达,本端TCP才能从套接字发送缓存区中丢弃已确认的数据。

1240

在类似于HTTP的应用层协议中,客户端在发送完请求数据之后,可以调用s.shutdown(socket.SHUT_WR)告诉服务端所有的数据已经发送完成,服务端通过recv会读取到空字符串,之后就可以处理请求数据了。

recv

recv方法用于TCP接收数据,方法签名如下:

1240

每一个TCP套接字也都有一个接受缓存区,默认大小通过socket.SO_RCVBUF查看。当某个进程调用recv而且缓存区没有数据时,该进程会被投入睡眠(套接字阻塞的情况),内核将不从系统调用返回。

在《Unix网络编程》中,所有C语言调用accept,read, write函数都会检查errno是否等于EINTR,这是因为进程在执行这些系统调用的时候可能会被信号打断,导致系统调用返回。而我自己用python2.7尝试的时候发现并没有此问题,猜测是python针对系统调用被信号打断的情况,自动重新执行系统调用,stackoverflow上也证实了这一点: http://stackoverflow.com/questions/16094618/python-socket-recv-and-signals。

IO多路复用

在做服务器开发的时候,经常会碰到处理多个套接字的情形,此时可以通过多进程或这多线程的模型解决此问题。用一个主进程或者主线程负责监听套接字,其它每个进程或线程负责一个已连接套接字,这样还可以利用操作系统的线程切换实现多并发,提高机器利用率。但是机器资源有限,不可能无限制的生成新线程或进程,IO多路复用应运而生。当内核一旦发现进程指定的一个或者多个IO条件就绪,它就通知进程。

IO模型

Unix下有5中IO模型:

阻塞式IO

非阻塞式IO

IO复用

信号驱动IO

异步IO

已读取数据为例,讲解这物种IO模型的区别。每次读取数据包括以下两个阶段,而这五种模型的不同之处也体现在这两个阶段不同的处理。

等待数据准备好

从内核想进程复制数据

阻塞式IO

socket套接字默认就是阻塞式IO。以recvfrom为例,用户进程通过系统调用获取TCP数据,如果套接字缓存区没有数据,系统调用不会返回,造成用户进程一直阻塞。直到缓存区有可用数据,内核将缓存区数据拷贝至用户进程空间,系统调用才会返回。

1240

非阻塞式IO

python可以通过调用s.setblocking(False)或者s.settimeout(0.0)将一个套接字设置为非阻塞式IO。以recvfrom为例,当没有可用的数据时,用户进程不会阻塞,而是马上抛出EWOULDBLOCK错误(或者EAGAIN,对应的errno错误码都是11),只有当数据复制到内核空间后,才会正确返回数据。

1240

IO多路复用

在有多个IO操作时,先阻塞于select调用,等待数据报套接字变为可读,然后再通过recvfrom把缓存区数据复制到用户进程空间。和阻塞是IO相比,当处理的套接字个数较少的时候,多路复其实没有性能上的优势,它的优势在于可以方便操作很多套接字。

1240

信号驱动式IO

通过信号处理的方式读取数据。

1240

异步IO

当数据包被复制到用户进程后,用户通过callback的方式获取数据。

1240

模型对比

1240

可以发现,前四种IO模型——阻塞式IO、非阻塞式IO、IO复用、信号驱动IO都是同步IO模型,因为真正的IO操作(recvfrom)将阻塞进程,只有异步IO模型才不会导致用户进程阻塞。

python使用

较早的时候使用的多路复用是select函数,但是由于时间复杂度较高,很快就被其他的函数替代:linux下的epoll,unix下的kqueue,windows下的iocp。为了屏蔽不同系统下的不同实现,跨平台的第三方库出现:libuv、libev、libevent等,这些库根据平台的不同,调用不同的底层代码。

如果想直接使用底层的epoll或者select,它们封装在python的select库中;libuv、libev都有相应的python封装,库名叫做pyuv、pyev,通过pip安装后即可使用。

python示例

一般情况下,为了提升服务的承载量,都会采用进程+IO多路复用或者线程+IO多路复用的开发模式。IO多路复用是为了一个并发单位管理多个套接字,而多进程或者多线程是为了充分利用多核。由于GIL的存在,python多线程模型并不能充分多核,因此我们常见的wsgi server,例如:gunicorn、uwsgi、tornado等都是使用的多进程+IO多路服用开发模式。

tornado使用epoll管理多个套接字,gunicorn和uwsgi都可以使用gevent,gevent是一个python网络库,用greenlet做协程切换,每个协程管理一个套接字,主协程通过libevent轮询查找可用的套接字。因为gevent可以通过monkey patch将socket设置为非阻塞模式,因此当服务器有数据库、缓存或者其他网络请求的时候,相比tornado,uwsgi和gunicorn可以充分利用这部分的阻塞时间。和gunicorn相比,uwsgi是c语言实现,直观感觉这三个server的性能应该是:uwsgi > gunicorn > tornado,和网上的benchmark大致匹配。

django的作者在github上实现了一个wsgi server,项目地址: https://github.com/jonashaag/bjoern,使用C语言实现,代码量很少,性能据说比uwsgi还好,十分适合网络开发进阶学习。参考这份代码,我用python实现了一个thrift server,项目地址:https://github.com/LiuRoy/dracula,和thriftpy的TThreadedServer做了一个简单的性能对比。

50

100

150

200

250

300

350

400

450

libev

92

181

269.9

355.2

362.6

367.1

373.8

378.5

315(3%)

thread

88.9

180.5

266.1

354.8

428.9

460.2

486.5(2%)

477.9(7%)

486.5(22%)

1240

横坐标是连接个数,纵坐标是qps,括号内的数字表示错误率。在连接数较少的情况下,使用libev管理socket和多线程性能相差不大,在连接数超过200后,libev模型的请求耗时会增加,导致qps增加的并不多,但是线程模型在连接数很多的情况下,会导致部分请求一直得不到处理,在连接个数350的时候就会出现部分请求超时,而libev模型在450的时候才会出现。

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

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

相关文章

分享10个值得关注的C语言开源项目

来源于网络,如有侵权,告知必删。

python3 x默认使用的编码_python3默认使用什么编码

python3默认编码为unicode,由str类型进行表示。二进制数据使用byte类型表示。 字符串通过编码转换成字节码,字节码通过解码成为字符串encode:str --> bytes(推荐学习:Python视频教程) decode&#xff1a…

html中写css代码,开发DIV CSS时 先写CSS代码还是先写HTML代码

相信良多LOVE用DIVCSS技术启示重构网页的爱好者友好,在起源学习DIVCSS的时分都邑想一个标题,想晓得DIVCSS妙手或有教育者在开发制作html页面的时刻,下场是先写html照样先写css?带着这…

象棋子 设计模式_通过设计国际象棋游戏了解策略模式

象棋子 设计模式今天,我们将借助一个示例来尝试了解策略模式。 我们将考虑的示例是国际象棋游戏。 这里的目的是解释策略模式,而不是构建全面的国际象棋游戏解决方案。 策略模式:策略模式被称为行为模式–用于管理对象之间的算法&#xff0…

入门C语言10问10答

1 如何理解变量与常量?变量与常量相当于数据的可读可写与只读,常量是数据的一种保护机制。在内存分配给程序的内存块中有专门的常量(只读)存储区。2 整型数据的溢出问题任何一种数据类型的数据在计算机中都有它确定的数值表示范围&#xff0…

mac json格式化工具_简洁好用的工具都是相似的

大家好,我是你们的章鱼猫。不知道大家了不了解 jq 这个工具呢?指的不是 JQuery,而是一个命令行工具。jq 是一个轻量级而且灵活的命令行 JSON 解析器,类似用于 JSON 数据的 sed 工具。我们来看一下使用 jq 处理 json 的基本用法(更…

战神4 幕后花絮 概念艺术_Java 9幕后花絮:新功能从何而来?

战神4 幕后花絮 概念艺术找出Java幕后发生的事情,以及新功能如何实现 在上一篇文章中,我们介绍了即将发布的Java 9版本的新功能和尚待解决的功能,并简要提到了将新功能添加到下一个版本之前要经历的过程。 由于此过程几乎影响了所有Java开发人…

ID生成器 雪花算法

背景:在很多业务场景下,我们都需要一个唯一的 ID 来进行一些数据的交互,那么如何生成这个唯一的 ID 呢?如果在单机的情况下,生成唯一ID,可以利用机器内存的特点,通过内存分配即可。但我们线上的…

python anaconda安装_Python - 安装并配置Anaconda环境

$ py --version # 当前默认python版本 Python 3.7.1 $ conda create --name testpy2 python2.7 pandas # 创建名为testpy2的运行环境,并安装pandas包及其依赖包 Solving environment: done ## Package Plan ## environment location: D:\DownLoadFiles\anaconda3\en…

jstack调试_增压的jstack:如何以100mph的速度调试服务器

jstack调试使用jstack调试实时Java生产服务器的指南 jstack就像U2一样-从时间的黎明就一直在我们身边,我们似乎无法摆脱它 。 除了笑话,到目前为止,jstack是您的工具库中用于调试实时生产服务器的最方便的工具之一。 即便如此,我仍…

C/C 输入输出缓冲区

【导读】:本文介绍C与C 输入输出缓冲的一些操作与特性。以下是正文(1)c 中cin、cout,cerr和c的stdin、stdout、stderr都是同步的,即iostream 对象和 and cstdio流是同步的,同步关系如下:同步即表…

python输入input数组_python怎么输入数组

python怎么输入数组? python输入数组 一维数组:arr input("") //输入一个一维数组,每个数之间使空格隔开 num [int(n) for n in arr.split()] //将输入每个数以空格键隔开做成数组 print(num) //打印数组 一维数组输入输出示例&a…

eclipse 扩展_Eclipse扩展的轻量级集成测试

eclipse 扩展最近,我为Eclipse扩展点评估引入了一个小助手。 辅助程序努力减少通用编程步骤的样板代码,同时增加开发指导和可读性。 这篇文章是希望的后续文章,它显示了如何将实用程序与AssertJ定制断言结合使用,以编写针对Eclip…

深入理解右值引用,move语义和完美转发

move语义最原始的左值和右值定义可以追溯到C语言时代,左值是可以出现在赋值符的左边和右边,然而右值只能出现在赋值符的右边。在C 里,这种方法作为初步判断左值或右值还是可以的,但不只是那么准确了。你要说C 中的右值到底是什么&…

java future用法_纯干货:Java学习过程中的21个知识点和技术点

我们在Java学习过程中要学会抓重点,善于总结,Java学习过程中常见的21个知识点和技术点你知道吗?下面和千锋广州小编一起来看看吧!1. JVM相关对于刚刚接触Java的人来说,JVM相关的知识不一定需要理解很深,对此…

如何优雅地检测类型/表达式有效性?

注1:本文至少需要编译器支持C 11。注2:本文不考虑使用宏。一、老办法在写C 的时候,有时候可能需要检查一个类是否有特定的成员类型,例如:// 检查 T::type 是否存在,存在则 value 为 true,否则为…

swagger api文档_带有Swagger的Spring Rest API –公开文档

swagger api文档创建API文档后,将其提供给涉众很重要。 在理想情况下,此发布的文档将足够灵活以解决任何最后的更改,并且易于分发(就成本以及完成此操作所需的时间而言)。 为了使之成为可能,我们将利用我在…

nuxt解决首屏加载慢问题_一个 Node 脚本让你的前端项目加载速度飞起来

写在最前面我的原创什么声明变成什么鬼了……前言随着前端三大框架的盛行,越来越多的前后端分离项目在服务器上跑了起来,随之而来,开发者也慢慢发现了这种开发模式所带来的弊端,其中之一就是首屏加载速度特别慢,因为虽…

数据库连接配置tomcat_Tomcat到Wildfly:配置数据库连接

数据库连接配置tomcat该摘录摘自《 从Tomcat到WildFly 》一书,您将在其中学习如何将现有的Tomcat体系结构移植到WildFly,包括服务器配置和在其顶部运行的应用程序。 WildFly是完全兼容的Java Enterprise Edition 7容器,与Tomcat相比&#xf…

左值、右值、左值引用、右值引用

【导读】:本文主要详细介绍了左值、右值、左值引用、右值引用以及move、完美转发。左值和右值左值(left-values),缩写:lvalues右值(right-values),缩写:rvalues直接上官网…