客户端每隔 5 秒钟发送一次消息给服务端,查看效果如何?实例:final ChannelFuture channelFuture=bootstrap.connect("127.0.0.1",80).sync();channelFuture.addListener(new ChannelFutureListener() { public void operationComplete(ChannelFuture future) throws Exception { if(future.isDone()){ if(future.isSuccess()){ //1秒钟之后,每隔5描述发送一次消息 channelFuture.channel().eventLoop().scheduleWithFixedDelay(new Runnable() { public void run() { channelFuture.channel().writeAndFlush("hello world"); } },1,5, TimeUnit.SECONDS); }else if(future.isCancelled()){ System.out.println("连接被取消"); }else if(future.cause()!=null){ System.out.println("连接出错:"); } } }});执行结果:handlerAddedchannelRegisteredchannelActivechannelReadchannelReadComplete channelReadchannelReadComplete channelReadchannelReadComplete通过执行结果我们发现,第一次的时候执行 handlerAdded ()、channelRegistered ()、channelActive (),后面就不会被执行了。客户端的每次请求时,都会触发 channelRead () 和 channelReadComplete () 两个核心方法。
勾画直角坐标系下的散点图,最简情况下需要配置:yAxis、xAxis、series,示例:1368示例效果:效果与 折线图 相似,但散点图比折线图强大的地方在于,它可以通过颜色、大小表达出更多维度的信息。简单方法,可以通过 symbolSize 属性修改点的大小,该属性接受如下值:number: 点的像素大小值;array: 二维数组如 [10, 30],第一个值指定 symbol 宽度;第二个值指定 symbol 高度;function: (value: Array|number, params: Object) => number|Array: 回调函数。在上例基础上,修改 series 配置如下:{ series: [ { data: [ ['Mon', 120, 224], ['Tue', 80, 320], ['Wed', 140, 1288], ['Thu', 192, 64], ['Fri', 140, 228], ['Sat', 180, 248], ['Sun', 178, 240], ], type: 'scatter', symbolSize: function (data) { return Math.sqrt(data[2]); }, }, ],}一是在 data 序列上增加第三个维度;二是增加 symbolSize 配置项,通过函数计算出各节点大小。示例效果:还可以通过散点上不同的颜色,表现多维数据,简单方法可通过 itemStyle.color 配置各节点颜色。在上例基础上,修改 series 配置如下:{ series: [ { data: [ ['Mon', 120, 224], ['Tue', 80, 320], ['Wed', 140, 1288], ['Thu', 192, 64], ['Fri', 140, 228], ['Sat', 180, 248], ['Sun', 178, 240], ], type: 'scatter', itemStyle: { color: function (node) { return `hsla(${node.data[2] % 360}, 100%, 50%, 0.8)`; }, }, }, ],}示例效果:注意,上述示例需要手动计算每个节点的颜色、大小属性,这部分逻辑在散点图内部会有更好的实现,只需配合使用 visualMap 即可,下面会有详细介绍。
public class NettyServer { public static void main(String[] args) { //线程组-主要是监听客户端请求 NioEventLoopGroup bossGroup = new NioEventLoopGroup(); //线程组-主要是处理具体业务 NioEventLoopGroup workerGroup = new NioEventLoopGroup(); //启动类 ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap //指定线程组 .group(bossGroup, workerGroup) //指定 NIO 模式 .channel(NioServerSocketChannel.class) //双向链表管理 .childHandler(new ChannelInitializer<NioSocketChannel>() { protected void initChannel(NioSocketChannel ch) { //责任链,指定自定义处理业务的 Handler ch.pipeline().addLast(new NettyServerHandler()); } }); //绑定端口号 serverBootstrap.bind(80); }}代码说明:以上都是模板代码,需要变动的是根据不同的业务自定义对应的 Handler,并且在 initChannel () 添加逻辑处理器;根据实际情况指定 bind () 方法的端口号,注意的是端口号不能和其它端口号冲突;这里大家先熟练掌握模板代码的编写,后面章节会讲解 NioEventLoopGroup、Pipeline 等核心组件的原理。
我们执行 docker build --network=host -t ubuntu-nginx:v1 . 命令,看看在构建过程中做了哪些操作:[user@centos8 nginx]$ docker build --network=host -t ubuntu-nginx:v1 .# 将上下文求发送给Docker引擎Sending build context to Docker daemon 2.56kB# 下载依赖的镜像Step 1/7 : FROM ubuntulatest: Pulling from library/ubuntud51af753c3d3: Pull completefc878cd0a91c: Pull complete6154df8ff988: Pull completefee5db0ff82f: Pull completeDigest: sha256:747d2dbbaaee995098c9792d99bd333c6783ce56150d1b11e333bbceed5c54d7Status: Downloaded newer image for ubuntu:latest# 生成镜像 1d622ef86b13 ---> 1d622ef86b13Step 2/7 : MAINTAINER my_name myemail@domain.com# 运行容器 4eec6e3094f0,在容器内运行上面的这个命令,标记维护者信息 ---> Running in 4eec6e3094f0# 移除临时容器 4eec6e3094f0Removing intermediate container 4eec6e3094f0# 生成镜像 6679d1c204e3 ---> 6679d1c204e3Step 3/7 : RUN sed -i 's/archive.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list# 运行容器84d38c20d8c4,在容器内运行上面的这个命令,更换软件源记录 ---> Running in 84d38c20d8c4# 移除临时容器 84d38c20d8c4Removing intermediate container 84d38c20d8c4# 生成镜像 83f29f7b055a ---> 83f29f7b055aStep 4/7 : RUN sed -i 's/security.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list# 运行容器 763e4493d93f, 在容器内运行上面的这个命令,更换软件源记录 ---> Running in 763e4493d93f# 移除临时容器 763e4493d93fRemoving intermediate container 763e4493d93f# 生成镜像 6297f20605d9 ---> 6297f20605d9Step 5/7 : RUN apt update >/dev/null 2>&1# 运行容器 2665a7e5a2e9,在容器内运行上面的这个命令, 更新软件源缓存 ---> Running in 2665a7e5a2e9# 移除临时容器 2665a7e5a2e9Removing intermediate container 2665a7e5a2e9# 生成镜像 fdfed940ca4d ---> fdfed940ca4dStep 6/7 : RUN apt install nginx -y >/dev/null 2>&1# 运行 容器 722a9a544643,在容器内运行上面的这个命令, 安装nginx ---> Running in 722a9a544643# 移除临时容器 722a9a544643Removing intermediate container 722a9a544643# 生成镜像 6ee76f7df9e5 ---> 6ee76f7df9e5Step 7/7 : EXPOSE 80# 运行容器 a12ed3216ee0,在容器内运行上面的这个命令, 暴露80端口 ---> Running in a12ed3216ee0# 移除临时容器 a12ed3216ee0Removing intermediate container a12ed3216ee0# 生成最终的镜像 7cf64279ba98 ---> 7cf64279ba98Successfully built 7cf64279ba98# 将这个镜像标记命名 ubuntu-nginx 版本号v1Successfully tagged ubuntu-nginx:v1结合之前讲到的 docker commit 命令,不难理解 Dockerfile 就是将我们在文件中书写的构建指令,一层一层从 FROM 指定的基础镜像使用临时容器过渡,逐层叠加起来最终生成目标镜像。使用 docker history ubuntu-nginx:v1 命令可以验证我们的想法:[user@centos8 nginx]$ docker history ubuntu-nginx:v1IMAGE CREATED CREATED BY SIZE COMMENT7cf64279ba98 21 minutes ago /bin/sh -c #(nop) EXPOSE 80 0B6ee76f7df9e5 21 minutes ago /bin/sh -c apt install nginx -y >/dev/null 2… 59.2MBfdfed940ca4d 21 minutes ago /bin/sh -c apt update >/dev/null 2>&1 21.4MB6297f20605d9 21 minutes ago /bin/sh -c sed -i 's/security.ubuntu.com/mir… 2.76kB83f29f7b055a 21 minutes ago /bin/sh -c sed -i 's/archive.ubuntu.com/mirr… 2.76kB6679d1c204e3 21 minutes ago /bin/sh -c #(nop) MAINTAINER my_name myemai… 0B1d622ef86b13 2 weeks ago /bin/sh -c #(nop) CMD ["/bin/bash"] 0B<missing> 2 weeks ago /bin/sh -c mkdir -p /run/systemd && echo 'do… 7B<missing> 2 weeks ago /bin/sh -c set -xe && echo '#!/bin/sh' > /… 811B<missing> 2 weeks ago /bin/sh -c [ -z "$(apt-get indextargets)" ] 1.01MB<missing> 2 weeks ago /bin/sh -c #(nop) ADD file:a58c8b447951f9e30… 72.8MB
scheme 一般指的是协议,URI 的通用格式并没有太多限制,一般是如下,以 scheme 开头,冒号 “:” 分隔开。 <scheme>:<scheme-specific-part>虽然 URI 的格式没怎么限制,但是不同 scheme 一般会遵循下面的格式来定义。<scheme>://<authority><path>?<query>以 scheme = http 加以说明: http://www.imocc.com:80/index.htm?id=3937Http 的 <authority>模块一般不会写在路径上面,即使是 Basic Authorization 也是将用户名密码 base64(user:passwd) 写在 head 里面。下面的例子说明 RUI 的一般用法:ftp://ftp.is.co.za/rfc/rfc1808.txt;gopher://spinaltap.micro.umn.edu/00/Weather/California/Los%20Angeles;http://www.math.uio.no/faq/compression-faq/part1.html;mailto:mduerst@ifi.unizh.ch;news:comp.infosystems.www.servers.unix; telnet://melvyl.ucop.edu/0
对于两个一维数组,dot() 函数计算的是这两个数组对应下标元素的乘积和,也称之为內积。对于二维数组,计算的是两个数组的矩阵乘积。案例创建两个一维数组 a 和 b:a = np.array([1, 2, 3, 4])b = np.array([6, 7, 8, 9])计算一维数组的內积:np.dot(a, b)out: 80创建两个二维数组:A = np.array([[1, 2, 3], [4, 5, 6]])B = np.array([[11, 22], [33, 44], [55, 66]])计算二维数组的矩阵乘积:np.dot(A, B)out: array([[242, 308], [539, 704]])可以发现,对二维数组,矩阵的乘积满足如下规律:m×p的矩阵A,p×n的矩阵B,其矩阵乘积结果为大小为m×n的矩阵。
简单来说,跨域请求就是一个域下的资源请求另外一个域下的资源。同一个域,指的是,协议名、域名、端口号都一致。 举个例子来说,假如 “http://www.a.com” 下的 JavaScript 脚本发起 Ajax 请求 “http://www.a.com/ajax” ,由于 协议名 http 、域名 www.a.com 和 端口号(默认都是 80)三者都是一致的,因此都属于同一个域,不造成跨域请求。而假如其中任一元素不相同,则造成跨域请求。与此同时,浏览器出于安全考虑,基于同源策略则会做一定的限制:比方说:无法获取不同域的 Cookie、LocalStorage 等等。无法获取不同域的 DOM 对象。无法向不同域发送 Ajax 请求。
在 nginx.conf 中添加如下的 http 指令块:http { include mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log logs/access.log main; sendfile on; keepalive_timeout 65; gzip on; server { listen 8000; return 200 '8000, server\n'; } server { listen 8001; return 200 '8001, server\n'; } server { listen 8002; return 200 '8002, server\n'; } upstream backends { # ip_hash # hash user_$arg_username; server 127.0.0.1:8000; server 127.0.0.1:8001; server 127.0.0.1:8002; } server { listen 80; location / { proxy_pass http://backends; proxy_http_version 1.1; proxy_set_header Connection ""; } }}上述配置中,我们用8000,8001和8002三个端口模拟了3个上游服务器,默认使用轮询负载均衡算法,而且三个的权重均为1。进行如下的 http 请求操作,可以看到 Nginx 转发 http 请求会均匀地分配到3个服务器上。[shen@shen ~]$ curl http://180.76.152.1138000, server[shen@shen ~]$ curl http://180.76.152.1138001, server[shen@shen ~]$ curl http://180.76.152.1138002, server[shen@shen ~]$ curl http://180.76.152.1138000, server
bind () 主要用来绑定本地端口号。实例:ChannelFuture future=serverBootstrap.bind(80);future.addListener(new GenericFutureListener<Future<? super Void>>() { public void operationComplete(Future<? super Void> future) { if (future.isSuccess()) { System.out.println("端口绑定成功!"); } else { System.err.println("端口绑定失败!"); } }});代码升级,如果绑定的端口已经存在,则端口号递增。当然,实际情况很少会去递增端口号,一般都是上线之前确定端口号,否则客户端不知道端口号,无法连接。实例:private static void bind(ServerBootstrap serverBootstrap, final int port) { ChannelFuture future=serverBootstrap.bind(port); future.addListener(new GenericFutureListener<Future<? super Void>>() { public void operationComplete(Future<? super Void> future) { if (future.isSuccess()) { System.out.println("端口[" + port + "]绑定成功!"); } else { System.err.println("端口[" + port + "]绑定失败!"); //递归重新绑定端口号 bind(serverBootstrap, port + 1); } } });}
在测试集合上进行验证需要我们首先在数据集合中预留出一定的测试集合,一般而言,我们会将所有数据的 80% 用于训练集合,而剩下的 20% 用于测试集合。在测试集合上我们主要是使用 evaluate 方法进行模型的评估:model.evaluate(x_test, y_test)其中假设我们的模型已经经过,而 x_test 与 y_test 分别是测试集合的数据和标签。我们可以得到如下输出:313/313 - 0s - loss: 0.34440.34437522292137146如果我们在模型编译的过程之中添加了指标,比如准确率:model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])那么我们会得到输出:313/313 - 0s - loss: 0.3836 - accuracy: 0.8792[0.3835919201374054, 0.8791999816894531]可以看到,evaluate 方法会记录损失函数和编译中指定的指标。而在 evaluate 函数中返回的指标一般可以作为我们参考的较为可靠的依据。
端口号是用 16 bit 无符号整数表示的,取值范围是 0~65535,总共可以分配 65536 个端口号。端口号属于稀缺资源,是由 Internet Assigned Numbers Authority (IANA)统一管理和分配的。端口号当前分配状况:0 ~ 1023此区间内的端口号叫做知名端口号,已经被系统或者是一些知名的服务所占用,比如:端口号用途20 , 21用于 FTP 协议23用于 telnet 协议80用于著名的 HTTP 服务443用于 HTTPS 服务1023 ~ 65535此区间端口号也有很多被知名的应用占有,比如:端口号用途1433用于 SQL Server 服务等等1935用于 RTMP 服务3306用于 MySQL 服务8080作为 HTTP 服务的另外一个端口号
package com.emercy.myapplication;import android.app.Activity;import android.graphics.Bitmap;import android.graphics.Canvas;import android.os.Bundle;import android.view.View;import android.widget.Button;import android.widget.Toast;import java.io.ByteArrayOutputStream;import java.io.FileOutputStream;import java.io.IOException;public class MainActivity extends Activity { static ByteArrayOutputStream byteOut = null; private Bitmap bitmap = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Button btn_cut = (Button) findViewById(R.id.button); btn_cut.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { capture(); } }); } public void capture() { Runnable action = new Runnable() { @Override public void run() { final View contentView = getWindow().getDecorView(); try { bitmap = Bitmap.createBitmap(contentView.getWidth(), contentView.getHeight(), Bitmap.Config.ALPHA_8); contentView.draw(new Canvas(bitmap)); ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.JPEG, 100, byteOut); save(bitmap); } catch (Exception e) { e.printStackTrace(); } finally { try { if (null != byteOut) byteOut.close(); if (null != bitmap && !bitmap.isRecycled()) { bitmap = null; } } catch (IOException e) { e.printStackTrace(); } } } }; try { action.run(); } catch (Exception e) { e.printStackTrace(); } } private void save(Bitmap b) { FileOutputStream fos; try { fos = new FileOutputStream("sdcard/short.png"); boolean success = b.compress(Bitmap.CompressFormat.PNG, 80, fos); fos.flush(); fos.close(); if (success) { Toast.makeText(MainActivity.this, "截图完成", Toast.LENGTH_SHORT).show(); } } catch (IOException e) { e.printStackTrace(); } }}在capture()方法中,我们获取当前 Activity 的“DecorView”(Activtiy 的顶层 View,我们设置的 CotentView 是其子 View),然后获取 DecorView 的输入流并会转化成 Bitmap,最后直接输出到文件中即可。
假设 Nginx 启动了多个 Worker 进程,并且在 Master 进程中通过 socket 套接字监听80端口。这些 Worker 进程 fork 自 Master 进程,然后每个worker进程都可以去 accept 这个监听的 socket。 当一个连接进来后,所有在 accept 在这个 socket 上面的进程,都会收到消息,但是只有一个进程可以 accept 这个连接,其它的则 accept 失败,这便是所谓的惊群现象。Nginx 处理这种情况的方式就是加锁。有了锁之后,在同一时刻,就只会有一个 Worker 进程在 accpet 连接,这样就不会有惊群问题了。在 Worker 进程拿到 Http 请求后,就开始按照前面介绍的 11个阶段处理该 Http 请求,最后返回响应结果并断开连接。
使用 Gradle wrapper 下载 Gradle 我们可以点击 AS 界面上的同步按钮,也可以使用 gradlew 命令来执行 gradle 命令来下载,下面以 Windows 系统为例,我本地distributionUrl配置的 Gradle 版本是gradle-6.0.1-bin.zip,我们现在改为gradle-5.0-all.zip版本,执行clean命令,看看有没有下载 5.0 版本下载:C:\Users\LeiQi PC\Documents\MyApplication>gradlew cleanDownloading https://services.gradle.org/distributions/gradle-5.0-all.zip............10%............20%.............30%............40%............50%.............60%............70%.............80%............90%............100%Welcome to Gradle 5.0!BUILD SUCCESSFUL in 1s2 actionable tasks: 2 up-to-date我们看到这里已经下载了 gradle 5.0 版本,那么按照前面所说,再次执行命令,直接调用本地的 Gradle,不会去下载我们来看下:C:\Users\LeiQi PC\Documents\MyApplication>gradlew cleanDeprecated Gradle features were used in this build, making it incompatible with Gradle 7.0.Use '--warning-mode all' to show the individual deprecation warnings.See https://docs.gradle.org/5.0/userguide/command_line_interface.html#sec:command_line_warningsBUILD SUCCESSFUL in 1s2 actionable tasks: 2 up-to-date我们看到这里没有再去下载 Gradle 版本了而是直接使用上次下载的缓存下来的。
大家好,从本小节开始,我们将一起学习SQL wiki系列。谈到 SQL,每个人心里会有不同的理解,但大家可能都会不约而同地想到一个词——数据库。SQL 发展到今天,它的足迹其实早已遍布各个应用领域了。学好 SQL,你可以做到很多,数据管理、数据分析甚至机器学习,你都可以用 SQL 来完成。学习是一个颇为技巧的活,它也遵守二八定律,学好20%的知识你就可以做好80%的事情,这也是wiki想要带给大家的。我们希望整理出 SQL “百分之二十”的知识,帮你办到“百分之八十”的事情,当然这里的知识远非百分之二十。作为 SQL 系列的第一小节,我们将为你简单的介绍什么是 SQL,SQL 能做什么以及学习 SQL 的注意事项。慕课赠言:空无以求全,倒空的杯子才能装满更多的水。
先来看一个我们经常见到的 URL 形式:http://www.imooc.com/wiki/html5上面这个 URL 由以下几部分组成:scheme://host:port/path?key=valuescheme:代表的是访问的协议,一般为 http 或者 https。例如,https://www.baidu.com 的协议是 https;host:主机名、域名,例如,https://www.baidu.com 的 host 为 www.baidu.com;port:端口号,http 协议默认使用 80 端口,https 协议默认使用 443 端口。通常情况下,使用默认值,不需要显式的写明端口号,例如,https://www.baidu.com 的端口是 443。某些情况下,可以显式的写明端口号,例如,http://localhost:5000 的端口号是 5000;path:页面路径,例如:http://www.imooc.com/wiki/html5 的 path 是 wiki/html5;key=value:查询字符串,例如:https://www.baidu.com/s?wd=python,查询字符串是 wd=python,查询字符串包括两部分:参数名和参数值,这个例子中,参数名是 wd,参数值是 python。
1.为文字添加阴影。<div class="demo">慕课网</div>.demo{ text-shadow:5px 5px 5px red;}效果图为文字添加阴影效果图制作一个文字发光效果。html,body{ background: #000;}.demo{ color: #fff; text-shadow:5px 5px 20px #fff,-5px -5px 20px #fff,5px -5px 20px #fff,-5px 5px 20px #fff;}效果图 制作一个文字发光效果效果图说明:其实就是在各个方向上都增加一个白色的阴影,在黑色的背景下就显得有发光的效果了。通过投影直至化制作一个3D的文字效果。.demo{ font-size: 30px; color: #fff; text-shadow:1px 1px hsl(0,0%,85%), 2px 2px hsl(0,0%,80%), 3px 3px hsl(0,0%,75%), 4px 4px hsl(0,0%,70%), 5px 5px hsl(0,0%,65%), 5px 5px 10px black;}效果图 制作一个3D的文字效果效果图说明:这个效果也是利用各种色组叠加来实现的。
Syntax: listen address[:port] [default_server] [ssl] [http2 | spdy] [proxy_protocol] [setfib=number] [fastopen=number] [backlog=number] [rcvbuf=size] [sndbuf=size] [accept_filter=filter] [deferred] [bind] [ipv6only=on|off] [reuseport] [so_keepalive=on|off|[keepidle]:[keepintvl]:[keepcnt]]; listen port [default_server] [ssl] [http2 | spdy] [proxy_protocol] [setfib=number] [fastopen=number] [backlog=number] [rcvbuf=size] [sndbuf=size] [accept_filter=filter] [deferred] [bind] [ipv6only=on|off] [reuseport] [so_keepalive=on|off|[keepidle]:[keepintvl]:[keepcnt]]; listen unix:path [default_server] [ssl] [http2 | spdy] [proxy_protocol] [backlog=number] [rcvbuf=size] [sndbuf=size] [accept_filter=filter] [deferred] [bind] [so_keepalive=on|off|[keepidle]:[keepintvl]:[keepcnt]]; Default: listen *:80 | *:8000; Context: serverlisten 指令的上下文环境是 server 指令,所以 listen 指令只能出现在 server 指令块中。此外,listen 指令的作用就是监听上层端口,将对应端口发来的请求进行拦截并处理。官方给了许多 listen 的使用示例:# 这些写法要考虑特定的环境和场景listen 127.0.0.1:8000;listen 127.0.0.1;listen 8000;listen *:8000;listen localhost:8000;# 比较特殊的用法,针对unix系统listen unix:/var/run/nginx.sock;
我们在本地新建一个目录,编辑一个名为 Dockerfile 的文件,注意文件名大小写。mkdir -p ~/docker/nginx/cd ~/docker/nginxvim Dockerfile# 构建一个基于ubuntu的docker定制镜像# 基础镜像FROM ubuntu# 镜像作者MAINTAINER my_name myemail@domain.com# 执行命令## 换成国内的软件源RUN sed -i 's/archive.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list RUN sed -i 's/security.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list ## 安装nginxRUN apt update >/dev/null 2>&1 RUN apt install nginx -y >/dev/null 2>&1# 暴露对外端口EXPOSE 80将代码保存下来,尝试构建镜像。 在当前 Dockerfile 文件所在目录执行如下命令:docker build --network=host -t ubuntu-nginx:v1 . --network=host 使用宿主机的网络连接代理容器的网络(在一些情况下,容器可能无法顺畅地连接外网)。-t ubuntu-nginx:v1 指定生产的镜像名称为 ubuntu-nginx ,版本号为 v1。
我们请求在页面上请求 80 端口,会出现 Nginx 的默认页面,也就是 html 目录下的那个 index.html 页面;另外直接访问http://localhost/50x.html 时候,会出现针对状态码为 50x 异常页面。接下来,我们添加一个简单的请求访问接口,模拟 500 异常,然后会将请求重定向到这个异常页面。在上述默认配置的 server 块中,我们添加一个新的匹配路径,如下:location /internal_error { return 500;}return 指令一般用于对请求的客户端直接返回响应状态码。在该作用域内 return 指令之后的所有 Nginx 配置都是无效的。可以使用在 server、location 以及 if 配置中。 除了支持跟状态码,还可以跟字符串或者 url 链接,比如写成这样的形式:return 200 ‘hello, world’这样,使用 -s reload 热加载 Nginx 后,我们直接在浏览器中敲 http://ip/internal_error, 就可以看到50x的异常页面了。
ServerBootstrap 的用法基本上都是固定的,一般对于新接触 Netty 的同学来说,会觉得这些模板代码比较多,难以理解。我们主要记住几个核心配置即可。指定线程模型: 通过.group(bossGroup, workerGroup) 给引导类配置两大线程组,这个引导类的线程模型也就定型了。其中 bossGroup 表示监听端口,accept 新连接的线程组;workerGroup 表示处理每一条连接的数据读写的线程组;指定 IO 模型: 通过.channel(NioServerSocketChannel.class) 来指定 NIO 模型。如果指定 IO 模型为 BIO,那么这里配置上 OioServerSocketChannel.class 类型即可,通常都是使用 NIO,因为 Netty 的优势就在于 NIO;指定处理逻辑: 通过 childHandler () 方法,给这个引导类创建一个 ChannelInitializer,这里主要就是定义后续每条连接的数据读写,业务处理逻辑;绑定端口号: 调用 bind (80),端口号自定义,不要和其他应用的端口号有冲突即可。
Future 可以通过四个核心方法来判断任务的执行情况。状态说明 isDone() 任务是否执行完成,无论成功还是失败 isSuccess() 任务是否执行采购 isCancelled() 任务是否被取消 cause() 获取执行异常信息执行过程状态的改变说明当一个异步任务操作开始的时候,一个新的 future 对象就会被创建。在开始的时候该 future 是处于未完成的状态,也就是说,isDone ()=false、isSuccess ()=false、isCancelled ()=false;只要该任务中任何一种状态结束了,无论是说成功、失败、或者被取消,那么整个 Future 就会被标记为已完成。注意的是,如果执行失败那么 cause () 方法会返回异常信息的内容。实例:ChannelFuture channelFuture=bootstrap.connect("127.0.0.1",80);channelFuture.addListener(new ChannelFutureListener() { public void operationComplete(ChannelFuture future) throws Exception { if(future.isDone()){ if(future.isSuccess()){ System.out.println("执行成功..."); }else if(future.isCancelled()){ System.out.println("任务被取消..."); }else if(future.cause()!=null){ System.out.println("执行出错:"+future.cause().getMessage()); } } }});
数学中的线段拥有 3 个属性:start,表示开始位置end,表示结束位置length,表示线段的长度,等于 end - start当修改属性 start 时,属性 length 会发生变化;当修改属性 end 时,属性 length 也会发生变化;如果修改属性 start 或者 end 时,忘记修改属性 length 的话,则会造成逻辑错误,示例代码如下:class Segment: def __init__(self, start, end): self.start = start self.end = end self.length = self.end - self.start def show(self): print('start = %d, end = %d, length = %d' % (self.start, self.end, self.length))segment = Segment(10, 100)segment.start = 20segment.show()在第 2 行,定义构造方法在第 5 行,使用属性 start 和 end 计算属性 length在第 7 行,定义方法 show,打印属性 start、end、length在第 10 行,创建线段 segment,设置 start = 10,end = 100在第 11 行,将 start 修改为 20在第 12 行,调用方法 show 打印属性 start、end、length程序运行输出结果如下:start = 20, end = 100, length = 90start 修改为 20 后,length 应该等于 80,但是程序输出表明 length 等于 90。由于 start 修改后,忘记修改 length,造成了这样的逻辑错误。
ch.pipeline().addLast(new LoginHandler()); 添加链表节点的时候,我们是手工 new 一个对象,其实也就是说,每个客户端连接进来的时候,都需要组建一条双向链表,并且都是 new 每个节点的对象,我们都知道每次 new 性能肯定是不高。Spring 的 IOC 其实就是解决手工 new 对象的,项目启动的时候把所有对象创建完放到 Spring 容器,后面每次使用的时候无需再创建,而是直接从容器里面获取,这种方式可以提高性能。同样道理,Netty 也提供类似的功能,那就是 @Shareable 注解修饰的 Handler,只要用该注解修饰之后,那么该 Handler 就会变成共享,也就是说被所有的客户端所共享,无需每次都创建,自然性能会得到提升。实例://使用注解修饰@ChannelHandler.Sharablepublic class ServerLoginHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { }}public class NettyServer { public static void main(String[] args) { NioEventLoopGroup bossGroup = new NioEventLoopGroup(); NioEventLoopGroup workerGroup = new NioEventLoopGroup(); //提前创建好 final ServerLoginHandler serverLoginHandler=new ServerLoginHandler(); ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap .group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<NioSocketChannel>() { protected void initChannel(NioSocketChannel ch) { //这里无需再创建,只需要传递实例即可 ch.pipeline().addLast(serverLoginHandler); } }); serverBootstrap.bind(80); }}
服务端收到将客户端发送的数据后,封装形成一个请求对象,在 Flask 中,请求对象是一个模块变量 flask.request,request 对象包含了众多的属性。假设 URL 等于 http://localhost/query?userId=123,则与 URL 参数相关的属性如下:属性说明urlhttp://localhost/query?userId=123base_urlhttp://localhost/queryhostlocalhosthost_urlhttp://localhost/path/queryfull_path/query?userId=123下面编写一个 Flask 程序 request.py,打印 request 中和 URL 相关的属性:#!/usr/bin/python3from flask import Flaskfrom flask import requestapp = Flask(__name__)def echo(key, value): print('%-10s = %s' % (key, value))@app.route('/query')def query(): echo('url', request.url) echo('base_url', request.base_url) echo('host', request.host) echo('host_url', request.host_url) echo('path', request.path) echo('full_path', request.full_path) print() print(request.args) print('userId = %s' % request.args['userId']) return 'hello'if __name__ == '__main__': app.run(port = 80)在第 10 行,定义路径 /query 的处理函数 query();在第 11 行到第 16 行,打印 request 对象中和 URL 相关的属性;URL 中的查询参数保存在 request.args 中,在第 20 行,打印查询参数 userId 的值。在浏览器中输入 http://localhost/query?userId=123,Flask 程序在终端输出如下:url = http://localhost/query?userId=123base_url = http://localhost/queryhost = localhosthost_url = http://localhost/path = /queryfull_path = /query?userId=123ImmutableMultiDict([('userId', '123')])userId = 123
这次,我们依然使用猫狗分类的例子来进行实现,具体的代码如下所示:注意:部分代码来自 TensorFlow 官方 API 。import tensorflow_datasets as tfdsimport tensorflow as tfimport numpy as nptrain_data, validation_data = tfds.load( "cats_vs_dogs", split=["train[:80%]", "train[80%:]"], as_supervised=True,)# 重新调整大小train_data = train_data.map(lambda x, y: (tf.image.resize(x, (150, 150)), y))validation_data = validation_data.map(lambda x, y: (tf.image.resize(x, (150, 150)), y))# 分批次train_data = train_data.batch(32)validation_data = validation_data.batch(32)# 迁移模型base_model = tf.keras.applications.Xception( weights="imagenet", input_shape=(150, 150, 3), include_top=False,)base_model.trainable = False# 定义输入inputs = tf.keras.Input(shape=(150, 150, 3))# 数据正则化norm_layer = tf.keras.layers.experimental.preprocessing.Normalization()x = norm_layer(inputs)mean = np.array([127.5] * 3)norm_layer.set_weights([mean, mean ** 2])# 数据经过迁移模型x = base_model(x, training=False)# 数据经过自定义网络x = tf.keras.layers.GlobalAveragePooling2D()(x)outputs = tf.keras.layers.Dense(1)(x)model = tf.keras.Model(inputs, outputs)model.summary()model.compile( optimizer=tf.keras.optimizers.Adam(), loss=tf.keras.losses.BinaryCrossentropy(from_logits=True), metrics=[tf.keras.metrics.BinaryAccuracy()],)model.fit(train_ds, epochs=20, validation_data=validation_ds)在这里的代码之中,我们有几处需要注意的地方:在数据获取方面,我们采用了 tfds.load 函数,该函数能够直接获取相应的内置数据集,同时进行相应的分割,这里我们按照 8:2 的比例来进行训练集、测试集的划分;我们使用 map 函数,来将所有的数据的图片重新调整至(150, 150)大小,我们将图片调整至相同大小是为了方便后面的处理;使用 tf.keras.applications.Xception API 来获取已经预训练的 Xception 模型,在该 API 之中,包含三个参数:weights:表示在哪个数据集上训练;input_shape:表示输入图片的形状;include_top=False:表示不含顶层网络,因为我们要定义自己的网络。然后我们使用 base_model.trainable=False 语句来将基本模型的训练参数冻结,这样我们就不能训练 Xception 的参数。我们使用了 tf.keras.layers.experimental.preprocessing.Normalization 这个 API 来进行数据的正则化,我们需要通过 norm_layer.set_weights () 设定它的权重:第一个参数是输入的每个通道的平均值,这里是 255/2=127.5;第二个参数是第一个参数的平方;最后我们采用了一种新的定义模型的方式:先定义一个 Input ,然后将该 Input 逐次经过自己需要处理的网络层得到 output,最后通过 tf.keras.Model (inputs, output) 来让 TensorFlow s 根据数据的流动过程来自动生成网络模型。最终我们可以得到结果:Model: "functional_5"_________________________________________________________________Layer (type) Output Shape Param # =================================================================input_10 (InputLayer) [(None, 150, 150, 3)] 0 _________________________________________________________________normalization_3 (Normalizati (None, 150, 150, 3) 7 _________________________________________________________________xception (Functional) (None, 5, 5, 2048) 20861480 _________________________________________________________________global_average_pooling2d_2 ( (None, 2048) 0 _________________________________________________________________dropout_2 (Dropout) (None, 2048) 0 _________________________________________________________________dense_2 (Dense) (None, 1) 2049 =================================================================Total params: 20,863,536Trainable params: 2,049Non-trainable params: 20,861,487_________________________________________________________________Epoch 1/20291/291 [==============================] - 9s 31ms/step - loss: 0.1607 - binary_accuracy: 0.9313 - val_loss: 0.0872 - val_binary_accuracy: 0.9703Epoch 2/20291/291 [==============================] - 8s 27ms/step - loss: 0.1181 - binary_accuracy: 0.9501 - val_loss: 0.0869 - val_binary_accuracy: 0.9690......Epoch 20/20291/291 [==============================] - 8s 27ms/step - loss: 0.0914 - binary_accuracy: 0.9841 - val_loss: 0.0875 - val_binary_accuracy: 0.9765我们可以看到,我们的模型最终达到了 97% 的分类准确率,这是一个非常高的准确率,而这得益于 Xception 模型强大的特征提取能力。
前端应用的部署更加简单,我们直接在云服务器上下载 nginx 然后解压。打开网址 http://nginx.org/en/download.html ,点击下图中的链接下载即可。nginx 下载链接下载解压后,将前端页面直接放到 nginx/html 目录下即可。当然如果有很多网页,可以先在该目录下建立子目录便于归类网页。我们建立 shop-front 目录(表示商城系统的前端项目),然后将网页放入其中,效果如下:商城系统前端项目目录内容注意还需要修改 goods.html 中访问的后端 URL 地址,假设云服务器的公网 IP 为 x.x.x.x ,则修改为:实例:$.ajax({ type: "GET", url: "http://x.x.x.x:8080/goods", //后端接口地址 dataType: "json", contentType: "application/json; charset=utf-8", success: function (res) { $.each(res, function (i, v) { row = "<tr>"; row += "<td>" + v.id + "</td>"; row += "<td>" + v.name + "</td>"; row += "<td>" + v.price + "</td>"; row += "<td>" + v.pic + "</td>"; row += "</tr>"; $("#goodsTable").append(row); }); }, error: function (err) { console.log(err); } });此处解释下后端地址 http://x.x.x.x:8080/goods , HTTP 代表协议, x.x.x.x 代表云服务器公网地址, 8080 是我们后端项目的启动端口,由于我们没有在配置文件中设置,所以默认就是 8080 ,最后 goods 是控制器中设定的后端接口路径。双击 nginx.exe 启动 nginx ,由于 nginx 默认启动端口是 80 ,所以此时访问 http://x.x.x.x ,效果如下,说明 nginx 启动成功!nginx 已启动成功
Markdown 的目标是整个文档的风格统一,但是既然依托于 html 语法,那我们就依然能通过修改 CSS 的方式定制分割线的样式。实例 2:修改分割线的粗细### 分割线的尺寸#### 3px 宽线条___#### 5px 宽线条___#### 10px 宽线条___<style>hr:nth-of-type(1) { border-width: 3px 0 0 0 !important;}hr:nth-of-type(2) { border-width: 5px 0 0 0 !important;}hr:nth-of-type(3) { border-width: 10px 0 0 0 !important;}</style>渲染结果如下:实例 3:修改分割线的颜色:### 分割线的颜色#### 红色分割线___#### 蓝色分割线___#### 半透明的黑色分割线___#### 渐变色分割线___<style>hr:nth-of-type(1) { border-color: red !important;}hr:nth-of-type(2) { border-color: #00F !important;}hr:nth-of-type(3) { border-color: #0005 !important;}hr:nth-of-type(4) { border-image: linear-gradient(to right, #F00, #0F0 20%, #00F 80%, #000) 1 !important;}</style>渲染结果如下:实例 4:修改分割线的类型:### 分割线的类型#### 实线分隔线___#### 虚线分割线___#### 点状分割线___#### 双线分割线___#### 凹槽分割线___#### Inset分割线___#### Outset分割线___<style>hr { border-style: none !important; border-top-width: 5px !important;}hr:nth-of-type(1) { border-top-style: solid !important;}hr:nth-of-type(2) { border-top-style: dashed !important;}hr:nth-of-type(3) { border-top-style: dotted !important;}hr:nth-of-type(4) { border-top-style: double !important;}hr:nth-of-type(5) { border-top-style: groove !important;}hr:nth-of-type(6) { border-top-style: ridge !important;}hr:nth-of-type(7) { border-top-style: inset !important;}hr:nth-of-type(8) { border-top-style: outset !important;}</style>渲染结果如下:
在了解上面这些基本术语后,我们介绍下当在浏览器中敲下 www.baidu.com 这个 URL,到百度返回给我们搜索首页,这个过程中究竟发生了哪些事情?解析输入 URL 中包含的信息,比如 HTTP 协议和域名 (baidu.com);客户端先检查本地是否有对应的 IP 地址,若找到则返回响应的 IP 地址。若没找到则请求在 ISP 的 DNS 服务器上。如果还没找到,则请求会被发向根域名服务器,直到找到对应的 IP 地址;浏览器在 DNS 服务器中找到对应域名的 IP 地址,然后结合 URL 中的端口(没有指明端口则使用默认端口,HTTP 协议的默认端口是 80,HTTPS 的默认端口是 443) 组成新的请求 URL,并与百度的 Web 服务建立 TCP 连接;浏览器根据用户操作向百度的 Web 服务器发送 HTTP 请求;Web 服务器接收到该请求后会根据请求的路径查找对应的 Web 资源并返回;最后客户端浏览器将这些返回的 Web 信息 (包括图片、HTML 静态页面,JS 等)组织成用户可以查看的网页形式,最后就得到了我们熟悉的那个 百度一下,你就知道 的搜索主页了。
在 Web 开发过程中,经常会遇到 “路由” 的概念。简单来说,路由就是 URL 到处理函数的映射。Web 后端处理大致流程可以看成这样:浏览器发出请求服务器端监听到 80 端口的请求,解析请求的 url 路径根据服务器的路由配置,找到对应 url 对应的处理函数运行处理函数生成一段 HTML 文本,并返回给浏览器假设一个论坛系统由如下数据构成:主题,每个主题包含有标题和内容,使用 topicID 标识该主题用户,每个用户包含姓名和密码,使用 userID 标识该用户论坛的域名是 www.bbs.com,它向外界提供了若干可访问的 URL:URL功能http://www.bbs.com/topics/12373访问 topicID 为 12373 的主题http://www.bbs.com/users/1353访问 userID 为 1353 的用户页面在服务器端有两个处理页面函数:showTopic(topicId) 显示指定 topicId 的主题内容showUser(userId) 显示指定 userId 的用户信息在下图中,当用户请求形式为 /topics/xxx 的 URL 时,服务器需要找到 showTopic 函数处理该请求;当用户请求形式为 /users/xxx 的 URL 时,服务器需要找到 showUser 函数处理该请求。URL 到处理函数的映射,就被称为路由。Web 开发框架提供了路由配置的功能,可以方便的指定处理 URL 的函数。