第3章_Java仿微信全栈高性能后台+移动客户端
当服务器构建完毕并且启动之后,我们通过网页URL地址就可以访问这台服务器,并且服务器会向网页输出Hello Netty这样几个字。
Netty有三种线程模型:单线程、多线程、主从线程。Netty官方推荐使用主从线程组,因为主从线程组比较高效。因为任何的服务器,不管是tomcat还是Jetty,都会有一个启动的类bootstrap。这样的一个Server类我们也会通过Netty在我们的服务器里面去进行一个设置,去打开去定义。设置完成Sevrer类之后去设置Channel。讲NIO的时候讲过,当客户端和服务端建立连接之后,那么它就会有一个双向的通道。这个通道就是channel。所以我们需要在服务器里面定义channel的类型,这样的channel的类型就是NIO的类型。channel是会有一堆的助手类和handler去对它进行处理,比方说编解码处理,或者说写数据读数据等等这样的操作。这些所有的操作都是要归类到一个助手类的一个初始化器里面。它就是一个类,在这个类里面会添加很多很多的助手类,你可以把它理解为拦截器,你可以配置多个拦截器去拦截我们的channel。当一些相应的内容在Server里面去写完设置之后,就针对我们的Server需要去启动。我们就需要去启动和监听Server。监听要设置某一个端口,启动完了之后我们也是要针对我们的服务器去做一个关闭的监听。因为你可以去关闭服务器,关闭服务器之后你需要去进行一个优雅的关闭。
项目名称Artifact Id:imooc-netty-hello
使用netty先把相应的依赖加入到工程里面来。
https://mvnrepository.com/artifact/io.netty/netty-all/5.0.0.Alpha1
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>5.0.0.Alpha1</version>
</dependency>
/** * Copyright © 2018Nathan.Lee.Salvatore. All rights reserved. * * @Title: HelloServer.java * @Prject: imooc-netty-hello * @Package: com.hello.server * @Description: TODO * @author: ZHONGZHENHUA * @date: 2018年11月8日 上午1:53:01 * @version: V1.0 */
package com.hello.server; /**
* @author ZHONGZHENHUA
*
*/
public class HelloServer { /** * @Title:HelloServer * @Description:TODO * @param args * @author: ZHONGZHENHUA * @date: 2018年11月8日 上午1:53:01 */
public static void main(String[] args) {
// TODO Auto-generated method stub } }
创建一对线程组,那么它是由两个线程池构建的。一对线程组就是两个线程池。EventLoop是一个线程,Group是一个组。EventLoopGroup的解释:
Special EventExecutorGroup which allows to register Channel's that getprocessed for later selection during the event loop.
它可以允许让channel去进行注册。当有客户端连接到我们服务端之后,我们会通过这样的一个线程组去注册。注册完了之后它会获得它的一个相应的一些客户端的channels然后再直接丢给我们下面的一个线程组去处理。
服务端的启动类叫做ServerBootStrap,它是专门用于去启动的。
Bootstrap sub-class which allows easy bootstrap of ServerChannel
它可以让我们简单地去启动我们的ServerChannel。
前端有一个CSS框架,它也叫做BootStrap,它是完全不一样的。我们的线程模型是一个主从的线程模型,我们的server里面要设置两个线程组,并且它们的任务分配会由Server自动处理,我们开发者不需要额外地关注。
当客户端和Server建立链接之后,我们会有相应的通道的产生。这通道是什么类型呢?我们也是要进行相应的设置。我们使用的是Nio,所以我们会使用NioServerSocketChannel.
通道有了之后,当客户端和从线程组Server建立链接之后,我们的从线程池将一组线程组会对我们相应的通道做处理。做处理的时候针对channel其实它会有一个一个的管道,这个在下节讲初始化器的时候会去说。这个其实就是一个初始化器,这个初始化器的话针对每一个channel都会有。初始化器里面会有很多很多的助手类,很多很多的助手类是针对我们的每一个channel去做不同的处理的,相当于是一个拦截器。这里我们先暂时这样理解,下一节我们会写一个具体的图例来编写初始化器。
<!-- https://mvnrepository.com/artifact/io.netty/netty-all -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.25.Final</version>
</dependency>
childHandler,针对从线程组去做一个相应的操作。让netty它自己的助手类,netty的类很丰富,它有很多很多的助手类,并且助手类可以让我们开发者去自定义去重写,可以去写成我们自己所需要的一个样式。
serverBootStrap.bind(8088).sync();
绑定一个端口8088,绑定是需要耗时需要等待,所以它有一个方法叫做sync()。绑定完一个端口之后设置为一个同步的启动方式。设置完之后netty它会一直在这里等待,等待8088启动完毕。
启动完毕之后需要设置关闭的监听。监听是针对我们当前某一个通道。每一个客户端都会有一个channel。监听这样的channel是否关闭的话,那么我们只需要.channel()就可以了。.channel()就是获取当前某个客户端对应的一个管道。
代码其实是OK了,但是整体的线程组还没有被关闭。当服务器启动完之后,我们要去关闭服务器,关闭完服务器之后那么针对现在的两个线程组我们要去优雅地关闭。netty也提供给我们一个如何去优雅关闭的方式。
这样的一个Server的启动类其实是写完了。下一节我们会针对childHandler设置一个子处理器(初始化器)。
/imooc-netty-hello/src/main/java/com/hello/server/HelloServer.java
/** * Copyright © 2018Nathan.Lee.Salvatore. All rights reserved. * * @Title: HelloServer.java * @Prject: imooc-netty-hello * @Package: com.hello.server * @Description: 实现客户端发送一个请求,服务器会返回hello netty * @author: ZHONGZHENHUA * @date: 2018年11月8日 上午1:53:01 * @version: V1.0 */
package com.hello.server; import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel; /**
* @author ZHONGZHENHUA
*
*/
public class HelloServer { /** * @Title:HelloServer * @Description:TODO * @param args * @author: ZHONGZHENHUA * @date: 2018年11月8日 上午1:53:01 */
public static void main(String[] args) throws Exception{
// TODO Auto-generated method stub
// 定义一对线程组
// 主线程组,用于接受客户端的连接,但是不做任何处理,跟老板一样,不做事
EventLoopGroup bossGroup = new NioEventLoopGroup();
// 从线程组,老板线程组会把任务丢给他,让手下线程组去做任务
EventLoopGroup workerGroup = new NioEventLoopGroup();
try { // netty服务器的创建, ServerBootstrap 是一个启动类
ServerBootstrap serverBootStrap = new ServerBootstrap();
serverBootStrap.group(bossGroup, workerGroup)//设置主从线程组
.channel(NioServerSocketChannel.class)//设置nio的双向通道
.childHandler(null);// 子处理器,用于处理workerGroup
// 启动server,并且设置8088为启动的端口号,同时启动方式为同步
ChannelFuture channelFuture = serverBootStrap.bind(8088).sync();
// 监听关闭的channel,设置为同步方式
channelFuture.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
} }
上一节留下一个子处理器没有讲。如何设置子处理器(channel的初始化器)。每一个client和server连接完之后都会有一个channel,每一个channel有一个管道。管道会由很多个handler共同组成。当channel注册完之后,就会有一个管道。管道需要开发者编写,其实就是一个初始化器。管道需要我们设置很多的助手类handler。助手类是针对channel去做一些相应的处理。设置的handler都会在管道里面。当客户端和服务端交互的时候,相应的助手类会针对我们的请求去做相应的处理。你可以把这块内容当做拦截器去理解。管道可以被当做一个大的拦截器,大拦截器里面会有很多的小拦截器。当请求过来的时候我们会逐个逐个地进行拦截。
HelloServerInitializer其实就是把我们的handler逐个去添加。既然是针对channel去初始化,这里我们会使用channel的初始化器。我们的通信是socket通信,使用的是socket类型的channel。Channelnitializer就是对我们的channel进行初始化。
channel里面有管道,我们需要在客户端里面去做一些相应的处理。其实是为我们客户端所对应的channel做一层层的处理,所以channel需要添加相应的handler助手类。
在pipeline里面会有很多的助手类,或者称之为拦截器。我们把它理解为拦截器的话可能会更加的便于理解。
不管是开发者自定义的handler还是netty它所提供的handler,我们都可以一个一个地添加到pipeline管道里面去。比方说我们添加的第一个handler是由netty提供的。在HTTP网络上打开我们的链接,访问我们的服务器之后会返回一个相应的字符串hello netty。既然是HTTP,我们就会使用到HTTP Server的一些相应的编解码器。
用户请求我们的服务端之后,我们要返回一个hello netty这样的一个字符串,所以我们要添加一个自定义的handler。
/imooc-netty-hello/src/main/java/com/hello/server/HelloServerInitializer.java
/** * Copyright © 2018Nathan.Lee.Salvatore. All rights reserved. * * @Title: HelloServerInitializer.java * @Prject: imooc-netty-hello * @Package: com.hello.server * @Description: 初始化器,channel注册后,会执行里面的相应的初始化方法 * @author: ZHONGZHENHUA * @date: 2018年11月8日 下午3:05:54 * @version: V1.0 */
package com.hello.server; import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpServerCodec; /**
* @author ZHONGZHENHUA
*
*/
public class HelloServerInitializer extends ChannelInitializer<SocketChannel>{ @Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
// TODO Auto-generated method stub
// 通过SocketChannel去获得对应的管道
ChannelPipeline pipeline = socketChannel.pipeline();
// 通过管道,添加handler
// HttpServerCodec是由netty自己提供的助手类,可以理解为拦截器
// 当请求到服务端,我们需要做解码,响应到客户端做编码
pipeline.addLast("HttpServerCodec", new HttpServerCodec()); // 添加自定义的助手类,返回“hello netty~”
pipeline.addLast("customHandler", null);
} }
/imooc-netty-hello/src/main/java/com/hello/server/HelloServer.java
/** * Copyright © 2018Nathan.Lee.Salvatore. All rights reserved. * * @Title: HelloServer.java * @Prject: imooc-netty-hello * @Package: com.hello.server * @Description: 实现客户端发送一个请求,服务器会返回hello netty * @author: ZHONGZHENHUA * @date: 2018年11月8日 上午1:53:01 * @version: V1.0 */
package com.hello.server; import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel; /**
* @author ZHONGZHENHUA
*
*/
public class HelloServer { /** * @Title:HelloServer * @Description:TODO * @param args * @author: ZHONGZHENHUA * @date: 2018年11月8日 上午1:53:01 */
public static void main(String[] args) throws Exception{
// TODO Auto-generated method stub
// 定义一对线程组
// 主线程组,用于接受客户端的连接,但是不做任何处理,跟老板一样,不做事
EventLoopGroup bossGroup = new NioEventLoopGroup();
// 从线程组,老板线程组会把任务丢给他,让手下线程组去做任务
EventLoopGroup workerGroup = new NioEventLoopGroup();
try { // netty服务器的创建, ServerBootstrap 是一个启动类
ServerBootstrap serverBootStrap = new ServerBootstrap();
serverBootStrap.group(bossGroup, workerGroup)//设置主从线程组
.channel(NioServerSocketChannel.class)//设置nio的双向通道
.childHandler(new HelloServerInitializer());// 子处理器,用于处理workerGroup
// 启动server,并且设置8088为启动的端口号,同时启动方式为同步
ChannelFuture channelFuture = serverBootStrap.bind(8088).sync();
// 监听关闭的channel,设置为同步方式
channelFuture.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
} }
当一个Server启动完毕之后,它就会针对我们的childHandler,其实就是针对我们的workerGroup做一个初始化,把我们的相应的一些channel进行注册。管道里面放一些编解码器、自定义的处理器等等,这些东西全部都归在一起,形成了我们的一个服务端。这一节现这样,下一节我们把自定义的handler进行编写。
编写属于开发者自己的一个自定义的一个助手类,并且在这里返回一个hello netty这样的一个字符串。针对客户端向服务端发送起请求之后,NIO的原理是,请求过来数据过来,它是把首先数据放在缓冲区,然后服务端再从这个缓冲区里面去读,客户端向服务端写数据是请求的话,其实就是一个入站或者说是一个入境。如果有朋友做过云服务器的配置的话,那么其实针对网关安全组或者是防火墙的话,那么就会有一个入站的概念。那么在这个地方其实也是一个类似的概念,它是入站。我们现在是一个HTTP的请求,过来的话我们会写上HttpObject这样的类型。CustomHandler的channelRead0方法是从缓冲区里面读数据。既然是在handler里面,其实它是一个管道。ChannelHandlerContext是上下文对象,上下文对象可以获取channel。接下来我们要把相应的数据刷到客户端去,在这里我们先打印一下客户端的地址。接下来我们要发送内容消息,发消息我们并不是直接去发,我们要通过缓冲区。我们需要把数据拷贝到缓冲区ByteBuf。Unpooled可以深拷贝ByteBuf。
charset是一个字符值,字符值我们一般都会使用UTF-8。可以使用netty提供的CharsetUtil的UTF-8。copiedBuffer是创建一个新的buf(缓冲区),在NIO的模型里面提到过不管是读数据还是写数据我们都是通过一个缓冲区来进行一个数据的交换/交互。
要把内容content刷到客户端,刷到客户端其实就是一个HTTP的response,它是一个响应。我们可以使用一个新的接口FullHttpResponse,DefaultFullHttpResponse是默认的专门用于处理HTTP的响应。version是HTTP的版本号,HttpResponseStatus.OK其实就是200,
validateHeaders是内容,响应首先是版本号和状态码,然后就是内容,validateHeaders就是content。response的一个基本设置有了。针对数据的类型、长度也是要设置。它是一个HTTP Header。这种编程方式其实就是类似于函数式的编程。第一个需要设置数据的类型。数据类型返回出去是一个文本/字符串,图片和JSON对象也行。
数据类型设置好了就进行一个长度的设置。content.readableBytes(),ByteBuf可读的长度。它是一个可读的长度,它会把整个长度给取出来返回。
我们拿到这样的长度再返回到客户端,先响应出去就可以了。当现在准备就绪之后,我们需要把相应的内容/消息给刷出去,就是我们的response。ctx.write是把response写到缓冲区,但是并不会把消息刷到客户端。ctx.writeAndFlush不仅仅是进行一个写,它还会进行一个刷。它会先把数据写到缓冲区,然后再刷到客户端。
这个就是一个自定义的处理类。
/imooc-netty-hello/src/main/java/com/hello/server/CustomHandler.java
/** * Copyright © 2018Nathan.Lee.Salvatore. All rights reserved. * * @Title: CustomHandler.java * @Prject: imooc-netty-hello * @Package: com.hello.server * @Description: 创建自定义助手类 * @author: ZHONGZHENHUA * @date: 2018年11月8日 下午9:55:36 * @version: V1.0 */
package com.hello.server; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.util.CharsetUtil; /**
* @author ZHONGZHENHUA
*
*/
//SimpleChannelInboundHandler: 对于请求来讲, 其实相当于[入站,入境]
public class CustomHandler extends SimpleChannelInboundHandler<HttpObject>{ @Override
protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
// TODO Auto-generated method stub
// 获取channel
Channel channel = ctx.channel(); // 显示客户端的远程地址
System.out.println(channel.remoteAddress()); // 定义发送的数据消息
ByteBuf content = Unpooled.copiedBuffer("Hello netty~", CharsetUtil.UTF_8); // 构建一个http response
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content); // 为响应增加数据类型和长度
response.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/plain");
response.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes()); // 把响应刷到客户端
ctx.writeAndFlush(response);
} }
编写完毕之后我们需要把Handler放到初始化器里面去,让channel一开始注册的时候就要添加到我们的pipeline里面去,相当于重新在这里面注册了一个拦截器。
/imooc-netty-hello/src/main/java/com/hello/server/HelloServerInitializer.java
/** * Copyright © 2018Nathan.Lee.Salvatore. All rights reserved. * * @Title: HelloServerInitializer.java * @Prject: imooc-netty-hello * @Package: com.hello.server * @Description: 初始化器,channel注册后,会执行里面的相应的初始化方法 * @author: ZHONGZHENHUA * @date: 2018年11月8日 下午3:05:54 * @version: V1.0 */
package com.hello.server; import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpServerCodec; /**
* @author ZHONGZHENHUA
*
*/
public class HelloServerInitializer extends ChannelInitializer<SocketChannel>{ @Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
// TODO Auto-generated method stub
// 通过SocketChannel去获得对应的管道
ChannelPipeline pipeline = socketChannel.pipeline();
// 通过管道,添加handler
// HttpServerCodec是由netty自己提供的助手类,可以理解为拦截器
// 当请求到服务端,我们需要做解码,响应到客户端做编码
pipeline.addLast("HttpServerCodec", new HttpServerCodec()); // 添加自定义的助手类,返回“hello netty~”
//pipeline.addLast("customHandler", null);
pipeline.addLast("customHandler", new CustomHandler());
} }
现在服务端和初始化器以及自定义的一个助手类全部都创建完毕。
我们监听的端口是8088。
// 显示客户端的远程地址
System.out.println(channel.remoteAddress());
在自定义的CustomHandler这一块打印客户端的远程地址的时候打印了很多,因为我们的msg接收的时候没有对它做一个类型的判断。
判断msg是不是一个HTTP Request的请求类型,在打印客户端的远程地址之前先判断msg的类型
/imooc-netty-hello/src/main/java/com/hello/server/CustomHandler.java
/** * Copyright © 2018Nathan.Lee.Salvatore. All rights reserved. * * @Title: CustomHandler.java * @Prject: imooc-netty-hello * @Package: com.hello.server * @Description: 创建自定义助手类 * @author: ZHONGZHENHUA * @date: 2018年11月8日 下午9:55:36 * @version: V1.0 */
package com.hello.server; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.util.CharsetUtil; /**
* @author ZHONGZHENHUA
*
*/
//SimpleChannelInboundHandler: 对于请求来讲, 其实相当于[入站,入境]
public class CustomHandler extends SimpleChannelInboundHandler<HttpObject>{ @Override
protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
// TODO Auto-generated method stub
// 获取channel
Channel channel = ctx.channel(); if(msg instanceof HttpRequest) { // 显示客户端的远程地址
System.out.println(channel.remoteAddress()); // 定义发送的数据消息
ByteBuf content = Unpooled.copiedBuffer("Hello netty~", CharsetUtil.UTF_8); // 构建一个http response
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content); // 为响应增加数据类型和长度
response.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/plain");
response.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes()); // 把响应刷到客户端
ctx.writeAndFlush(response);
}
} }
刷新一下之后,刷新页面的时候谷歌浏览器会针对服务端发送两次请求, 第一次请求localhost是我们所需要的,
favicon.ico,你请求每一个网站的时候,它其实默认会有这样的一个图标的请求,这个和我们其实没有任何的关系我们不需要去管。我们在请求后端的时候其实我们并没有加相应的路由,就相当于是一个Spring Boot或者说Spring MVC里面的一个RequestMapping。我们没有在后边去加相应的请求的路径,所以它统一了,只要你去访问我们的8088这样的一个端口,那么它就会直接到我们的Handler里面去。
还是会返回Hello netty~,因为它没有去做相应的捕获。如果要屏蔽favicon.ico可以去设置可以去获取请求的路径,然后再去截取再去判断,如果是这样的一个ico那么就直接return不要去做额外的处理。
content-type是后端设置的文本类型text/plain,content-length是content.readableBytes(),也就是这个字符串Hello netty~的长度。
// 构建一个http response
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content);
HTTP 1_1默认会开启长链接keep-alive,那么这样针对于我们的客户端和服务端请求传输的效率、速度会比HTTP1.0要快很多。
response是后端返回到前端的代码。
讲一个扩展知识,不通过浏览器也可以去访问服务器。需要一个linux服务器,linux服务器和这台主机是需要相互ping通的。linux和本地是可以相互ping通。ping通之后我们使用curl可以和当前这个Hello netty~进行交互。
看来不是处于同一个局域网是很难访问windows主机的。
第3章_Java仿微信全栈高性能后台+移动客户端的更多相关文章
- 第4章_Java仿微信全栈高性能后台+移动客户端
基于web端使用netty和websocket来做一个简单的聊天的小练习.实时通信有三种方式:Ajax轮询.Long pull.websocket,现在很多的业务场景,比方说聊天室.或者手机端onli ...
- “全栈2019”113篇Java基础学习资料及总结
难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java第 ...
- 微信小程序商城构建全栈应用 Thinkphp5
课程——微信小程序商城构建全栈应用[目录]第1章 前言:不同的时代,不同的Web第2章 环境,工具与准备工作第3章 模块,路由与获取请求参数第4章 构建验证层第5章 REST与RESTFul第6章 A ...
- 微信小程序云开发-从0打造云音乐全栈小程序
第1章 首门小程序“云开发”课程,你值得学习本章主要介绍什么是小程序云开发以及学习云开发的重要性,并介绍项目的整体架构,真机演示项目功能,详细介绍整体课程安排.课程适用人群以及需要掌握的前置知识.通过 ...
- C蛮的全栈之路-序章 技术栈选择与全栈工程师
目录 C蛮的全栈之路-序章 技术栈选择与全栈工程师C蛮的全栈之路-node篇(一) 环境布置C蛮的全栈之路-node篇(二) 实战一:自动发博客 博主背景 985院校毕业,至今十年C++开发工作经验, ...
- python全栈开发中级班全程笔记(第二模块、第四章(三、re 正则表达式))
python全栈开发笔记第二模块 第四章 :常用模块(第三部分) 一.正则表达式的作用与方法 正则表达式是什么呢?一个问题带来正则表达式的重要性和作用 有一个需求 : 从文件中读取所有联 ...
- python全栈开发中级班全程笔记(第二模块、第四章)(常用模块导入)
python全栈开发笔记第二模块 第四章 :常用模块(第二部分) 一.os 模块的 详解 1.os.getcwd() :得到当前工作目录,即当前python解释器所在目录路径 impor ...
- python全栈开发中级班全程笔记(第二模块、第三章)(员工信息增删改查作业讲解)
python全栈开发中级班全程笔记 第三章:员工信息增删改查作业代码 作业要求: 员工增删改查表用代码实现一个简单的员工信息增删改查表需求: 1.支持模糊查询,(1.find name ,age fo ...
- 全栈开发工程师微信小程序-中(下)
全栈开发工程师微信小程序-中(下) 微信小程序视图层 wxml用于描述页面的结构,wxss用于描述页面的样式,组件用于视图的基本组成单元. // 绑定数据 index.wxml <view> ...
随机推荐
- webservice 交错数组
net webservices public DataSet SelectOPQuestionByWhere(string strWhere, string[][] strArry) { if (!k ...
- JAVA常见函数
输入函数 : Scanner cin=new Scanner(System.in); int a=cin.nextInt(); //输入一个int数据 double dl=cin.nextDou ...
- 从virustotal上下载病毒样本
#!/usr/bin/env python import os import csv #import Queue import zipfile import requests import argpa ...
- RNN、LSTM、Char-RNN 学习系列(一)
RNN.LSTM.Char-RNN 学习系列(一) zoerywzhou@gmail.com http://www.cnblogs.com/swje/ 作者:Zhouw 2016-3-15 版权声明 ...
- 面试题46:求1+2+...+n
题目:求1+2+...+n,要求不能使用乘除法.for.while.if.else.swithc.case等关键字及条件判断语句(A?B:C). 解法一:利用构造函数求解 class Temp { p ...
- Shell编程-运算符
1.declare命令 declare声明变量类型:declare [+/-][选项] 变量名 -:给变量设定类型属性 +:取消变量的类型属性 -a:将变量声明为数组型 -i:整数型 -x:环境变量 ...
- 【SQL查询】查询的列起别名_AS
方法一: 以as关键字指定字段别名,as在select的字段和别名之间. 方法二: 直接在字段名称后面加上别名,中间以空格隔开.
- git教程1-git工作原理与初始化仓库
一.git工作原理 1.git是版本控制器,因此管理的是版本,每一次提交commit就是新建一个版本. 2.分支:git主分支可以存放一个阶段已经完成好的版本,而修改版本则放置在次分支上. 3.融合: ...
- mysql之 xtrabackup原理、备份日志分析、备份信息获取
一. xtrabackup备份恢复工作原理: extrabackup备份简要步骤 InnoDB引擎很大程度上与Oracle类似,使用redo,undo机制,XtraBackup在备份的时候,以read ...
- bzoj 3398 [Usaco2009 Feb]Bullcow 牡牛和牝牛——前缀和优化dp / 排列组合
题目:https://www.lydsy.com/JudgeOnline/problem.php?id=3398 好简单呀.而且是自己想出来的. dp[ i ]表示最后一个牡牛在 i 的方案数. 当前 ...