Gradle 的优点 原文:https://blog.csdn.net/achenyuan/article/details/80682288
1. 按约定声明构建和建设;
2. 强大的支持多工程的构建;
3. 强大的依赖管理(基于Apache Ivy),提供最大的便利去构建工程;
4. 全力支持已有的 Maven 或者Ivy仓库基础建设;
5. 支持传递性依赖管理,在不需要远程仓库和pom.xml和ivy配置文件的前提下;
6. 基于groovy脚本构建,其build脚本使用groovy语言编写;
7. 具有广泛的领域模型支持构建;
8. 深度 API;
9. 易迁移;
10. 自由和开放源码,Gradle是一个开源项目,基于 ASL 许可。
下面是Gradle 基本配置的简介
build.gradle (作用类似于 maven 中的epom 文件)
plugins { id 'java' id 'war' } group 'gradle02-projext' version '1.0-SNAPSHOT' sourceCompatibility = 1.8 dependencies { testCompile group: 'junit', name: 'junit', version: '4.11' testCompile group: 'junit', name: 'junit', version: '4.12' }
原文位置(https://blog.csdn.net/cluzax/article/details/46583771)
目录文件 | 作用 |
---|---|
.gradle | gradle项目产生文件(自动编译工具产生的文件) |
.idea | IDEA项目文件(开发工具产生的文件) |
app | 其中一个module,复用父项目的设置,可与父项目拥有相同的配置文件 |
build | 自动构建时生成文件的地方 |
gradle | 自动完成gradle环境支持文件夹 |
.gitignore | git源码管理文件 |
build.gradle | gradle 项目自动编译的配置文件 |
gradle.properties | gradle 运行环境配置文件 |
gradlew | 自动完成 gradle 环境的linux mac 脚本,配合gradle 文件夹使用 |
gradlew.bat | 自动完成 gradle 环境的windows 脚本,配合gradle 文件夹使用 |
local.properties | Android SDK NDK 环境路径配置 |
*.iml | IDEA 项目文件 |
setting.gradle | gradle 项目的子项目包含文件 |
- .gradle .idea 是在分别在 gradle ,IDEA 运行时候会生成的文件,一般这样的文件也不会纳入源代码管理之中。
- app文件夹,是其中一个module,里面的文件内容与父类差不多,若没有定义,则在项目中使用父类的设置(意思就是,里面也能包含build.gradle、gradle.properties、setting.gradle 等相关gradle文件,怎么理解?其实每一层都是一个module,整个项目是一个大的 module 而已)
- gradle 文件夹,用于保存gradle 下载路径的配置文件位置,用于没有gradle环境的环境初始化使用
- build.gradle 项目的编译环境配置,比如制定项目依赖的lib包。
- gradle.properties 配置gradle运行环境的文件,比如配置gradle运行模式,运行时jvm虚拟机的大小
- gradlew && gradlew.bat 代替gradle 命令实现自动完成gradle环境搭建。配合gradle文件夹的内容,会降到IDEA如何搭配gradlew使用。
- local.properties 配置android NDK,SDK的地方,恩,非android项目可能没有这个文件,这个路径根据不同想电脑不同,一般也不会纳入源代码管理之中,一般可以写一个local.properties.simple 文件,告知需要修改该文件名并写上本地SDK NDK 路径。simple文件纳入源码管理之中。
- setting.gradle 子项目包含文件,声明当前目录下含有什么module,当然你的app底下加上这样的文件,也能继续在app底下加module。和我第点说的,整个project就是一个大的module,每个module下面还能包含相应的module。如果你理解这个了,其实app目录单独作为一个项目管理也是可以的,,把相应的配置文件配上而已,相当于主目录应用 android 的gradle plugin (下一点会说到这个)
-
gitignore 该文件是源码管理的配置文件,不在该文讲解。
既然gradle 是多 module形式,那么我们来看看 setting.gradle 配置的内容
rootProject.name = 'gradle'
从上面目录的配置文件内容来看,整个project也算是一个module,如果改module底下还有module,就可以通过setting.gradle配置进来,使得该module底下的gradle,从app module底下可以看出,module最少要含有 build.gradle文件,这个module的项目编译文件,该module依赖什么插件对该目录进行编译都在此配置,比如android与android-library,其他内容可继承父类的
同样maven项目需要依赖,Gradle也需要jar包
但是Gradle的jar包去哪里找呢 根据Build.Gradle 已经指出来了
repositories {
mavenCentral() maven中心仓库
}
网址是https://search.maven.org/ 使用搜狗浏览器我的打不开,所以我使用了谷歌
里面有一个jar包是将netty所有的jar包都包含了
我都拷贝出来吧
maven版本下的 还不让我copy
<dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <veresion>5.0.0.Alpha2<version> </dependency>
'io.netty:netty-all:4.1.34.Final'
E:sturts2gradle03-projext>gradle clean build BUILD SUCCESSFUL in 12s 2 actionable tasks: 2 executed E:sturts2gradle03-projext>
下面先上一个代码,输出helloworld 我再学习的时候,也是一脸懵或许等一下就好了吧
按照我的习惯,先写配置
plugins { id 'java' id 'war' } group 'gradle03-projext' version '1.0' sourceCompatibility = 1.8 targetCompatibility = 1.8 repositories { mavenCentral() } dependencies { /*testCompile group: 'junit', name: 'junit', version: '4.11'*/ testCompile group: 'junit', name: 'junit', version: '4.12' compile( 'io.netty:netty-all:4.1.34.Final' ) }
TestServer
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; /** * Description: gradle03 * Created by lenovo on 2019/4/17 19:39 */ public class TestServer { public static void main (String[] args) throws InterruptedException { /*netty中大量的就是对线程的处理对线程的控制,对io的一些异步的操作*/ /*先定义两个线程组NIo 就是异步的*/ /*事件循环组:*/ EventLoopGroup bossLoopGroup = new NioEventLoopGroup(); EventLoopGroup workLoopGroup = new NioEventLoopGroup(); ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(bossLoopGroup,workLoopGroup).channel(NioServerSocketChannel.class).childHandler(new TestServerInitializer()); ChannelFuture channelFuture = serverBootstrap.bind(8899).sync(); channelFuture.channel().closeFuture().sync(); bossLoopGroup.shutdownGracefully(); workLoopGroup.shutdownGracefully(); } }
TestHttpServerHandler
import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.*; import io.netty.util.CharsetUtil; import javax.servlet.http.HttpServlet; import java.nio.charset.Charset; /** * Description: gradle03 * Created by lenovo on 2019/4/17 20:50 */ public class TestHttpServerHandler extends SimpleChannelInboundHandler<HttpObject> { @Override protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception { if (msg instanceof HttpRequest){ ByteBuf content = Unpooled.copiedBuffer("hello world", CharsetUtil.UTF_8); DefaultFullHttpResponse 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); } } }
TestServerInitializer
import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.http.HttpServerCodec; /** * Description: gradle03 * Created by lenovo on 2019/4/17 20:46 */ public class TestServerInitializer extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast("httpServerCode",new HttpServerCodec()); pipeline.addLast("testHttpServletHandler",new TestHttpServerHandler()); } }
浏览器中是可以访问到的
http://localhost:8899/
现在分析时间执行的流程,和方法回调
执行结果就是这个
channelRegister
channelRegister
channelActive
channelActive
channelReadComplete
等你停下来3秒,管道就自动取消了注册
channelRegister
channelRegister
channelActive
channelActive
channelReadComplete
channelReadComplete
channelInactive
channelUnregister
使用netty能做什么,也是对请求进行响应
Netty并没与实现servlet规范,netty编写代码要实现的步骤
1.编写服务器
2.在服务器中编写我们自定义的Servletinitializer对象
3.在Servletinitializer中去使用很多的handler
现在再放上一个例子 客户端通信
服务端
package com.netty.chatting; import com.netty.socket.MyServletInitializer; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; /** * Description: gradle03 * Created by lenovo on 2019/4/18 10:14 */ public class MyChattingServer { public static void main (String[] args) throws InterruptedException { NioEventLoopGroup bossGroup = new NioEventLoopGroup(); NioEventLoopGroup workerGroup = new NioEventLoopGroup(); ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class). childHandler(new MychatServerInitalizer()); serverBootstrap.bind(8899).sync(); bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } }
package com.netty.chatting; import com.netty.socket.MyHttpServerHandler; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.DelimiterBasedFrameDecoder; import io.netty.handler.codec.Delimiters; import io.netty.handler.codec.LengthFieldBasedFrameDecoder; import io.netty.handler.codec.LengthFieldPrepender; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; import io.netty.util.CharsetUtil; import java.net.Socket; /** * Description: gradle03 * Created by lenovo on 2019/4/18 10:21 */ /* public class MychatServerInitalizer extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new DelimiterBasedFrameDecoder(4096, Delimiters.lineDelimiter())); pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8)); pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8)); pipeline.addLast(new MyChatServerHandler()); } } */ public class MychatServerInitalizer extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new DelimiterBasedFrameDecoder(4096, Delimiters.lineDelimiter())); pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8)); pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8)); pipeline.addLast("testHttpServletHandler",new MyChatServerHandler()); } }
package com.netty.chatting; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.group.ChannelGroup; import io.netty.channel.group.DefaultChannelGroup; import io.netty.util.concurrent.GlobalEventExecutor; import jdk.nashorn.internal.objects.Global; /** * Description: gradle03 * Created by lenovo on 2019/4/18 10:43 */ public class MyChatServerHandler extends SimpleChannelInboundHandler<String> { public static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); @Override protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { Channel channel = ctx.channel(); channelGroup.forEach(ch -> { if (channel!=ch){ ch.writeAndFlush(channel.remoteAddress()+"发送的消息"+msg+" "); }else{ ch.writeAndFlush("自己"+msg+" "); } }); } @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { super.handlerAdded(ctx); Channel channel = ctx.channel(); channelGroup.writeAndFlush("服务器-"+channel.remoteAddress()+"加入 "); channelGroup.add(channel); } @Override public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { super.handlerRemoved(ctx); Channel channel = ctx.channel(); channelGroup.writeAndFlush("服务器-"+channel.remoteAddress()+"离开 "); System.out.println(channelGroup.size()); } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { super.channelActive(ctx); Channel channel = ctx.channel(); System.out.println(channel.remoteAddress()+"上线 "); } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { super.channelInactive(ctx); Channel channel = ctx.channel(); System.out.println(channel.remoteAddress()+"下线 "); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { super.exceptionCaught(ctx, cause); ctx.close(); } }
客户端
package com.netty.chatting; import com.netty.socket.MySClientInitializer; import com.sun.org.apache.bcel.internal.generic.NEW; import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; /** * Description: gradle03 * Created by lenovo on 2019/4/18 11:16 */ public class MyChatClientServer { public static void main (String[] args){ EventLoopGroup eventExecutors = new NioEventLoopGroup(); try { Bootstrap bootstrap = new Bootstrap(); bootstrap.group(eventExecutors).channel(NioSocketChannel.class).handler(new MyChatClientInitializer()); Channel channel = bootstrap.connect("localhost",8899).sync().channel(); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in)); for (;;){ channel.writeAndFlush(bufferedReader.readLine()+" "); } } catch (InterruptedException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { eventExecutors.shutdownGracefully(); } } }
package com.netty.chatting; import com.netty.socket.MyClientHandler; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.DelimiterBasedFrameDecoder; import io.netty.handler.codec.Delimiters; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; import io.netty.util.CharsetUtil; public class MyChatClientInitializer extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new DelimiterBasedFrameDecoder(4096, Delimiters.lineDelimiter())); pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8)); pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8)); pipeline.addLast("testHttpServletHandler",new MyChatClientServerHandler()); } }
package com.netty.chatting; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; /** * Description: gradle03 * Created by lenovo on 2019/4/18 11:17 */ public class MyChatClientServerHandler extends SimpleChannelInboundHandler<String> { @Override protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { System.out.println(msg); } }
放上执行结果
先后打开了三个客户端
下面是长连接的例子
package com.netty.longConnection; 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; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; /** * Description: gradle03 * Created by lenovo on 2019/4/18 15:37 */ public class MyServer { public static void main (String[] args) throws InterruptedException { NioEventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workGroup = new NioEventLoopGroup(); ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(bossGroup,workGroup).channel(NioServerSocketChannel.class). handler(new LoggingHandler(LogLevel.INFO)). childHandler(new MyServletInitializer()); ChannelFuture channelFuture = serverBootstrap.bind(8899).sync(); channelFuture.channel().close().sync(); /* bossGroup.shutdownGracefully(); workGroup.shutdownGracefully();*/ } }
package com.netty.longConnection; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; import io.netty.handler.timeout.IdleStateHandler; import java.util.concurrent.TimeUnit; /** * Description: gradle03 * Created by lenovo on 2019/4/18 15:41 */ public class MyServletInitializer extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new IdleStateHandler(5,7,10, TimeUnit.SECONDS)); pipeline.addLast(new MyServerHandler()); } }
package com.netty.longConnection; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelInboundHandlerAdapter; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.timeout.IdleStateEvent; /** * Description: gradle03 * Created by lenovo on 2019/4/18 15:48 */ public class MyServerHandler extends ChannelInboundHandlerAdapter { @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { super.userEventTriggered(ctx, evt); if (evt instanceof IdleStateEvent){ IdleStateEvent event = (IdleStateEvent) evt; String eventType = null; switch (event.state()){ case READER_IDLE: eventType="读空闲"; case WRITER_IDLE: eventType="写空闲"; case ALL_IDLE: eventType="读写空闲"; break; } System.out.println(ctx.channel().remoteAddress()+"超时事件"+eventType); ctx.channel().close(); } } }
netty实现服务端与客户端的长联通通信
<%-- Created by IntelliJ IDEA. User: lenovo Date: 2019/4/17 Time: 18:19 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>webSocket客户端</title> <script> var socket; if(window.WebSocket){ socket= new WebSocket("ws://localhost:8899/ws"); socket.onmessage = function (event) { var ta = document.getElementById("responseText"); ta.value = ta.value+" "+event.data; } socket.onopen = function (event) { var ta = document.getElementById("responseText"); ta.value="连接开启"; } socket.onclose= function (event) { var ta = document.getElementById("responseText"); ta.value = ta.value+" "+"连接关闭"; } }else { alert("浏览器不支持websocket"); } function send(message) { if(!window.WebSocket){ return; }if(socket.readyState == WebSocket.OPEN){ socket.send(message); }else { alert("连接尚未开启") } } </script> </head> <body> <form onsubmit="false"> <textarea name="message" style=" 400px; height: 200px"></textarea> <input type="button" value="发送数据" onclick="send(this.form.message.value)"> <h3>服务端输出:</h3> <textarea id="responseText" style=" 400px; height: 300px;"></textarea> <input type="button" onclick="javascript:document.getElementById('responseText').value=''" value="清空内容"> </form> </body> </html>
package com.netty.ServerSocke; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.EventLoop; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; /** * Description: gradle03 * Created by lenovo on 2019/4/18 16:38 */ public class MyServer { public static void main (String[] args) throws InterruptedException { NioEventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workGroup = new NioEventLoopGroup(); ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(bossGroup,workGroup).channel(NioServerSocketChannel.class).handler(new LoggingHandler(LogLevel.INFO)).childHandler(new MyServerInitializer()); ChannelFuture channelFuture = serverBootstrap.bind("localhost", 8899).sync(); /* channelFuture.channel().close().sync(); bossGroup.shutdownGracefully(); workGroup.shutdownGracefully();*/ } }
package com.netty.ServerSocke; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.HttpServerCodec; import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; import io.netty.handler.stream.ChunkedWriteHandler; /** * Description: gradle03 * Created by lenovo on 2019/4/18 16:43 */ public class MyServerInitializer extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast( new HttpServerCodec()); pipeline.addLast(new ChunkedWriteHandler()); pipeline.addLast(new HttpObjectAggregator(8192)); pipeline.addLast(new WebSocketServerProtocolHandler("/ws")); pipeline.addLast(new MyServerhandler()); } }
package com.netty.ServerSocke; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; import java.time.LocalDateTime; /** * Description: gradle03 * Created by lenovo on 2019/4/18 17:13 */ public class MyServerhandler extends SimpleChannelInboundHandler<TextWebSocketFrame> { @Override protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception { System.out.println("收到消息"+msg.text()); ctx.channel().writeAndFlush(new TextWebSocketFrame("服务事件"+ LocalDateTime.now())); } @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { super.handlerAdded(ctx); System.out.println("handlerAdded"+ctx.channel().id()); } @Override public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { super.handlerRemoved(ctx); System.out.println("handlerremove"+ctx.channel().id().asLongText()); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { super.exceptionCaught(ctx, cause); System.out.println("异常发生exceptionCaught"); ctx.close(); } }
下载protobuf
Protocol Buffer 简称 ProtoBuf 是用于结构化数据串行化的灵活、高效、自动的方法,又如 XML,不过它更小、更快、也更简单。你可以定义自己的数据结构,然后使用代码生成器生成的代码来读写这个数据结构。你甚至可以在无需重新部署程序的情况下更新数据结构
网址(https://github.com/protocolbuffers/protobuf/releases/tag/v3.7.1)
二选一
下载成功了之后别忘了在你的环境变量中进行配置。
选择你对应的版本
在google中去寻找你的jar包
https://search.maven.org/artifact/com.google.protobuf/protobuf-java/3.7.1/bundle
不过好像不能复制,我只能手写了
'com.google.protobuf:protobuf-java:3.7.1'
com.google.protobuf:protobuf-java-util:3.7.1
在idea中使用protobef 也是花费了我一番力气,首先毋庸置疑先安装插件
此时我创建的Student.proto文件的图标应该发生变化的但是没有发生变化
尝试另外一种解决方案
这下就发生了变化
详细讲解配置文件的连接(https://www.meiwen.com.cn/subject/chzzcxtx.html)
syntax = "proto2";
package cn.edu.aynu.protobuf; //可以自定义
option optimize_for = SPEED; //默认也是speed加速解析
option java_package = "cn.edu.aynu.protobuf";
option java_outer_classname = "DataInfo"; //这个名字也可以自己定义
message Student{
required string name =1; //每一个字段都有唯一标识符这些标识符是用来在消息的二进制格式中识别各个字段的,一旦开始使用就不能够再改 变
optional int32 age =2;
optional string address = 3;
}
所指定的消息字段修饰符必须是如下之一:
² required:一个格式良好的消息一定要含有1个这种字段。表示该值是必须要设置的;
² optional:消息格式中该字段可以有0个或1个值(不超过1个)。
² repeated:在一个格式良好的消息中,这种字段可以重复任意多次(包括0次)。重复的值的顺序会被保留。表示该值可以重复,相当于Java中的List。
由于一些历史原因,基本数值类型的repeated的字段并没有被尽可能地高效编码。在新的代码中,用户应该使用特殊选项[packed=true]来保证更高效的编码。如:
repeated int32 samples = 4 [packed=true]; |
required是永久性的:在将一个字段标识为required的时候,应该特别小心。如果在某些情况下不想写入或者发送一个required的 字段,将原始该字段修饰符更改为optional可能会遇到问题——旧版本的使用者会认为不含该字段的消息是不完整的,从而可能会无目的的拒绝解析。在这 种情况下,你应该考虑编写特别针对于应用程序的、自定义的消息校验函数。Google的一些工程师得出了一个结论:使用required弊多于利;他们更 愿意使用optional和repeated而不是required。当然,这个观点并不具有普遍性。
在终端上输入命令
表示在protoc --java_out= src/mian/java 这个目录下生成代码,源文件在
src/protobuf/Student.proto 目录下。
这里出现了一个小问题我再斜着跳语句的时候
E:sturts2gradle05-project>protoc --java_out = src/main/java src/protobuf/Student.proto src/main/java: Permission denied
我查了很多,但是网上几乎没有人遇到过这个问题,根本搜不到后来我又敲了一遍
E:sturts2gradle05-project>protoc --java_out=src/main/java src/protobuf/Student.proto
发现问题了吧,对就是空格,= 好前后的空格。把前后的空格去掉就执行成功了。
需要指出的是,你绝对不能修改生成的DataInfo 这个文件,因为现在我们还不具备修改这个文件的个水平。
新建一个java类
package cn.edu.aynu.protobuf; import com.google.protobuf.InvalidProtocolBufferException; /** * Description: gradle05 * Created by lenovo on 2019/4/19 17:26 */ public class ProobufTest { public static void main (String[] args) throws InvalidProtocolBufferException { DataInfo.Student student = DataInfo.Student.newBuilder().setName("张三"). setAge(20).setAddress("北京").build(); //先构建一个java对象 byte[] bytes = student.toByteArray(); //将java对象转换为一个字节数组 DataInfo.Student student1 = DataInfo.Student.parseFrom(bytes);//从字节数组中将java对象的信息给恢复过来 System.out.println(student1.getAddress()); System.out.println(student1.getName()); System.out.println(student1.getAge()); } }
执行结果
我再执行下一个程序的时候遇到了一个错误
com.google.protobuf.InvalidProtocolBufferException: While parsing a protocol message, the input ende
或者是无法远程连接
我的原因是我写错了一个关闭
应该写成 channelFuture.channel().closeFuture().sync();
结果写成了 channelFuture.channel().close().sync();
所以就导致了上面的两个问题,如果你将下面连个注释掉虽然服务不会停但是当你启动客户端的时候就会发生 连接被拒绝,没有远程服务
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
下面粘上我正确的代码
package cn.edu.aynu.netty; 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; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; /** * Description: gradle05 * Created by lenovo on 2019/4/19 17:50 */ public class MyServer { public static void main (String[] args) throws InterruptedException { /*同理首先要构建事件*/ EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workGroup = new NioEventLoopGroup(); ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(bossGroup,workGroup).channel(NioServerSocketChannel.class). handler(new LoggingHandler(LogLevel.INFO)). childHandler(new ServerInitializer()); // 绑定端口后 ChannelFuture channelFuture = serverBootstrap.bind(8899).sync(); // 关闭端口 channelFuture.channel().closeFuture().sync(); /* channelFuture.channel().close().sync();*/ //关闭两个线程 bossGroup.shutdownGracefully(); workGroup.shutdownGracefully(); } }
package cn.edu.aynu.netty; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.protobuf.ProtobufDecoder; import io.netty.handler.codec.protobuf.ProtobufEncoder; import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder; import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender; /** * Description: gradle05 * Created by lenovo on 2019/4/19 17:55 */ public class ServerInitializer extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new ProtobufVarint32FrameDecoder()); pipeline.addLast(new ProtobufDecoder(DataInfo.Student.getDefaultInstance()));//解码器把字节数组转换为真正的对象 pipeline.addLast(new ProtobufVarint32LengthFieldPrepender()); pipeline.addLast(new ProtobufEncoder()); pipeline.addLast(new MyServerHandler()); } }
package cn.edu.aynu.netty; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; /** * Description: gradle05 * Created by lenovo on 2019/4/19 17:59 */ public class MyServerHandler extends SimpleChannelInboundHandler<DataInfo.Student> { @Override protected void channelRead0(ChannelHandlerContext ctx, DataInfo.Student msg) throws Exception { System.out.println(msg.getAddress()); System.out.println(msg.getAge()); System.out.println(msg.getName()); } }
客户端
package cn.edu.aynu.netty; import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.ServerSocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; /** * Description: gradle05 * Created by lenovo on 2019/4/19 18:07 */ public class MyClient { public static void main (String[] args) throws IOException, InterruptedException { NioEventLoopGroup eventExecutors = new NioEventLoopGroup(); try { Bootstrap bootstrap = new Bootstrap(); bootstrap.group(eventExecutors).channel(NioSocketChannel.class).handler(new MyClientInitializer()); ChannelFuture channelFuture = bootstrap.connect("localhost", 8899).sync(); channelFuture.channel().closeFuture().sync(); } catch (InterruptedException e) { e.printStackTrace(); } finally { eventExecutors.shutdownGracefully(); } } }
package cn.edu.aynu.netty; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.protobuf.ProtobufDecoder; import io.netty.handler.codec.protobuf.ProtobufEncoder; import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder; import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender; /** * Description: gradle05 * Created by lenovo on 2019/4/19 18:15 */ public class MyClientInitializer extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new ProtobufVarint32FrameDecoder()); pipeline.addLast(new ProtobufDecoder(DataInfo.Student.getDefaultInstance()));//解码器把字节数组转换为真正的对象 pipeline.addLast(new ProtobufVarint32LengthFieldPrepender()); pipeline.addLast(new ProtobufEncoder()); pipeline.addLast(new MyClientHandler()); } }
package cn.edu.aynu.netty; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; /** * Description: gradle05 * Created by lenovo on 2019/4/19 18:15 */ public class MyClientHandler extends SimpleChannelInboundHandler<DataInfo.Student> { @Override protected void channelRead0(ChannelHandlerContext ctx, DataInfo.Student msg) throws Exception { } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { super.channelActive(ctx); DataInfo.Student student1 = DataInfo.Student.newBuilder().setName("张三").setAddress("背景").setAge(23).build(); ctx.channel().writeAndFlush(student1); } }
syntax = "proto2"; package cn.edu.aynu.protobuf; //可以自定义 option optimize_for = SPEED; //默认也是speed加速解析 option java_package = "cn.edu.aynu.netty"; option java_outer_classname = "DataInfo"; message Student{ required string name =1; //每一个字段都有唯一标识符 optional int32 age =2; optional string address = 3; }
多协议消息支援
syntax = "proto2"; package cn.edu.aynu.protobuf; //可以自定义 option optimize_for = SPEED; //默认也是speed加速解析 option java_package = "cn.edu.aynu.netty"; option java_outer_classname = "DataInfo"; message MyMessage{ enum DataType{ StudentType =1; DogType =2; CatType =3; } required DataType data_type = 1; oneof dataBody{ Student student =2; Dog dog =3; Cat cat =4; } } message Dog{ optional string name =1; optional int32 age =2; } message Cat{ optional string name =1; optional int32 age =2; } message Student{ required string name =1; //每一个字段都有唯一标识符 optional int32 age =2; optional string address = 3; }
package cn.edu.aynu.netty; 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; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; /** * Description: gradle05 * Created by lenovo on 2019/4/19 17:50 */ public class MyServer { public static void main (String[] args) throws InterruptedException { /*同理首先要构建事件*/ EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workGroup = new NioEventLoopGroup(); ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(bossGroup,workGroup).channel(NioServerSocketChannel.class). handler(new LoggingHandler(LogLevel.INFO)). childHandler(new ServerInitializer()); // 绑定端口后 ChannelFuture channelFuture = serverBootstrap.bind(8899).sync(); // 关闭端口 channelFuture.channel().closeFuture().sync(); /* channelFuture.channel().close().sync();*/ //关闭两个线程 bossGroup.shutdownGracefully(); workGroup.shutdownGracefully(); } }
package cn.edu.aynu.netty; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.protobuf.ProtobufDecoder; import io.netty.handler.codec.protobuf.ProtobufEncoder; import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder; import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender; /** * Description: gradle05 * Created by lenovo on 2019/4/19 17:55 */ public class ServerInitializer extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new ProtobufVarint32FrameDecoder()); pipeline.addLast(new ProtobufDecoder(DataInfo.MyMessage.getDefaultInstance()));//解码器把字节数组转换为真正的对象 pipeline.addLast(new ProtobufVarint32LengthFieldPrepender()); pipeline.addLast(new ProtobufEncoder()); pipeline.addLast(new MyServerHandler()); } }
package cn.edu.aynu.netty; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; /** * Description: gradle05 * Created by lenovo on 2019/4/19 17:59 */ public class MyServerHandler extends SimpleChannelInboundHandler<DataInfo.MyMessage> { @Override protected void channelRead0(ChannelHandlerContext ctx, DataInfo.MyMessage msg) throws Exception { DataInfo.MyMessage.DataType dataType = msg.getDataType(); if (dataType == DataInfo.MyMessage.DataType.StudentType){ DataInfo.Student student = msg.getStudent(); System.out.println(student.getAddress()); System.out.println(student.getAge()); System.out.println(student.getName()); }else if(dataType == DataInfo.MyMessage.DataType.DogType){ DataInfo.Dog dog = msg.getDog(); System.out.println(dog.getName()); System.out.println(dog.getAge()); }else{ DataInfo.Cat cat = msg.getCat(); System.out.println(cat.getName()); System.out.println(cat.getAge()); } } }
client
package cn.edu.aynu.netty; import io.netty.bootstrap.Bootstrap; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.ServerSocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.channel.socket.nio.NioSocketChannel; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; /** * Description: gradle05 * Created by lenovo on 2019/4/19 18:07 */ public class MyClient { public static void main (String[] args) throws IOException, InterruptedException { NioEventLoopGroup eventExecutors = new NioEventLoopGroup(); try { Bootstrap bootstrap = new Bootstrap(); bootstrap.group(eventExecutors).channel(NioSocketChannel.class).handler(new MyClientInitializer()); ChannelFuture channelFuture = bootstrap.connect("localhost", 8899).sync(); channelFuture.channel().closeFuture().sync(); } catch (InterruptedException e) { e.printStackTrace(); } finally { eventExecutors.shutdownGracefully(); } } }
package cn.edu.aynu.netty; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.protobuf.ProtobufDecoder; import io.netty.handler.codec.protobuf.ProtobufEncoder; import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder; import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender; /** * Description: gradle05 * Created by lenovo on 2019/4/19 18:15 */ public class MyClientInitializer extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new ProtobufVarint32FrameDecoder()); pipeline.addLast(new ProtobufDecoder(DataInfo.MyMessage.getDefaultInstance()));//解码器把字节数组转换为真正的对象 pipeline.addLast(new ProtobufVarint32LengthFieldPrepender()); pipeline.addLast(new ProtobufEncoder()); pipeline.addLast(new MyClientHandler()); } }
package cn.edu.aynu.netty; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import java.util.Random; /** * Description: gradle05 * Created by lenovo on 2019/4/19 18:15 */ public class MyClientHandler extends SimpleChannelInboundHandler<DataInfo.MyMessage> { @Override protected void channelRead0(ChannelHandlerContext ctx, DataInfo.MyMessage msg) throws Exception { } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { super.channelActive(ctx); DataInfo.MyMessage message = null; int randomInt = new Random().nextInt(3); if (0==randomInt){ message= DataInfo.MyMessage.newBuilder(). setDataType(DataInfo.MyMessage.DataType.StudentType). setStudent(DataInfo.Student.newBuilder(). setName("张三").setAddress("背景").setAge(23).build()).build(); }else if(1==randomInt){ message= DataInfo.MyMessage.newBuilder(). setDataType(DataInfo.MyMessage.DataType.DogType). setDog(DataInfo.Dog.newBuilder(). setName("小汪").setAge(2).build()).build(); }else { message= DataInfo.MyMessage.newBuilder(). setDataType(DataInfo.MyMessage.DataType.CatType). setCat(DataInfo.Cat.newBuilder(). setName("小猫").setAge(3).build()).build(); } DataInfo.Student student1 = DataInfo.Student.newBuilder().setName("张三").setAddress("背景").setAge(23).build(); ctx.channel().writeAndFlush(message); } }
接下来是apache Thrift (详细讲解可以参考 https://www.cnblogs.com/fingerboy/p/6424248.html)
简单来说,是Facebook公布的一款开源跨语言的RPC框架.
Thrift最初由Facebook开发的,后来提交给了Apache基金会将Thrift作为一个开源项目。当时facebook开发使用它是为了解决系统中各系统间大数据量的传输通信以及系统之间语言环境不同需要跨平台的特性,所以Thrift是支持跨语言,比如C++, Java, Python, PHP, Ruby, Erlang, Perl, Haskell, C#, Cocoa, JavaScript, Node.js, Smalltalk, and OCaml都支持。Thrift是一个典型的CS结构,客户端和服务端可以使用不同的语言开发。既然客户端和服务端能使用不同的语言开发,那么一定就要有一种中间语言来关联客户端和服务端的语言,没错,这种语言就是IDL(Interface Description Language)。
RPC全称为Remote Procedure Call,意为远程过程调用.
假设有两台服务器A,B.A服务器上部署着一个应用a,B服务器上部署着一个应用b,现在a希望能够调用b应用的某个函数(方法),但是二者不在同一个进程内,不能直接调用,就需要通过网络传输,在AB服务器之间建一条网络传输通道,a把参数传过去,b接收到参数调用自己的方法,得到结果,再通过网络传回给a,简单讲就是A通过网络来调用B的过程.这个过程要涉及的东西很多,比如多线程,Socket,序列化反序列化,网络I/O,很复杂,于是牛掰的程序员把这些封装起来做成一套框架,供大家使用,就是RPC框架.
下载地址(http://thrift.apache.org/download),然后将下载
不会安装的可以参考这个(https://blog.csdn.net/haihaa/article/details/76577797)
在idea 下使用Gradle写一个Thrift的例子
注意图标的变化
namespace java thrift.generated typedef i16 short typedef i32 int typedef i64 long typedef bool boolean typedef string String struct Person{ 1:optional String username, 2:optional int age, 3:optional boolean married } exception DataException{ 1:optional String message, 2:optional String callStack, 3:optional String data } service PersonService{ Person getPersonByUsername(1:required String username) throws (1:DataException dataException), void savePerson(1:required Person person) throws(1:DataException dataException) }
然后再终端中运行这句话
E:sturts2gradle05-project>thrift --gen java src/Thrift/data.thrift
如果你在环境变量中已经配置好了,cmd命令行中也能访问到,但是在idea 中的终端上不能访问,那么你可以尝试将程序都关掉,或者将电脑重启
这时再打开就可以了。
当在idea终端总运行后会出现一个
因为生成的这个目录不在java 目录下,所以将其拷贝到java 目录下
需要导入依赖
"org.apache.thrift.:thrift-maven-plugin:0.10.0"
thrift的一个架构
package cn.edu.aynu.thrift; import com.sun.org.apache.bcel.internal.generic.NEW; import org.apache.thrift.TProcessorFactory; import org.apache.thrift.protocol.TCompactProtocol; import org.apache.thrift.protocol.TProtocolFactory; import org.apache.thrift.server.THsHaServer; import org.apache.thrift.server.TServer; import org.apache.thrift.transport.TFramedTransport; import org.apache.thrift.transport.THttpClient; import org.apache.thrift.transport.TNonblockingServerSocket; import org.apache.thrift.transport.TTransportException; import thrift.generated.PersonService; /** * 这是服务端 */ public class ThriftServer { public static void main (String[] args) throws TTransportException { TNonblockingServerSocket serverSocket = new TNonblockingServerSocket(8899); THsHaServer.Args workerThreads = new THsHaServer.Args(serverSocket).minWorkerThreads(2).maxWorkerThreads(4); PersonService.Processor<PersonServiceImpl> processor = new PersonService.Processor<>(new PersonServiceImpl()); workerThreads.protocolFactory(new TCompactProtocol.Factory()); workerThreads.transportFactory(new TFramedTransport.Factory()); workerThreads.processorFactory(new TProcessorFactory(processor)); TServer server = new THsHaServer(workerThreads); System.out.println("Thrift Server Started"); //这是一个死循环 server.serve(); } }
package cn.edu.aynu.thrift; import org.apache.thrift.TException; import org.apache.thrift.protocol.TCompactProtocol; import org.apache.thrift.transport.TFramedTransport; import org.apache.thrift.transport.TSocket; import org.apache.thrift.transport.TTransport; import org.apache.thrift.transport.TTransportException; import thrift.generated.Person; import thrift.generated.PersonService; /** * Description: gradle05 * Created by lenovo on 2019/4/20 11:20 */ public class ThriftClient { public static void main (String[] args){ TTransport transport = new TFramedTransport(new TSocket("localhost",8899,600)); TCompactProtocol protocol = new TCompactProtocol(transport); PersonService.Client client = new PersonService.Client(protocol); try { transport.open(); Person person = client.getPersonByUsername("张三"); System.out.println(person.getAge()); System.out.println(person.getUsername()); System.out.println(person.isMarried()); System.out.println("-----------------------"); Person person1 = new Person(); person1.setUsername("李艾"); person1.setAge(23); person1.setMarried(true); client.savePerson(person1); } catch (TException e) { e.printStackTrace(); }finally { transport.close(); } } }
package cn.edu.aynu.thrift; import org.apache.thrift.TException; import thrift.generated.DataException; import thrift.generated.Person; import thrift.generated.PersonService; /** * Description: gradle05 * Created by lenovo on 2019/4/20 10:58 */ public class PersonServiceImpl implements PersonService.Iface { @Override public Person getPersonByUsername(String username) throws DataException, TException { System.out.println("Got client Param"+username); Person person = new Person(); person.setUsername(username); person.setAge(20); person.setMarried(false); return person; } @Override public void savePerson(Person person) throws DataException, TException { System.out.println("Got Client Param"); System.out.println(person.getUsername()); System.out.println(person.getAge()); System.out.println(person.isMarried()); } }
运行结果
当你执行 gradle wrapper
会出现3个文件
还可以在built.gradle中指定版本
这样就不需要在终端中输入命令。
Grpc 的一些了解知识
官网(https://grpc.io/docs/tutorials/basic/java.html)
https://grpc.io/docs/quickstart/java.html
Grpc与Gradle整合,我是失败了,你们可以试试,我现在先往下看,等过段时间不忙了,我再重新看一下官网把这个问题给找出来。
https://github.com/grpc/grpc-java
我这个目前是一直都不对,我先记录一下等回头再学习
下面这个例子是grpc 通信实例与JVM 回调
我上面最基本的环境都搭建不起来多以这个例子我也跑不起来,先过,看下面的例子,等我有空了再把这个部分内容补充回来
。。。。。。。。。。。。。。。。。。。。。。。
所有的有关grpc 的内容都暂且略过。
。。。。。。。。。。
1。grpc 服务器流式调用实现
2.grpc 双向流式数据通信详解
3.grpc 与Gradle 流式整合
下面就主要讲解,Gradle插件问题解决方案,与Nodejs 环境搭建
目前我是真是坚持不下去了,真的是很难啊,环境打不好,我还是先看基础的吧,能看完的都是人才啊。