当前位置: 首页 > 编程日记 > 正文

HTTP 2.0与OkHttp

HTTP 2.0是对1.x的扩展而非替代,之所以是“2.0”,是因为它改变了客户端与服务器之间交换数据的方式。HTTP 2.0增加了新的二进制分帧数据层,而这一层并不兼容之前的HTTP 1.x服务器及客户端——是谓2.0。  在正式介绍HTTP 2.0之前,我们需要先了解几个概念。

  • 流,已建立的连接上的双向字节流。
  • 消息,与逻辑消息(RequestResponse)对应的完整的一系列数据帧。
  • 帧,HTTP 2.0通信的最小单位,如Header帧(存储的是Header)、DATA帧(存储的是发送的内容或者内容的一部分)。
1、HTTP 2.0简介

总所周知,HTTP 1.x拥有队首阻塞、不支持多路复用、Header无法压缩等诸多缺点。尽管针对这些缺点也提出了很多解决方案,如长连接、连接与合并请求、HTTP管道等,但都治标不治本,直到HTTP 2.0的出现,它新增的以下设计从根本上解决了HTTP 1.x所面临的诸多问题。

  • 二进制分帧层,是HTTP 2.0性能增强的核心,改变了客户端与服务器之间交互数据的方式,将传输的信息(HeaderBody等)分割为更小的消息和帧,并采用二进制格式的编码。
  • 并行请求与响应,客户端及服务器可以把HTTP消息分解为互不依赖的帧,然后乱序发送,最后再在另一端把这些消息组合起来。
  • 请求优先级(0表示最高优先级、2^{31}-1表示最低优先级),每个流可以携带一个优先值,有了这个优先值,客户端及服务器就可以在处理不同的流时采取不同的策略,以最优的方式发送流、消息和帧。但优先级的处理需要慎重,否则有可能会引入队首阻塞问题。
  • 单TCP连接HTTP 2.0可以让所有数据流共用一个连接,从而更有效的使用TCP连接
  • 流量控制,控制每个流占用的资源,与TCP的流量控制实现是一模一样的。
  • 服务器推送HTTP 2.0可以对一个客户端请求发送多个响应,即除了最初请求响应外,服务器还可以额外的向客户端推送资源,而无需客户端明确地请求。
  • 首部(Header)压缩HTTP 2.0会在客户端及服务器使用“首部表”来跟踪和存储之前发送的键-值对,对于相同的数据,不会再通过每次请求和响应发送。首部表在连接存续期间始终存在,由客户端及服务器共同渐进的更新。每个新的首部键-值对要么追加到当前表的末尾,要么替换表中的值。

虽然HTTP 2.0解决了1.x中的诸多问题,但它也存在以下问题。

  • 虽然消除了HTTP队首阻塞现象,但TCP层次上仍然存在队首阻塞现象。要想彻底解决这个问题,就需要彻底抛弃TCP,自己来定义协议。可以参考谷歌的QUIC。
  • 如果TCP窗口缩放被禁用,那宽带延迟积效应可能会限制连接的吞吐量。
  • 丢包时,TCP拥塞窗口会缩小。
2、二进制分帧简介

HTTP 2.0的根本改进还是新增的二进制分帧层。与HTTP 1.x使用换行符分割纯文本不同,二进制分帧层更加简介,通过代码处理起来更简单也更有效。

图片来自HTTP/2 简介

建立了HTTP 2.0连接后,客户端与服务器会通过交换帧来通信,帧也是基于这个新协议通信的最小单位。所有帧都共享一个8字节的首部,其中包括帧的长度、类型、标志,还有一个保留位和一个31位的流标识符。

共有的8字节帧首部
  • 16位的长度前缀意味着一帧大约可以携带64KB数据,不包括8字节首部
  • 8位的类型字段决定如何解释帧其余部分的内容
  • 8位的标志字段允许不同的帧类型定义特定于帧的消息标志
  • 1位的保留字段始终置为0
  • 31位的流标识符唯一标识HTTP 2.0的流

HTTP 2.0规定了以下的帧类型。

  • DATA,用于传输HTTP消息体
  • HEADERS,用于传输关于流的额外的首部字段(Header
  • PRIORITY,用于指定或者重新指定流的优先级
  • RST_STREAM,用于通知流的非正常终止
  • SETTINGS,用于通知两端通信方式的配置数据
  • PUSH_PROMISE,用于发出创建流和服务器引用资源的要约
  • PING,用于计算往返时间,执行“活性”检查
  • GOAWAY,用于通知客户端/服务器停止在当前连接中创建流
  • WINDOW_UPDATE,用于针对个别流或者个别连接实现流量控制
  • CONTINUATION,用于继续一系列首部块片段
2.1、HEADER帧

在发送应用数据之前,必须创建一个新流并随之发送相应的元数据,比如流的优先级、HTTP首部等。HTTP 2.0协议规定客户端和服务器都可以发起新流,因此有以下两种可能。

  • 客户端通过发送HEADERS帧来发起新流,这个帧里包含带有新流ID的公用首部、可选的31位优先值,以及一组HTTP键值对首部
  • 服务器通过发送PUSH_PROMISE帧来发起推送流,这个帧与HEADER帧等效,但它包含“要约流ID”,没有优先值

带优先值得HEADERS帧
2.2、DATA帧

应用数据可以分为多个DATA帧,最后一帧要翻转帧首部的END_STREAM字段。

DATA帧

数据净荷不会被另行编码或压缩。DATA帧的编码方式取决于应用或者服务器,纯文本、gzip压缩、图片或者视频压缩格式都可以。整个帧由公用的8字节首部及HTTP净荷组成。  从技术上说,DATA帧的长度字段决定了每帧的数据净荷最多可达2^{31}-1(65535)字节。可是,为了减少队首阻塞,HTTP 2.0标准要求DATA帧不能超过 2^{14}-1(16383)字节。长度超过这个阀值的数据,就得分帧发送。

3、HTTP 2.0在OKHttp中的应用

HTTP 2.0是通过RealConnectionstartHttp2方法开启的,在该方法中会创建一个Http2Connection对象,然后调用Http2Connectionstart方法。

  private void startHttp2(int pingIntervalMillis) throws IOException {socket.setSoTimeout(0); // HTTP/2 connection timeouts are set per-stream.//创建Http2Connection对象http2Connection = new Http2Connection.Builder(true).socket(socket, route.address().url().host(), source, sink).listener(this).pingIntervalMillis(pingIntervalMillis).build();//开启HTTP 2.0http2Connection.start();}
复制代码

start方法中会首先给服务器发送一个字符串PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n来进行协议的最终确定,并用于建立 HTTP/2 连接的初始设置。然后给服务器发送一个SETTINGS类型的Header帧,该帧主要是将客户端每一帧的最大容量、Header表的大小、是否开启推送等信息告诉给服务器。如果Window的大小发生改变,就还需要更新Window的大小(HTTP 2.0的默认窗口大小为64KB,而客户端则需要将该大小改为16M,从而避免频繁的更新)。最后开启一个子线程来读取从服务器返回的数据。

  public void start() throws IOException {start(true);}void start(boolean sendConnectionPreface) throws IOException {if (sendConnectionPreface) {//发送一个字符串PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n来进行协议的最终确定,即序言帧writer.connectionPreface();//告诉服务器本地的配置信息writer.settings(okHttpSettings);//okHttpSetting中Window的大小是设置为16Mint windowSize = okHttpSettings.getInitialWindowSize();//默认是64kb,但如果在客户端则需要重新设置为16Mif (windowSize != Settings.DEFAULT_INITIAL_WINDOW_SIZE) {//更新窗口大小writer.windowUpdate(0, windowSize - Settings.DEFAULT_INITIAL_WINDOW_SIZE);}}//子线程监听服务器返回的消息new Thread(readerRunnable).start(); // Not a daemon thread.}复制代码

ReaderRunnable的名称就可以看出它是用来读取从服务器返回的各种类型数据。

  class ReaderRunnable extends NamedRunnable implements Http2Reader.Handler {...@Override protected void execute() {ErrorCode connectionErrorCode = ErrorCode.INTERNAL_ERROR;ErrorCode streamErrorCode = ErrorCode.INTERNAL_ERROR;try {//读取服务器返回的序言帧reader.readConnectionPreface(this);//不断的读取下一帧,所有消息从这里开始分发while (reader.nextFrame(false, this)) {}connectionErrorCode = ErrorCode.NO_ERROR;streamErrorCode = ErrorCode.CANCEL;} catch (IOException e) {...} finally {...}}//读取返回的DATA类型数据@Override public void data(boolean inFinished, int streamId, BufferedSource source, int length)throws IOException {...}//读取返回的HEADERS类型数据@Override public void headers(boolean inFinished, int streamId, int associatedStreamId,List<Header> headerBlock) {...}//读取返回的RST_TREAM类型数据   @Override public void rstStream(int streamId, ErrorCode errorCode) {...}//读取返回的SETTINGS类型数据@Override public void settings(boolean clearPrevious, Settings newSettings) {...}//回复服务器返回的ackSettingsprivate void applyAndAckSettings(final Settings peerSettings) ...}//恢复客户端发送的SETTING数据,客户端默认不实现@Override public void ackSettings() {...}//读取返回的PING类型数据@Override public void ping(boolean reply, int payload1, int payload2) {...}//读取服务器返回的GOAWAY类型数据@Override public void goAway(int lastGoodStreamId, ErrorCode errorCode, ByteString debugData) {...}//读取服务器返回的WINDOW_UPDATE类型数据@Override public void windowUpdate(int streamId, long windowSizeIncrement) {...}//读取服务器返回的PRIORITY类型数据@Override public void priority(int streamId, int streamDependency, int weight,boolean exclusive) {...}//读取返回的PUSH_PROMISE类型数据@Overridepublic void pushPromise(int streamId, int promisedStreamId, List<Header> requestHeaders) {... }//备用Service@Override public void alternateService(int streamId, String origin, ByteString protocol,String host, int port, long maxAge) {...}}
复制代码

上面简述了在OkHttp中如何开启HTTP 2.0协议。下面就来介绍客户端与服务器通过HTTP 2.0协议来进行数据读写操作。

3.1、向服务器写入Headers

向服务器写入Header是通过httpCodec.writeRequestHeaders(request)来实现的,httpCodecHTTP 2.0协议下的实现类是Http2CodecwriteRequestHeaders方法主要是创建一个新流Http2Stream,在这个流创建成功后就会向服务器发送Headers类型数据。

    boolean hasRequestBody = request.body() != null;List<Header> requestHeaders = http2HeadersList(request);//创建新流stream = connection.newStream(requestHeaders, hasRequestBody);//我们可能在创建新流并发送Headers时被要求取消,但仍然没有要关闭的流。if (canceled) {stream.closeLater(ErrorCode.CANCEL);throw new IOException("Canceled");}...}//以下方法在Http2Connection类中public Http2Stream newStream(List<Header> requestHeaders, boolean out) throws IOException {return newStream(0, requestHeaders, out);}private Http2Stream newStream(int associatedStreamId, List<Header> requestHeaders, boolean out) throws IOException {...synchronized (writer) {synchronized (this) {//每个TCP连接的流数量不能超过Integer.MAX_VALUEif (nextStreamId > Integer.MAX_VALUE / 2) {shutdown(REFUSED_STREAM);}if (shutdown) {throw new ConnectionShutdownException();}//每个流的IDstreamId = nextStreamId;//下一个流的ID是在当前流ID基础上加2nextStreamId += 2;//创建新流stream = new Http2Stream(streamId, this, outFinished, inFinished, null);flushHeaders = !out || bytesLeftInWriteWindow == 0L || stream.bytesLeftInWriteWindow == 0L;if (stream.isOpen()) {streams.put(streamId, stream);}}if (associatedStreamId == 0) {//向服务器写入Headerswriter.headers(outFinished, streamId, requestHeaders);} else if (client) {throw new IllegalArgumentException("client streams shouldn't have associated stream IDs");} else {//用于服务器writer.pushPromise(associatedStreamId, streamId, requestHeaders);}}//刷新if (flushHeaders) {writer.flush();}return stream;}
复制代码

在客户端,流的ID是从3开始的所有奇数,在服务器,流的ID则是所有偶数。在Http2Connection的构造函数中定义了定义了流ID的初始值。

  Http2Connection(Builder builder) {....//如果是客户端,流的ID则从1开始nextStreamId = builder.client ? 1 : 2;if (builder.client) {//在HTTP2中,1保留,用于升级nextStreamId += 2;}...}
复制代码
3.2、读取服务器返回的Headers

readResponseHeaders是从服务器读取Headers数据,该方法在Http2Codec中。

  @Override public Response.Builder readResponseHeaders(boolean expectContinue) throws IOException {//从流中拿到Headers信息,Headers headers = stream.takeHeaders();Response.Builder responseBuilder = readHttp2HeadersList(headers, protocol);if (expectContinue && Internal.instance.code(responseBuilder) == HTTP_CONTINUE) {return null;}return responseBuilder;}//该方法在Http2Stream中public synchronized Headers takeHeaders() throws IOException {readTimeout.enter();try {//如果队列中没有数据就等待while (headersQueue.isEmpty() && errorCode == null) {waitForIo();}} finally {readTimeout.exitAndThrowIfTimedOut();}//从队列中拿到Headers数据if (!headersQueue.isEmpty()) {return headersQueue.removeFirst();}throw new StreamResetException(errorCode);}
复制代码

headersQueue是一个双端队列,它主要是存储服务器返回的Headers。当服务器返回Headers时,就会更新该链表。

3.3、读/写Body

在创建流的时候,都会创建一个FramingSinkFramingSource对象。FramingSink用来向服务器写入数据,FramingSource则读取服务器返回的数据。因此关于读/写Body其实就是对Okio的运用,不熟悉Okio的可以先去了解一下Okio的知识。

  //向服务器写数据final class FramingSink implements Sink {private static final long EMIT_BUFFER_SIZE = 16384;...@Override public void write(Buffer source, long byteCount) throws IOException {assert (!Thread.holdsLock(Http2Stream.this));sendBuffer.write(source, byteCount);while (sendBuffer.size() >= EMIT_BUFFER_SIZE) {emitFrame(false);}}//private void emitFrame(boolean outFinished) throws IOException {...try {//向服务器写入DATA类型数据connection.writeData(id, outFinished && toWrite == sendBuffer.size(), sendBuffer, toWrite);} finally {writeTimeout.exitAndThrowIfTimedOut();}}...}//从服务器读取数据private final class FramingSource implements Source {//将从网络读取的数据写入该Buffer,仅供读线程访问private final Buffer receiveBuffer = new Buffer();//可读bufferprivate final Buffer readBuffer = new Buffer();//缓冲的最大字节数private final long maxByteCount;...//从receiveBuffer中读取数据@Override public long read(Buffer sink, long byteCount) throws IOException {...}...//接收服务器传递的数据,仅在ReaderRunnable中调用void receive(BufferedSource in, long byteCount) throws IOException {...}...}
复制代码
3.4、Http2Reader与Http2Writer

前面介绍了从服务器读写数据,但无论如何都离不开Http2ReaderHttp2Writer这两个类,毕竟这两个类才是真正向服务器执行读写操作的。先来看向服务器写数据。

final class Http2Writer implements Closeable {...//写入序言帧,来进行协议的最终确定public synchronized void connectionPreface() throws IOException {...}//发送PUSH_PROMISE类型数据public synchronized void pushPromise(int streamId, int promisedStreamId,List<Header> requestHeaders) throws IOException {...}...//发送RST_TREAM类型数据   public synchronized void rstStream(int streamId, ErrorCode errorCode)throws IOException {...}//发送DATA类型数据public synchronized void data(boolean outFinished, int streamId, Buffer source, int byteCount)throws IOException {...}//发送SETTINGS类型数据public synchronized void settings(Settings settings) throws IOException {...}//发送PING类型数据public synchronized void ping(boolean ack, int payload1, int payload2) throws IOException {...}//发送GOAWAY类型数据public synchronized void goAway(int lastGoodStreamId, ErrorCode errorCode, byte[] debugData)throws IOException {...}//发送WINDOW_UPDATE类型数据,进行Window更新public synchronized void windowUpdate(int streamId, long windowSizeIncrement) throws IOException {...}//发送HEADERS类型数据public void frameHeader(int streamId, int length, byte type, byte flags) throws IOException {...}@Override public synchronized void close() throws IOException {closed = true;sink.close();}...//写入CONTINUATION类型数据private void writeContinuationFrames(int streamId, long byteCount) throws IOException {...}//写入headersvoid headers(boolean outFinished, int streamId, List<Header> headerBlock) throws IOException {...}
}
复制代码

下面再来看看从服务器读数据,基本上就是根据数据的类型来进行分发。

final class Http2Reader implements Closeable {...//读取数据public boolean nextFrame(boolean requireSettings, Handler handler) throws IOException {try {source.require(9); // Frame header size} catch (IOException e) {return false; // This might be a normal socket close.}//  0                   1                   2                   3//  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+// |                 Length (24)                   |// +---------------+---------------+---------------+// |   Type (8)    |   Flags (8)   |// +-+-+-----------+---------------+-------------------------------+// |R|                 Stream Identifier (31)                      |// +=+=============================================================+// |                   Frame Payload (0...)                      ...// +---------------------------------------------------------------+int length = readMedium(source);if (length < 0 || length > INITIAL_MAX_FRAME_SIZE) {throw ioException("FRAME_SIZE_ERROR: %s", length);}byte type = (byte) (source.readByte() & 0xff);if (requireSettings && type != TYPE_SETTINGS) {throw ioException("Expected a SETTINGS frame but was %s", type);}byte flags = (byte) (source.readByte() & 0xff);int streamId = (source.readInt() & 0x7fffffff); // Ignore reserved bit.if (logger.isLoggable(FINE)) logger.fine(frameLog(true, streamId, length, type, flags));//这里的handler是ReaderRunnable对象switch (type) {case TYPE_DATA:readData(handler, length, flags, streamId);break;case TYPE_HEADERS:readHeaders(handler, length, flags, streamId);break;case TYPE_PRIORITY:readPriority(handler, length, flags, streamId);break;case TYPE_RST_STREAM:readRstStream(handler, length, flags, streamId);break;case TYPE_SETTINGS:readSettings(handler, length, flags, streamId);break;case TYPE_PUSH_PROMISE:readPushPromise(handler, length, flags, streamId);break;case TYPE_PING:readPing(handler, length, flags, streamId);break;case TYPE_GOAWAY:readGoAway(handler, length, flags, streamId);break;case TYPE_WINDOW_UPDATE:readWindowUpdate(handler, length, flags, streamId);break;default:// Implementations MUST discard frames that have unknown or unsupported types.source.skip(length);}return true;}...
}
复制代码

Http2ReaderHttp2Writer中都是以帧的形式(二进制)来读取或者写入数据的,这样相对字符串效率会更高,当然,我们还可以用哈夫曼算法(OkHttp支持哈夫曼算法)来对帧进行压缩,从而获得更好的性能。  记得在HTTP 1.x协议下的网络优化就有用Protocol Buffer(二进制)来替代字符串传递这一个选择,而如果用HTTP 2.0则无需使用Protocol Buffer

4、总结

到这里,相必对HTTP 2.0有了一个大概的了解,更多的就需要去实践了。当然如果要使用HTTP 2.0协议,就需要客户端及服务器一起才能搞定。注意:目前OKHttp仅支持在https请求下使用HTTP 2.0

【参考资料】 《Web性能权威指南》 科普:QUIC协议原理分析 初识HTTP2.0协议 HTTP 2.0 协议详解 HTTP协议探究(六):H2帧详解和HTTP优化 HTTP/2笔记之连接建立

相关文章:

根据“坐标”生成趋势图

数据库环境&#xff1a;SQL SERVER 2008R2 有一“坐标”表t&#xff0c;表结构如下&#xff1a; id int&#xff0c; num int 字段id是序号&#xff0c;递增且连续&#xff0c;字段num是数值类型。id可以看成是坐标轴的横轴&#xff0c;num则跟纵轴有关系&…

Winform程序怎么降低占用的内存?

1 Winform程序怎么降低占用的内存&#xff1f;winform程序占用的内存数一直居高不下&#xff0c;提供给用户的手册中说明内存不能大于50MB,但是每次运行的时候&#xff0c;内存都会飙高到100多MB. 2 3 后来终于发现了一个方法&#xff0c;可以解决这个问题&#xff1a; …

mysql关系表控制_mysql表关系

一、表的详细操作1.修改表名alter table 旧表名 rename 新表名;​2.修改表的引擎与字符编码alter table 表名 engine"引擎名" charset"编码名";​3.复制表 *#结构create table 新表名 like 旧表名;eg:1create table nt like tt;#将tt的表结构复制到新表nt中…

【Python3爬虫】常见反爬虫措施及解决办法(二)...

【Python3爬虫】常见反爬虫措施及解决办法&#xff08;二&#xff09; 这一篇博客&#xff0c;还是接着说那些常见的反爬虫措施以及我们的解决办法。同样的&#xff0c;如果对你有帮助的话&#xff0c;麻烦点一下推荐啦。 一、防盗链 这次我遇到的防盗链&#xff0c;除了前面说…

【原创】ListView快速滚动至新添加一行(自动滚动)

在C#开发中我们经常要开发一些日志系统&#xff0c;尤其是基于ListView的日志显示系统。但是当日志增多是你是否有一些困扰&#xff0c;就是它为什么不会自动滚动至最后一行。以下是一小段代码&#xff0c;希望可以帮助你. public void addLog(string logString) { lock (_lock…

MFC调用CFileDialog之后目录居然会改变,调试了好久终于发现是这个问题

MFC调用CFileDialog之后目录居然会改变&#xff0c;调试了好久终于发现是这个问题&#xff0c;上网搜了下&#xff0c;发现也有人和我出现相同的问题。他的博客如下&#xff1a; http://www.programlife.net/current-directory-changed-after-using-cfiledialog.html MFC调用C…

mysqlls_mysql基本命令

1、Mysql启动命令&#xff1a;命令行内容为&#xff1a;\>net start mysql运行情况如图1所示&#xff1a;图1(Mysql启动命令)2、连接Mysql服务器&#xff1a;命令行内容为&#xff1a;\>mysql -u root -h hostaddress -p password其中&#xff0c;root为Mysql的用户名&a…

2019年3月

分包加载 使用公众号登录微信提示  "公众号暂不支持此种登录方式" 使用已经注册过的手机号注册新的微信账号提示  "你申请注册的手机号已被其他微信号绑定,暂时不能使用该手机号注册" https://github.com/witcat/LayaWxCacheFromZip /******/ (functio…

8天学通MongoDB——第三天 细说高级操作

原文地址:http://www.cnblogs.com/huangxincheng/archive/2012/02/21/2361205.html 今天跟大家分享一下mongodb中比较好玩的知识&#xff0c;主要包括&#xff1a;聚合&#xff0c;游标。 一&#xff1a; 聚合 常见的聚合操作跟sql server一样&#xff0c;有&#xff1a;count&…

UVA 10954 Add All

UVA_10954 看了别人解题报告之后发现累加的过程可以这样操作&#xff0c;每次取最小的两个元素加和&#xff0c;然后把和当作一个新元素放进集合&#xff0c;直到剩下一个元素&#xff0c;然后把中间结果加起来就是要求的结果。实际上这个题目就是哈弗曼编码&#xff0c;在LRJ树…

Java将mysql输出csv,如何从Java中的Access数据库导出表并将其保存到.csv

I am trying to export a lot of large tables from a MS Access db with java using the jdbc:odbc bridge. I wanted to save these tables to a CSV file first was wondering what would the best way to do this would be? any help would be appreciated.解决方案Fetch …

windows下nodejs express安装及入门网站,视频资料,开源项目介绍

windows下nodejs express安装及入门网站,视频资料&#xff0c;开源项目介绍&#xff0c;pm2,supervisor,npm,Pomelo,Grunt安装使用注意事项等总结 第一步&#xff1a;下载安装文件下载地址&#xff1a;官网http://www.nodejs.org/download/ 第二步&#xff1a;安装nodejs下载完…

python 之 pip、pypdf2 安装与卸载

pip是个啥&#xff1f; pip 是一个现代的&#xff0c;通用的 Python 包管理工具。提供了对 Python 包的查找、下载、安装、卸载的功能。 第一步&#xff1a;pip 下载&#xff1a;https://pypi.org/project/pip/#files 第二步&#xff1a;解压&#xff0c;进入目录python pip\pi…

eclipse 3.55安装j2ee开发工具

选择help--->install new software -->work width --选择下拉框选择要安装插件转载于:https://www.cnblogs.com/yjhrem/articles/2309602.html

mysql中没有内置函数_[mysql]MySQL中的内置函数

用在select 语句&#xff0c;以及子句where order by hacing 中 update delete函数中可以将字段名作为字段来用&#xff0c;变量的值就是这个列对应的每一行记录。一、字符串函数php中用到的函数&#xff0c;mysql中大部分也提供了1、CONCAT(”字符串”,字段&…

tiny210V2 Uboot kernel filesystem 烧写和启动

1.sd启动 将u-boot镜像写入SD卡 将SD卡通过读卡器接上电脑&#xff08;或直接插入笔记本卡槽&#xff09;&#xff0c;通过"cat /proc/partitions"找出SD卡对应的设备&#xff0c;我的设备节点是/dev/sdb.执行下面的命令$sudo dd iflagdsync oflagdsync iftiny210-ub…

Linux下Shell日期的格式

2019独角兽企业重金招聘Python工程师标准>>> 不管是哪种语言&#xff0c;日期/时间都是一个非常重要的值。比如我们保存日志的时候&#xff0c;往往是某个前缀再加上当前时间&#xff0c;这样日志文件名称就可以做到唯一。在Shell环境里&#xff0c;我们获取时间的命…

usaco 6.1

6.1.2 rectbarn 首先要注意空间的消耗,3000*3000 大概10m的样子(最多16m),只够开个char,本想套用big barn的dp方法,定义struct [i,j]{int l;int h}来表示以(i,j)为右上顶点的矩形,貌似这样会爆,只好考虑其它解法(参考wc2003王知昆的论文). 大概思路: 定义h[i,j],l[i,j],r[i,j]分…

docker mysql详解_Docker轻松入门(详解)

一 Docker简介Docker 是一个开源的应用容器引擎&#xff0c;基于 Go 语言 并遵从Apache2.0协议开源。Docker 可以让开发者打包他们的应用以及依赖包到一个轻量级、可移植的容器中&#xff0c;然后发布到任何流行的 Linux 机器上&#xff0c;也可以实现虚拟化。容器是完全使用沙…

[恢]hdu 2014

2011-12-12 05:46:08 地址&#xff1a;http://acm.hdu.edu.cn/showproblem.php?pid2014 题意&#xff1a;中文题。 mark&#xff1a;wa了3次&#xff01;&#xff01;&#xff01;因为敲错变量&#xff01;&#xff01;&#xff01;min敲成了num&#xff0c;各种二。可能是困了…

java在继承中父类的成员变量是否会被子类所覆盖

假如 父类 int num 7&#xff1b;子类 int num 9&#xff1b;父类是否会被子类所覆盖? 给你看两个例子&#xff1a; 第一个例子&#xff1a; 第二个例子&#xff1a; 这两个例子的区别只有一句话 由此证明了子类从父类继承的时候 如果有同名的成员变量 默认情况下 父类的成…

长连接及在Node中的应用——HTTP/1.1 keep-alive

HTTP请求都要经过TCP三次握手建立连接&#xff0c;四次分手断开连&#xff0c;如果每个HTTP请求都要建立TCP连接的话是极其费时的&#xff0c;因此HTTP/1.1中浏览器默认开启了Connection: keep-alive。 请求头中的这个属性的作用可以在请求完成后&#xff0c;保持TCP连接一段时…

python 桑基图 地理坐标_【转载】Python数据可视化-实现Sankey桑基图

根据不完整统计&#xff0c;90%想用sankey图的朋友都是因为被它炫酷的外表所吸引&#xff0c;举个例子&#xff1a;在这里插入图片描述关于sankey图的定义是这样描述的&#xff1a;即桑基能量分流图&#xff0c;也叫桑基能量平衡图。它是一种特定类型的流程图&#xff0c;图中延…

[恢]hdu 2015

2011-12-14 05:49:09 地址&#xff1a;http://acm.hdu.edu.cn/showproblem.php?pid2015 题意&#xff1a;中文&#xff0c;忒麻烦了。 代码&#xff1a; # include <stdio.h>int main (){int n, m, flag ;int i, sum, cnt ;while (~scanf ("%d%d", &n, &a…

http://www.shanghaihaocong.com-WORDPRESS开发的企业主题站

wordpress是世界上使用最多的php开源博客系统&#xff0c;功能强大&#xff0c;而且拥有众多的插件&#xff0c;可扩展性强。 最近&#xff0c;我也用它做了一个企业网站&#xff0c;欢迎浏览&#xff1a;http://www.shanghaihaocong.com&#xff0c;上海灏璁实业有限公司转载于…

蓝桥杯 扑克序列(全排列)

扑克序列 A A 2 2 3 3 4 4&#xff0c; 一共4对扑克牌。请你把它们排成一行。要求&#xff1a;两个A中间有1张牌&#xff0c;两个2之间有2张牌&#xff0c;两个3之间有3张牌&#xff0c;两个4之间有4张牌。 请填写出所有符合要求的排列中&#xff0c;字典序最小的那个。 例如&a…

tensorflow with求导_3.4tensorflow2.x自动求导原理函数详解

自己开发了一个股票智能分析软件&#xff0c;功能很强大&#xff0c;需要的点击下面的链接获取&#xff1a;1.1 tensorflow2.x自动求导1.1.1 自动求导GradientTape类GradientTape的作用就是用于自动求导&#xff0c;需要有自变量x和因变量y&#xff0c;调用gradient(y…

WinRAR也能实现智能备份

日志 唐山郎&#xffe5; 一切随缘,顺其自然.加博友 关注他 最新日志 2012年意味着机会还是灾难墙壁网线插座的接法佛度有缘人我~想~你&#xff0c;但不。会找你asp.net"服务器应用程序不可超惊艳! 古装美女超精美剪辑该作者的其他文章 博主推荐 相关日志 随机阅读 首页推…

MediaCodeC解码视频指定帧,迅捷、精确

原创文章&#xff0c;转载请联系作者 若待明朝风雨过&#xff0c;人在天涯&#xff01;春在天涯 原文地址 提要 最近在整理硬编码MediaCodec相关的学习笔记&#xff0c;以及代码文档&#xff0c;分享出来以供参考。本人水平有限&#xff0c;项目难免有思虑不当之处&#xff0c;…

threejs 绘制球体_ThreeJs 绘制点、线、面

所有的三位物体都是由点构成&#xff0c;两点构成线&#xff0c;三点构成面&#xff0c;ThreeJs又如何绘制出点、线、面呢 &#xff1f;在ThreeJs中&#xff1a;模型由几何体和材质构成模型以何种形式(点、线、面)展示取决于渲染方式1. 几何体首先我们来创建一个自定义的几何体…