哪个网站可以做高像素动图/策划网络营销活动
dubbo在服务消费时调用的方法栈比较深,所以得一边看一边记,还是比较费力的。在dubbo服务发现中,我们看到通过ReferenceConfig#get()返回的是要调用接口的代理对象,因此通过接口的代理对象调用方法时是调用InvocationHandler(InvokerInvocationHandler)#invoke()方法,此时会调用注入的Invoker#invoke()方法,而InvokerInvocationHandler对象的invoker默认是DubboInvoker实现的,因此DubboInvoker#invoke()方法被调用,最终调用子类DubboInvoker#doInvoke()方法。
// DubboInvoker=>AbstractInvoker.javapublic Result invoke(Invocation inv) throws RpcException {//设置一系列参数......try {//调用子类方法return doInvoke(invocation);} catch (InvocationTargetException e) { // biz exception......}}
这里是调用远程服务有三种,异步无返回值、异步有返回值、同步阻塞获取返回,以同步方式往下分析。
// DubboInvokerprotected Result doInvoke(final Invocation invocation) throws Throwable {RpcInvocation inv = (RpcInvocation) invocation;final String methodName = RpcUtils.getMethodName(invocation);//接口路径inv.setAttachment(Constants.PATH_KEY, getUrl().getPath());inv.setAttachment(Constants.VERSION_KEY, version);//获取已经建立连接的客户端ExchangeClient currentClient;if (clients.length == 1) {currentClient = clients[0];} else {currentClient = clients[index.getAndIncrement() % clients.length];}try {//先从invocation中获取,再从URL中获取boolean isAsync = RpcUtils.isAsync(getUrl(), invocation);boolean isOneway = RpcUtils.isOneway(getUrl(), invocation);int timeout = getUrl().getMethodParameter(methodName, Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);//单向调用,直接调用不获取返回值if (isOneway) {boolean isSent = getUrl().getMethodParameter(methodName, Constants.SENT_KEY, false);currentClient.send(inv, isSent);RpcContext.getContext().setFuture(null);return new RpcResult();} else if (isAsync) {//异步调用有返回值需要手动获取ResponseFuture future = currentClient.request(inv, timeout);RpcContext.getContext().setFuture(new FutureAdapter<Object>(future));return new RpcResult();} else {//同步调用,get()会阻塞地获取返回值RpcContext.getContext().setFuture(null);return (Result) currentClient.request(inv, timeout).get();}} catch (TimeoutException e) {throw new RpcException(RpcException.TIMEOUT_EXCEPTION, "Invoke remote method timeout. method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);} catch (RemotingException e) {throw new RpcException(RpcException.NETWORK_EXCEPTION, "Failed to invoke remote method: " + invocation.getMethodName() + ", provider: " + getUrl() + ", cause: " + e.getMessage(), e);}}
DubboProtocol#getSharedClient()生成netty客户端时,会使用ReferenceCountExchangeClient装饰客户端ExchangeClient,然后被保存到数组中,因此这里获取的currentClient为ReferenceCountExchangeClient,调用注入的ExchangeClient(HeaderExchangeClient)对象client的request()方法。
// ReferenceCountExchangeClientpublic ResponseFuture request(Object request, int timeout) throws RemotingException {return client.request(request, timeout);}
这里会继续调用内部的ExchangeChannel对象channel的request()方法,那这里的channel怎么来得呢。构造HeaderExchangeClient对象时new出来的,同时构造方法中的client为NettyClient实例对象。
// HeaderExchangeClient.javapublic ResponseFuture request(Object request, int timeout) throws RemotingException {return channel.request(request, timeout);}public HeaderExchangeClient(Client client, boolean needHeartbeat) {....this.client = client;this.channel = new HeaderExchangeChannel(client);....}
那继续往下看HeaderExchangeChannel#request()方法,好像离发送请求数据越来越近了,这里会先构造Request和DefaultFuture,再调用内部注入的channel(NettyClient)的send()方法。
// HeaderExchangeChannelpublic ResponseFuture request(Object request, int timeout) throws RemotingException {if (closed) {throw new RemotingException(this.getLocalAddress(), null, "Failed to send request " + request + ", cause: The channel " + this + " is closed!");}// create request.Request req = new Request();req.setVersion("2.0.0");req.setTwoWay(true);req.setData(request);DefaultFuture future = new DefaultFuture(channel, req, timeout);try {channel.send(req);} catch (RemotingException e) {future.cancel();throw e;}return future;}
NettyClient的继承关系是NettyClient=>AbstractClient=>AbstractPeer,此时内部AbstractClient#send()方法被调用。这里就是先getChannel()获取通道再调用send(),再分析这个channel怎么来的,在doConnect()时如果连接服务端成功则能获取Channel,默认为NettyChannel。
// AbstractClientpublic void send(Object message, boolean sent) throws RemotingException {if (send_reconnect && !isConnected()) {connect();}//getChannel()由子类NettyClientChannel channel = getChannel();//TODO Can the value returned by getChannel() be null? need improvement.if (channel == null || !channel.isConnected()) {throw new RemotingException(this, "message can not send, because channel is closed . url:" + getUrl());}channel.send(message, sent);}
NettyChannel#send()中就会把请求发送给服务端了。
// NettyChannelpublic void send(Object message, boolean sent) throws RemotingException {super.send(message, sent);boolean success = true;int timeout = 0;try {ChannelFuture future = channel.write(message);if (sent) {timeout = getUrl().getPositiveParameter(Constants.TIMEOUT_KEY, Constants.DEFAULT_TIMEOUT);success = future.await(timeout);}Throwable cause = future.getCause();if (cause != null) {throw cause;}} catch (Throwable e) {throw new RemotingException(this, "Failed to send message " + message + " to " + getRemoteAddress() + ", cause: " + e.getMessage(), e);}if (!success) {throw new RemotingException(this, "Failed to send message " + message + " to " + getRemoteAddress()+ "in timeout(" + timeout + "ms) limit");}}
至此服务消费者向服务提供者发送调用服务的请求完成,最终返回的在HeaderExchangeChannel#request()构建的DefaultFuture。还记得DubboInvoker#doInvoke()吗,这里的get()获取服务端响应的代码如下,就是不断轮询判断服务端是否响应,超时则抛出异常。
// DefaultFuturepublic Object get(int timeout) throws RemotingException {if (timeout <= 0) {timeout = Constants.DEFAULT_TIMEOUT;}if (!isDone()) {long start = System.currentTimeMillis();lock.lock();try {while (!isDone()) {done.await(timeout, TimeUnit.MILLISECONDS);if (isDone() || System.currentTimeMillis() - start > timeout) {break;}}} catch (InterruptedException e) {throw new RuntimeException(e);} finally {lock.unlock();}if (!isDone()) {throw new TimeoutException(sent > 0, channel, getTimeoutMessage(false));}}return returnFromResponse();}
总结下发送请求的过程:首先获取已经建立连接的netty客户端,然后构建Request和DefaultFuture,通过netty通道将请求发送给netty服务端之后,DefaultFuture#get()会超时等待,默认超时时间1S。
接下来看服务端怎么处理调用请求的。服务提供者收到服务消费者的调用请求后,首先在DubboCodec#decodeBody()对编码后的请求字节流数据进行解码,得到调用远程服务的Request对象。
protected Object decodeBody(Channel channel, InputStream is, byte[] header) throws IOException {byte flag = header[2], proto = (byte) (flag & SERIALIZATION_MASK);Serialization s = CodecSupport.getSerialization(channel.getUrl(), proto);// get request id.long id = Bytes.bytes2long(header, 4);if ((flag & FLAG_REQUEST) == 0) {......} else {// decode request.Request req = new Request(id);req.setVersion("2.0.0");req.setTwoWay((flag & FLAG_TWOWAY) != 0);if ((flag & FLAG_EVENT) != 0) {req.setEvent(Request.HEARTBEAT_EVENT);}try {Object data;if (req.isHeartbeat()) {data = decodeHeartbeatData(channel, deserialize(s, channel.getUrl(), is));} else if (req.isEvent()) {data = decodeEventData(channel, deserialize(s, channel.getUrl(), is));} else {DecodeableRpcInvocation inv;if (channel.getUrl().getParameter(Constants.DECODE_IN_IO_THREAD_KEY,Constants.DEFAULT_DECODE_IN_IO_THREAD)) {inv = new DecodeableRpcInvocation(channel, req, is, proto);inv.decode();} else {inv = new DecodeableRpcInvocation(channel, req,new UnsafeByteArrayInputStream(readMessageData(is)), proto);}data = inv;}req.setData(data);} catch (Throwable t) {if (log.isWarnEnabled()) {log.warn("Decode request failed: " + t.getMessage(), t);}// bad requestreq.setBroken(true);req.setData(t);}return req;}}
在创建netty服务器的时候会调用NettyServer#doOpen()创建NettyHandler,当前服务端收到请求数据时钩子函数messageReceived会被调用。
//NettyHandler public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {NettyChannel channel = NettyChannel.getOrAddChannel(ctx.getChannel(), url, handler);try {handler.received(channel, e.getMessage());} finally {NettyChannel.removeChannelIfDisconnected(ctx.getChannel());}}
服务端处理请求是分层的,消费者调用提供者的请求在AllChannelHandler#received()中被处理的,此时请求会通过线程池调度线程执行。
// AllChannelHandlerpublic void received(Channel channel, Object message) throws RemotingException {ExecutorService cexecutor = getExecutorService();try {cexecutor.execute(new ChannelEventRunnable(channel, handler, ChannelState.RECEIVED, message));} catch (Throwable t) {......}}
这里会用DecodeHandler对请求解码后到HeaderExchangeHandler#received()中继续处理。
// ChannelEventRunnablepublic void run() {switch (state) {......case RECEIVED:try {handler.received(channel, message);} catch (Exception e) {logger.warn("ChannelEventRunnable handle " + state + " operation error, channel is " + channel+ ", message is " + message, e);}break;......}}
发现收到的消息是请求Request时,调用handleRequest()处理。
// HeaderExchangeHandlerpublic void received(Channel channel, Object message) throws RemotingException {channel.setAttribute(KEY_READ_TIMESTAMP, System.currentTimeMillis());ExchangeChannel exchangeChannel = HeaderExchangeChannel.getOrAddChannel(channel);try {if (message instanceof Request) {// handle request.Request request = (Request) message;if (request.isEvent()) {handlerEvent(channel, request);} else {if (request.isTwoWay()) {Response response = handleRequest(exchangeChannel, request);channel.send(response);} else {handler.received(exchangeChannel, request.getData());}}} else if (message instanceof Response) {handleResponse(channel, (Response) message);} else if (message instanceof String) {if (isClientSide(channel)) {Exception e = new Exception("Dubbo client can not supported string message: " + message + " in channel: " + channel + ", url: " + channel.getUrl());logger.error(e.getMessage(), e);} else {String echo = handler.telnet(channel, (String) message);if (echo != null && echo.length() > 0) {channel.send(echo);}}} else {handler.received(exchangeChannel, message);}} finally {HeaderExchangeChannel.removeChannelIfDisconnected(channel);}}
在handleRequest()中会调用DubboProtocol中的属性requestHandler引用的匿名内部类对象中的reply()方法。
// HeaderExchangeHandlerResponse handleRequest(ExchangeChannel channel, Request req) throws RemotingException {Response res = new Response(req.getId(), req.getVersion());if (req.isBroken()) {Object data = req.getData();String msg;if (data == null) msg = null;else if (data instanceof Throwable) msg = StringUtils.toString((Throwable) data);else msg = data.toString();res.setErrorMessage("Fail to decode request due to: " + msg);res.setStatus(Response.BAD_REQUEST);return res;}// find handler by message class.//获取请求数据,即客户端创建的RpcInvocation对象Object msg = req.getData();try {// handle data.//调用接口方法Object result = handler.reply(channel, msg);res.setStatus(Response.OK);res.setResult(result);} catch (Throwable e) {res.setStatus(Response.SERVICE_ERROR);res.setErrorMessage(StringUtils.toString(e));}return res;}
这里其实就是获取Invoker,再调用Invoker#invoke()。
// DubboProtocol.javaprivate ExchangeHandler requestHandler = new ExchangeHandlerAdapter() {@Overridepublic Object reply(ExchangeChannel channel, Object message) throws RemotingException {if (message instanceof Invocation) {Invocation inv = (Invocation) message;Invoker<?> invoker = getInvoker(channel, inv);......return invoker.invoke(inv);}......}};
在DubboProtocol#export()暴露接口服务时会将serviceKey对应的DubboExporter添加到exporterMap(ConcurrentHashMap<String, Exporter<?>>)中,此时就可以通过serviceKey获取到对应的DubboExporter,再通过DubboExporter获取Invoker,Invoker是在ServiceConfig中的doExportUrlsFor1Protocol()中生成的。
Invoker<?> getInvoker(Channel channel, Invocation inv) throws RemotingException {......String serviceKey = serviceKey(port, path, inv.getAttachments().get(Constants.VERSION_KEY), inv.getAttachments().get(Constants.GROUP_KEY));DubboExporter<?> exporter = (DubboExporter<?>) exporterMap.get(serviceKey);......return exporter.getInvoker();}
找到Invoker以后就会调用invoke(),最终调用子类的doInvoke()方法,wrapper是动态生成的,但是逻辑就是调用接口实现类对象中的方法。
public Result invoke(Invocation invocation) throws RpcException {try {//调用子类的doInvoke()方法return new RpcResult(doInvoke(proxy, invocation.getMethodName(), invocation.getParameterTypes(), invocation.getArguments()));} catch (InvocationTargetException e) {return new RpcResult(e.getTargetException());} catch (Throwable e) {throw new RpcException("Failed to invoke remote proxy method " + invocation.getMethodName() + " to " + getUrl() + ", cause: " + e.getMessage(), e);}}public <T> Invoker<T> getInvoker(T proxy, Class<T> type, URL url) {// TODO Wrapper cannot handle this scenario correctly: the classname contains '$'final Wrapper wrapper = Wrapper.getWrapper(proxy.getClass().getName().indexOf('$') < 0 ? proxy.getClass() : type);return new AbstractProxyInvoker<T>(proxy, type, url) {@Overrideprotected Object doInvoke(T proxy, String methodName,Class<?>[] parameterTypes,Object[] arguments) throws Throwable {return wrapper.invokeMethod(proxy, methodName, parameterTypes, arguments);}};}
调用方法获取到返回值以后就会在HeaderExchangeHandler#received()中将响应发给服务消费者,服务消费者再以类似的过程对响应数据进行解码,返回到应用层。
总结下服务提供者处理调用请求的过程:首先对请求字节流数据进行解码,得到请求Request,然后到保存导出模块的map根据serviceKey获取到代理对象,最终通过代理对象调用接口实现类对象的方法,将返回值发送给服务提供者
相关文章:

dubbo服务消费
dubbo在服务消费时调用的方法栈比较深,所以得一边看一边记,还是比较费力的。在dubbo服务发现中,我们看到通过ReferenceConfig#get()返回的是要调用接口的代理对象,因此通过接口的代理对象调用方法时是调用InvocationHandler(Invok…...

Python调用API接口,实现人脸识别
人生苦短,我用Python 在开始之前,先问问大家: 什么是百度Aip模块? 百度AI平台提供了很多的API接口供开发者快速的调用运用在项目中 本文写的是使用百度AI的**在线接口SDK模块(baidu-aip)**进行实现人脸识…...

2月10日刷题总结
编辑距离题目描述设 AA 和 BB 是两个字符串。我们要用最少的字符操作次数,将字符串 AA 转换为字符串 BB。这里所说的字符操作共有三种:删除一个字符;插入一个字符;将一个字符改为另一个字符。A, BA,B 均只包含小写字母。输入格式第…...

C++学习/温习:新型源码学编程(三)
写在前面(祝各位新春大吉!兔年如意!) 【本文持续更新中】面向初学者撰写专栏,个人原创的学习C/C笔记(干货)所作源代码输出内容为中文,便于理解如有错误之处请各位读者指正请读者评论回复、参与投票…...

阿里云ecs服务器搭建CTFd(ubuntu20)
1.更新apt包索引 sudo apt-get update更新源 1、使用快捷键【ctrlaltt】打开终端。 2、输入以下命令备份原有软件源文件。 cp /etc/apt/sources.list /etc/apt/sources.list.bak_yyyymmdd 3、再输入以下命令打开sources.list文件并添加新的软件源地址。 vim /etc/apt/sources.…...

视频号小店新订单如何实时同步企业微信
随着直播带货的火热,视频号小店也为商家提供商品信息服务、商品交易,支持商家在视频号运营电商,许多企业也将产品的零售路径渗透至视频号小店中了。如果我们希望在视频号小店接收到订单后,能尽快及时发货,给用户较好的…...

ag-Grid Enterprise
ag-Grid Enterprise Ag-Grid被描述为一种商业产品,已在EULA下分发,它非常先进,性能就像Row分组一样,还有范围选择、master和case、行的服务器端模型等等。 ag Grid Enterprise的巨大特点: 它具有以下功能和属性&#x…...

扫雷——C语言【详解+全部码源】
前言:今天我们学习的是C语言中另一个比较熟知的小游戏——扫雷 下面开始我们的学习吧! 文章目录游戏整体思路游戏流程游戏菜单的打印创建数组并初始化布置雷排查雷完整代码game.hgame.ctest.c游戏整体思路 我们先来看一下网上的扫雷游戏怎么玩 需要打印…...

【C++】类和对象(下)
文章目录1. 再谈构造函数1.1 初始化列表1.2 explicit关键字2. static成员2.1 概念2.2 特性3. 友元3.1 友元函数3.1 友元类4. 内部类5. 匿名对象6. 拷贝对象时的一些编译器优化7. 再次理解类和对象1. 再谈构造函数 1.1 初始化列表 在创建对象时,编译器通过调用构造…...

计算机网络
TCP和UDP TCP如何保证传输的可靠性 基于数据块传输:应用数据被分割成TCP认为最适合的数据块,传输给网络层,称为报文段连接管理:三次握手和四次挥手对失序数据包重新排序以及去重:每个数据包有一个序列号,…...

【Unity VR开发】结合VRTK4.0:将浮点操作转换为布尔操作
语录: 奈何桥上奈何愁,奈何桥下浣溪流,奈何人人奈何泪,奈何奈何洗春秋。 前言: 有时,您可能希望使用 一个值来激活或停用操作类型。例如,按下控制器上的扳机轴会导致在完全按下扳机时发生操作。…...

error when starting dev server:Error: Failed to resolve vue/compiler-sfc.
对于node 的包管理工具,我一般习惯用 yarn,但是最近使用 yarn 创建前端项目的时候出了一些问题。yarn create vite vite-project报错如下:error when starting dev server:Error: Failed to resolve vue/compiler-sfc.vitejs/plugin-vue requ…...

Vue2之完整基础介绍和指令与过滤器
Vue2之基础介绍和指令与过滤器一、简介1、概念2、vue的两个特性2.1 数据驱动视图2.2 双向数据绑定3、MVVM二、vue基础用法1、导入vue.js的script脚本文件2、在页面中声明一个将要被vue所控制的DOM区域3、创建vm实例对象(vue实例对象)4、样例完整代码三、…...

JY-7A/3DK/220 19-130V静态【电压继电器】
系列型号 JY-7A/1DK不带辅助电源电压继电器;JY-7B/1DK不带辅助电源电压继电器; JY-7/1DK/120不带辅助电源电压继电器;JY-7/1DK/120不带辅助电源电压继电器; JY-7A/1DKQ不带辅助电源电压继电器;JY-7B/1DKQ不带辅助电源…...

[ECCV 2018] Learning to Navigate for Fine-grained Classification
Contents MethodNavigator-Teacher-Scrutinizer Network (NTS-Net)Navigator and TeacherScrutinizerNetwork architectureJoint training algorithmExperimentReferencesMethod Navigator-Teacher-Scrutinizer Network (NTS-Net) Approach Overview:NTS-Net 在不使用额外的 …...

关于apifox和postman接口工具我有话要说~~
Apifox 和 Postman 都是流行的接口测试工具,各自有其优势和缺点。Apifox 的优势在于它提供了强大的可视化界面,可以方便地测试和监控 API。它还支持多种请求方式,并且支持对请求和响应进行代码生成。但是,Apifox 的缺点在于它不太…...

Vue3通透教程【二】更高效的构建工具—Vite
文章目录🌟 写在前面🌟 webpack🌟 Vite是什么?🌟 使用Vite创建项目🌟 写在最后🌟 写在前面 专栏介绍: 凉哥作为 Vue 的忠实 粉丝输出过大量的 Vue 文章,应粉丝要求开始更…...

前端中如何判断在线状态?
判断在线状态为了判断浏览器的在线状态,HTML5提供了两种方法来检测是否在线。(1)onLine属性:通过navigator对象的onLine属性可返回当前是否在线。如果返回true,则表示在线;如果返回false,则表示…...

[MySQL教程①] - MySQL的安装
目录 ❤ Windows下安装MySQL ❤ 下载mysql installer安装 ❤ 下载zip安装包安装 现在作为服务器操作系统的一般有三种,Windows Server,Linux,Unix,在这里我们只介绍在windows下和linux下安装mysql,Unix下安装应该…...

第八节 Linux 设备树
Linux3.x 以后的版本才引入了设备树,设备树用于描述一个硬件平台的板级细节。在早些的linux内核,这些“硬件平台的板级细节”保存在linux 内核目录“/arch”,以ARM 平台为例“硬件平台的板级细节”保存在“/arch/arm/plat-xxx”和“/arch/arm…...

一文了解kafka消息队列,实现kafka的生产者(Producer)和消费者(Consumer)的代码,消息的持久化和消息的同步发送和异步发送
文章目录1. kafka的介绍1.2 Kafka适合的应用场景1.2 Kafka的四个核心API2. 代码实现kafka的生产者和消费者2.1 引入加入jar包2.2 生产者代码2.3 消费者代码2.4 介绍kafka生产者和消费者模式3. 消息持久化4. 消息的同步和异步发送5. 参考文档1. kafka的介绍 最近在学习kafka相关…...

数学建模学习笔记(20)典型相关分析
典型相关分析概述:研究两组变量(每组变量都可能有多个指标)之间的相关关系的一种多元统计方法,能够揭示两组变量之间的内在联系。 典型相关分析的思想:把多个变量和多个变量之间的相关化为两个具有代表性的变量之间的…...

EL表达式
EL的概念JSP表达式语言(EL)使得访问存储在JavaBean中的数据变得非常简单。EL的作用用于替换作用域对象.getAttribute("name");3. EL的应用(获取基本类型、字符串)既可以用来创建算术表达式也可以用来创建逻辑表达式。在…...

优先级队列(PriorityQueue 和 Top-K问题)
一、PriorityQueue java中提供了两种优先级队列:PriorityQueue 和 PriorityBlockingQueue。其中 PriorityQueue 是线程不安全的,PriorityBolckingQueue 是线程安全的。 PriorityQueue 使用的是堆,且默认情况下是小堆——每次获取到的元素都是…...

计算机组成与设计04——处理器
系列文章目录 本系列博客重点在深圳大学计算机系统(3)课程的核心内容梳理,参考书目《计算机组成与设计》(有问题欢迎在评论区讨论指出,或直接私信联系我)。 第一章 计算机组成与设计01——计算机概要与技…...

IT行业那么辛苦,我们为什么还要选择它?
疫情三年,我们学会了什么?工作诚可贵,技能价更高。 搞IT辛苦?有啥辛苦的?说什么辛苦?能有工作,工资又高,还要什么自行车,有啥搞啥吧!每次看到网络上有人问有…...

PyTorch学习笔记:nn.CrossEntropyLoss——交叉熵损失
PyTorch学习笔记:nn.CrossEntropyLoss——交叉熵损失 torch.nn.CrossEntropyLoss(weightNone, size_averageNone, ignore_index-100, reduceNone, reductionmean, label_smoothing0.0)功能:创建一个交叉熵损失函数: l(x,y)L{l1,…,lN}T&…...

【VictoriaMetrics】什么是VictoriaMetrics
VictoriaMetrics是一个快速、经济、可扩展的监控解决方案和时间序列数据库,有单机版和集群版本,基础功能及集群版本基本功能不收费,VictoriaMetrics有二进制安装版本、Docker安装版本等多种安装方式,其源码及部署包更新迭代很快,VictoriaMetrics具有以下突出特点: 它可以作…...

(第五章)OpenGL超级宝典学习:统一变量(uniform variable)
统一变量 前言 本篇在讲什么 本篇记录对glsl中的变量uniform的认知和学习 本篇适合什么 适合初学Open的小白 适合想要学习OpenGL中uniform的人 本篇需要什么 对C语法有简单认知 对OpenGL有简单认知 最好是有OpenGL超级宝典蓝宝书 依赖Visual Studio编辑器 本篇的特色 …...

数据存储技术复习(四)未完
1.什么是NAS。一般用途服务器与NAS设备之间有何不同。NAS是一个基于IP的专用高性能文件共享和存储设备。—般用途服务器可用于托管任何应用程序,因为它运行的是一般用途操作系统NAS设备专用于文件服务。它具有专门的操作系统,专用于通过使用行业标准协议…...