如何用Netty实现一个轻量级的HTTP代理服务器

  为什么会想通过Netty构建一个HTTP代理服务器?这也是笔者发表这篇文章的目的所在。

  其主要还是源于解决在日常开发测试过程中,一直困扰测试同学很久的一个问题,现在我就来具体阐述一下这个问题。

  在日常开发测试过程中,为了确保上线项目的万无一失,集成测试通常有部署,测试环境和回归环境这两套环境。开发人员根据需求编写代码模块,自测通过之后,由测试的同学更新到测试环境,进行测试。如果测试通过,确定项目具备上线条件,后续会在回归环境,进行回归测试。回归验证通过的项目,才具备上线条件。

  由于模块的复杂性和多样性,我们系统要跟外系统进行一些数据的交互,这通常是通过HTTP协议方式完成的。现在由于某些条件的限制,通常只有测试环境的网络和端口是和外系统是打通的,回归环境的这块网络链路是关闭的。这样就产生了一个很尴尬的问题:如果一个模块有跟外系统进行交互,回归环境是不具备回归条件的,这样就要测试的同学,额外把模块更新到测试环境来验证,这样不仅耗时耗力。并且由于测试环境和回归环境系统数据的差异,往往可能导致项目的潜在风险没有被及时地发现。

  现在迫切希望有一个HTTP代理服务器,能够路由回归环境的请求到测试环境。更进一步地,如果能根据请求报文的某些关键字来过滤,决定最终路由的地址,这个当然是最好了。

  基于这些因素,考虑到HTTP代理服务器的主要用途是转发URL请求,可选的方案有很多种。比如Apache、Nginx等等。但是最终都没有被采用,主要基于以下几点考虑:

  1. Apache服务器不能根据某些指定的关键字过滤转发URL请求,只能做简单的代理转发。
  2. Nginx相比Aapche服务器单纯进行请求转发而言,通过OpenResty(http://openresty.org/cn/),可以把lua解析器内嵌到Nginx,这样可以编写lua脚本的关键字过滤规则,但是要测试同学短时间内学会配置不太现实。

  有没有通过简单的几个配置,就可以达到目的的可行方案呢?

  我首先想到了使用Netty这个NIO框架,来实现一个轻量级的HTTP代理转发服务器,同时只要简单地配置过滤规则,就可以实现请求的规则路由。

  本文要求你熟悉Netty网络框架的工作流程,基本原理。有兴趣的朋友,可以认真研读一下《Netty in Action》这本书,对提高Netty的功力有很大帮助。

  言归正传,下面是这个HTTP代理转发服务器的工作流程图:

  这里我简单描述一下:

  • 首先是Netty的服务端连接器(Acceptor)线程接收到HTTP请求,然后会把这个请求放入后端Netty专门负责处理I/O操作的线程池中。这个也是Netty经典的主从Reactor多线程模型的应用。
  • I/O处理线程先对HTTP请求,调用HttpRequestDecoder解码器进行解码。
  • HttpRequestDecoder把解码的结果,通知给路由规则计算的核心模块(GatewayServerHandler),核心模块根据配置加上请求报文中的关键字,计算出要转发的URL地址。
  • 通过HTTP POST方式把请求,转发给计算出来的URL地址。
  • 获取HTTP POST的获得到的应答结果。
  • 然后通过HttpResponseEncoder编码器,把应答结果进行HTTP编码,最后透传给调用方。

  流程描述很简单,现在关键是,如何设计关键字路由规则配置模块。

  我是通过属性配置文件(.properties)方式来实现的,主要有两个配置文件。

  • netty-gateway.properties配置文件,主要是用来描述URL中的路径、以及其没有和请求URL路径匹配成功时,默认转发的URL地址。

  配置文件的配置参考说明:

#配置说明参考:
#netty-gateway.config1.serverPath ==> URL路径关键字。
#netty-gateway.config1.defaultAddr ==> 请求报文中的关键字没有匹配成功时,默认转发的URL地址。
#config的数字后缀顺序递增即可。netty-gateway.config1.serverPath=fcgi-bin/UIG_SFC_186
netty-gateway.config1.defaultAddr=http://10.46.158.10:8088/fcgi-bin/UIG_SFC_186netty-gateway.config2.serverPath=fcgi-bin/BSSP_SFC
netty-gateway.config2.defaultAddr=http://10.46.158.10:8089/fcgi-bin/BSSP_SFC
  • netty-route.properties配置文件,则是主要配置URL中的路径、请求报文关键字集合、以及请求的URL路径、请求报文关键字和配置的匹配成功时,转发的URL地址。

  配置文件的配置参考说明:

#配置说明参考:
#netty-gateway.config1.serverPath ==> URL路径关键字。
#netty-gateway.config1.keyWord ==> 请求报文匹配关键字。支持1~N个关键字,多个关键字用逗号分割,关键字之间是逻辑与的关系。
#netty-gateway.config1.matchAddr ==> 请求报文匹配关键字匹配成功时,转发的ULR地址。
#config的数字后缀顺序递增即可。netty-gateway.config1.serverPath=fcgi-bin/UIG_SFC_186
netty-gateway.config1.keyWord=1,2,3
netty-gateway.config1.matchAddr=http://10.46.158.20:8088/fcgi-bin/UIG_SFC_186netty-gateway.config2.serverPath=fcgi-bin/UIG_SFC_186
netty-gateway.config2.keyWord=1,2,3,4
netty-gateway.config2.matchAddr=http://10.46.158.20:8088/fcgi-bin/UIG_SFC_186netty-gateway.config3.serverPath=fcgi-bin/BSSP_SFC
netty-gateway.config3.keyWord=HelloWorldNettyGateway
netty-gateway.config3.matchAddr=http://10.46.158.20:8089/fcgi-bin/BSSP_SFC

  有了上述两个基础的配置信息之后,就可以实现基于Netty的关键字HTTP路由转发服务器了。

  这里主要说明关键代码模块的设计思路:

  首先是GatewayAttribute类,它主要对应netty-gateway.properties配置文件的数据结构。

package com.newlandframework.gateway.commons;/*** @author tangjie<https://github.com/tang-jie>* @filename:GatewayAttribute.java* @description:GatewayAttribute功能模块* @blogs http://www.cnblogs.com/jietang/* @since 2018/4/18*/
public class GatewayAttribute {private String serverPath;private String defaultAddr;public String getDefaultAddr() {return defaultAddr;}public void setDefaultAddr(String defaultAddr) {this.defaultAddr = defaultAddr;}public String getServerPath() {return serverPath;}public void setServerPath(String serverPath) {this.serverPath = serverPath;}
}

  其次是RouteAttribute类,它主要对应netty-route.properties配置文件的数据结构。

package com.newlandframework.gateway.commons;/*** @author tangjie<https://github.com/tang-jie>* @filename:RouteAttribute.java* @description:RouteAttribute功能模块* @blogs http://www.cnblogs.com/jietang/* @since 2018/4/18*/
public class RouteAttribute {private String serverPath;private String keyWord;private String matchAddr;public String getMatchAddr() {return matchAddr;}public void setMatchAddr(String matchAddr) {this.matchAddr = matchAddr;}public String getServerPath() {return serverPath;}public void setServerPath(String serverPath) {this.serverPath = serverPath;}public String getKeyWord() {return keyWord;}public void setKeyWord(String keyWord) {this.keyWord = keyWord;}
}

  然后通过实现spring框架的BeanDefinitionRegistryPostProcessor接口,来实现配置文件的自动加载注入。对应代码如下:

package com.newlandframework.gateway.commons;import org.springframework.beans.BeansException;
import org.springframework.beans.MutablePropertyValues;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;import java.io.IOException;
import java.util.*;import static com.newlandframework.gateway.commons.GatewayOptions.*;/*** @author tangjie<https://github.com/tang-jie>* @filename:RoutingLoader.java* @description:RoutingLoader功能模块* @blogs http://www.cnblogs.com/jietang/* @since 2018/4/18*/
public class RoutingLoader implements BeanDefinitionRegistryPostProcessor {public static final List<RouteAttribute> ROUTERS = new ArrayList<RouteAttribute>();public static final List<GatewayAttribute> GATEWAYS = new ArrayList<GatewayAttribute>();private static final List<String> KEY_ROUTERS = new ArrayList<String>();private static final List<String> KEY_GATEWAYS = new ArrayList<String>();@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {initGatewayRule(registry);initRouteRule(registry);}@Overridepublic void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {GATEWAYS.clear();ROUTERS.clear();for (String beanName : KEY_GATEWAYS) {GATEWAYS.add(beanFactory.getBean(beanName, GatewayAttribute.class));}for (String beanName : KEY_ROUTERS) {ROUTERS.add(beanFactory.getBean(beanName, RouteAttribute.class));}}//加载netty-gateway.properties配置文件private void initGatewayRule(BeanDefinitionRegistry registry) {GenericBeanDefinition beanDefinition = new GenericBeanDefinition();Resource resource = new ClassPathResource(GATEWAY_OPTION_GATEWAY_CONFIG_FILE);Properties p = new Properties();try {p.load(resource.getInputStream());String key = null;String keyPrefix = null;String defaultAddr = null;String serverPath = null;Map<String, String> valuesMap = null;MutablePropertyValues mpv = null;for (Object obj : p.keySet()) {key = obj.toString();if (key.endsWith(GATEWAY_PROPERTIES_PREFIX_SERVER_PATH)) {keyPrefix = key.substring(0, key.indexOf(GATEWAY_PROPERTIES_PREFIX_SERVER_PATH));serverPath = p.getProperty(keyPrefix + GATEWAY_PROPERTIES_PREFIX_SERVER_PATH).trim();defaultAddr = p.getProperty(keyPrefix + GATEWAY_PROPERTIES_PREFIX_DEFAULT_ADDR).trim();valuesMap = new LinkedHashMap<String, String>();valuesMap.put(GATEWAY_PROPERTIES_DEFAULT_ADDR, defaultAddr);valuesMap.put(GATEWAY_PROPERTIES_SERVER_PATH, serverPath);mpv = new MutablePropertyValues(valuesMap);beanDefinition = new GenericBeanDefinition();beanDefinition.setBeanClass(GatewayAttribute.class);beanDefinition.setPropertyValues(mpv);registry.registerBeanDefinition(serverPath, beanDefinition);KEY_GATEWAYS.add(serverPath);}}} catch (IOException e) {e.printStackTrace();}}//加载netty-route.properties配置文件private void initRouteRule(BeanDefinitionRegistry registry) {GenericBeanDefinition beanDefinition = new GenericBeanDefinition();Resource resource = new ClassPathResource(GATEWAY_OPTION_ROUTE_CONFIG_FILE);Properties p = new Properties();try {p.load(resource.getInputStream());String key = null;String keyPrefix = null;String keyWord = null;String matchAddr = null;String serverPath = null;Map<String, String> valuesMap = null;MutablePropertyValues mpv = null;for (Object obj : p.keySet()) {key = obj.toString();if (key.endsWith(GATEWAY_PROPERTIES_PREFIX_KEY_WORD)) {keyPrefix = key.substring(0, key.indexOf(GATEWAY_PROPERTIES_PREFIX_KEY_WORD));keyWord = p.getProperty(keyPrefix + GATEWAY_PROPERTIES_PREFIX_KEY_WORD).trim();if (keyWord.isEmpty()) continue;matchAddr = p.getProperty(keyPrefix + GATEWAY_PROPERTIES_PREFIX_MATCH_ADDR).trim();serverPath = p.getProperty(keyPrefix + GATEWAY_PROPERTIES_PREFIX_SERVER_PATH).trim();valuesMap = new LinkedHashMap<String, String>();valuesMap.put(GATEWAY_PROPERTIES_KEY_WORD, keyWord);valuesMap.put(GATEWAY_PROPERTIES_MATCH_ADDR, matchAddr);valuesMap.put(GATEWAY_PROPERTIES_SERVER_PATH, serverPath);mpv = new MutablePropertyValues(valuesMap);beanDefinition = new GenericBeanDefinition();beanDefinition.setBeanClass(RouteAttribute.class);beanDefinition.setPropertyValues(mpv);String beanName = serverPath + GATEWAY_OPTION_SERVER_SPLIT + keyWord;registry.registerBeanDefinition(beanName, beanDefinition);KEY_ROUTERS.add(beanName);}}} catch (IOException e) {e.printStackTrace();}}
}

  最后是重点的关键字过滤转发代码模块,主要完成路由转发地址的匹配计算、路由转发、以及应答转发结果给请求客户端的工作。

import com.newlandframework.gateway.commons.GatewayAttribute;
import com.newlandframework.gateway.commons.HttpClientUtils;
import com.newlandframework.gateway.commons.RouteAttribute;
import com.newlandframework.gateway.commons.RoutingLoader;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.*;
import io.netty.util.Signal;
import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.FutureListener;
import io.netty.util.concurrent.GlobalEventExecutor;
import org.springframework.util.StringUtils;import java.util.concurrent.Callable;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;import static com.newlandframework.gateway.commons.GatewayOptions.*;
import static io.netty.handler.codec.http.HttpResponseStatus.BAD_REQUEST;
import static io.netty.handler.codec.http.HttpResponseStatus.CONTINUE;
import static io.netty.handler.codec.http.HttpResponseStatus.OK;
import static io.netty.handler.codec.http.HttpVersion.HTTP_1_1;/*** @author tangjie<https://github.com/tang-jie>* @filename:GatewayServerHandler.java* @description:GatewayServerHandler功能模块* @blogs http://www.cnblogs.com/jietang/* @since 2018/4/18*/
public class GatewayServerHandler extends SimpleChannelInboundHandler<Object> {private HttpRequest request;private StringBuilder buffer = new StringBuilder();private String url = "";private String uri = "";private StringBuilder respone;private GlobalEventExecutor executor = GlobalEventExecutor.INSTANCE;private CountDownLatch latch = new CountDownLatch(1);@Overridepublic void channelReadComplete(ChannelHandlerContext ctx) {ctx.flush();}@Overrideprotected void channelRead0(ChannelHandlerContext ctx, Object msg) {if (msg instanceof HttpRequest) {HttpRequest request = this.request = (HttpRequest) msg;//收到客户端的100-Continue协议请求,说明客户端要post数据给服务器if (HttpUtil.is100ContinueExpected(request)) {notify100Continue(ctx);}buffer.setLength(0);uri = request.uri().substring(1);}if (msg instanceof HttpContent) {HttpContent httpContent = (HttpContent) msg;ByteBuf content = httpContent.content();if (content.isReadable()) {buffer.append(content.toString(GATEWAY_OPTION_CHARSET));}//获取post数据完毕if (msg instanceof LastHttpContent) {LastHttpContent trace = (LastHttpContent) msg;System.out.println("[NETTY-GATEWAY] REQUEST : " + buffer.toString());//根据netty-gateway.properties、netty-route.properties匹配出最终转发的URL地址url = matchUrl();System.out.println("[NETTY-GATEWAY] URL : " + url);//http请求异步转发处理,不要阻塞当前的Netty Handler的I/O线程,提高服务器的吞吐量。Future<StringBuilder> future = executor.submit(new Callable<StringBuilder>() {@Overridepublic StringBuilder call() {return HttpClientUtils.post(url, buffer.toString(), GATEWAY_OPTION_HTTP_POST);}});future.addListener(new FutureListener<StringBuilder>() {@Overridepublic void operationComplete(Future<StringBuilder> future) throws Exception {if (future.isSuccess()) {respone = ((StringBuilder) future.get(GATEWAY_OPTION_HTTP_POST, TimeUnit.MILLISECONDS));} else {respone = new StringBuilder(((Signal) future.cause()).name());}latch.countDown();}});try {latch.await();writeResponse(respone, future.isSuccess() ? trace : null, ctx);ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);} catch (InterruptedException e) {e.printStackTrace();}}}}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {cause.printStackTrace();ctx.close();}//根据netty-gateway.properties、netty-route.properties匹配出最终转发的URL地址private String matchUrl() {for (GatewayAttribute gateway : RoutingLoader.GATEWAYS) {if (gateway.getServerPath().equals(uri)) {for (RouteAttribute route : RoutingLoader.ROUTERS) {if (route.getServerPath().equals(uri)) {String[] keys = StringUtils.delimitedListToStringArray(route.getKeyWord(), GATEWAY_OPTION_KEY_WORD_SPLIT);boolean match = true;for (String key : keys) {if (key.isEmpty()) continue;if (buffer.toString().indexOf(key.trim()) == -1) {match = false;break;}}if (match) {return route.getMatchAddr();}}}return gateway.getDefaultAddr();}}return GATEWAY_OPTION_LOCALHOST;}//把路由转发的结果应答给http客户端private void writeResponse(StringBuilder respone, HttpObject current, ChannelHandlerContext ctx) {if (respone != null) {boolean keepAlive = HttpUtil.isKeepAlive(request);FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, current == null ? OK : current.decoderResult().isSuccess() ? OK : BAD_REQUEST,Unpooled.copiedBuffer(respone.toString(), GATEWAY_OPTION_CHARSET));response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=GBK");if (keepAlive) {response.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);}ctx.write(response);}}private static void notify100Continue(ChannelHandlerContext ctx) {FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, CONTINUE);ctx.write(response);}
}

  这样把整个工程maven打包部署运行,服务器默认启动端口8999,你可以通过netty-gateway.xml的gatewayPort属性进行配置调整。

  控制台打印出如下的信息,则说明服务器启动成功。

  下面继续以一个实际的案例来说明一下,如何配置使用这个HTTP服务器。

  NettyGateway代理转发场景描述

  • NettyGateway部署在10.1.1.76主机,URL中的路径为:fcgi-bin/BSSP_SFC
  • 如果请求报文中出现HelloWorldNettyGateway关键字的时候,转发到http://10.46.158.20:8089/fcgi-bin/BSSP_SFC
  • 否则转发到http://10.46.158.10:8089/fcgi-bin/BSSP_SFC

  NettyGateway代理转发场景配置说明

  • 配置文件netty-gateway.properties新增如下属性:
netty-gateway.config2.serverPath=fcgi-bin/BSSP_SFC
netty-gateway.config2.defaultAddr=http://10.46.158.10:8089/fcgi-bin/BSSP_SFC
  • 配置文件netty-route.properties新增如下属性:
netty-gateway.config3.serverPath=fcgi-bin/BSSP_SFC
netty-gateway.config3.keyWord=HelloWorldNettyGateway
netty-gateway.config3.matchAddr=http://10.46.158.20:8089/fcgi-bin/BSSP_SFC

  NettyGateway代理转发测试

  • 发送HelloWorldNettyGateway到NettyGateway,关键字匹配成功,路由到http://10.46.158.20:8089/fcgi-bin/BSSP_SFC

  • 发送Tangjie到NettyGateway,关键字匹配不成功,路由到默认的http://10.46.158.10:8089/fcgi-bin/BSSP_SFC

 

  到此,整个基于Netty实现的,一个轻量级HTTP代理服务器的主要设计思路已经介绍完了。整个服务器实现代码非常的少,而且通过简单地配置,就能很好的满足实际要求。相比通过“重量级”的服务器:Apache、Nginx,进行HTTP代理转发而言,提供了另外一种解决问题的思路。在部门的实际部署运行中,这个Netty写的小而精的服务器,运行良好,很好地帮助测试部门的同学,解决了一个困扰他们很久的问题。

  俗话说得好,黑猫、白猫,抓到老鼠就是好猫。我把这个基于Netty的HTTP代理服务器取名“NettyGateway”,目前把代码托管在github上面:https://github.com/tang-jie/NettyGateway。

  有兴趣的朋友,可以关注一下。由于技术能力所限,文中难免有纰漏和不足,大家如果有疑问,欢迎在下方的博客园评论区留言,或者通过github提issue给我。

  最后,感谢您耐心阅读。如果喜欢本文的话,可以点击推荐,算是给我一个小小的鼓励!谢谢大家。

 

  附上本人曾经在博客园发表的,基于Netty框架实际应用的原创文章,有兴趣的朋友,可以关联阅读。

  基于Netty构建的RPC

  谈谈如何使用Netty开发实现高性能的RPC服务器

  Netty实现高性能RPC服务器优化篇之消息序列化

  基于Netty打造RPC服务器设计经验谈

  基于Netty构建的简易消息队列

  Netty构建分布式消息队列(AvatarMQ)设计指南之架构篇

  Netty构建分布式消息队列实现原理浅析

 

转载于:https://www.cnblogs.com/jietang/p/8926325.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/267200.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微型计算机系统包括( )几部分,微型计算机系统包括哪几个部分?

完整的计算机系统包括两大部分&#xff0c;即硬件系统和软件系统。所谓硬件&#xff0c;是指构成计算机的物理设备&#xff0c;即由机械、电子器件构成的具有输入、存储、计算、控制和输出功能的实体部件。下面介绍一下电脑主机的各个部件&#xff1a;(1)电源&#xff1a;电源是…

hello my first blog

2019独角兽企业重金招聘Python工程师标准>>> <?phpfunction hello {echo hello world;} 转载于:https://my.oschina.net/guanyue/blog/52043

python 钉钉机器人发送图片_Python结合钉钉实时自动监控股票行情,上班炒股再也不怕老板发现...

最近全球股市开始回暖&#xff0c;之前清仓的股民现在又陆续开始建仓。股市交易时间&#xff0c;一般也是上班时间&#xff0c;频繁的查看股票软件不仅会影响工作效率&#xff0c;也容易被老板以为工作不饱和。所以&#xff0c;我们就借助python的强大功能&#xff0c;把提前设…

Ubuntu远程操作Linux服务器GUI程序

此文针对在Server端执行gui程序失败的情况 OS&#xff1a;Ubuntu 12.04&#xff08;以下操作应该在其他版本的Ubuntu上也适用&#xff09; 首先&#xff0c;检查你的Xserver&#xff0c;看是否有一个“-nolisten”选项。在终端执行“ps -ef | grep nolisten",查看返回内容…

IDEA MAVEN Project 显示问题

今天正常打开idea&#xff0c;却发现maven窗口找不到了&#xff1a;试了这些方法 首先idea自带了maven控件&#xff0c;不像Eclipse还需要下载控件&#xff0c;如果你以前有maven在右边&#xff0c;出于某种原因&#xff0c;消失找不到 了&#xff0c;你可以试试我写的方法。 …

2 什么是计算机网络设置密码,计算机网络技术及应用(第2版)第6章网络操作系统基本配置.ppt...

计算机网络技术及应用1&#xff0e;访问组策略控制台模板 (1)当前计算机的控制台 Windows server 2008系统默认安装了组策略程序&#xff0c;在“开始”菜单中单击“运行”项&#xff0c;输入gpedit.msc并确定&#xff0c;即可运行程序。 (2)打开其他计算机的控制台 配置其他的…

画图调子图间距_好看好用的桥梁工程图,你值得拥有!

来源&#xff1a;筑龙路桥设计桥梁工程图的内容1.桥位地形、地物、地质及水文资料图&#xff0c;用来表示桥梁位置及周边关系的图纸。桥位平面图、桥位地质断面图。2.桥梁总体布置图&#xff0c;表示桥梁整体形状、大小、结构的图纸。立面图、侧立面图。3.桥梁的上部、下部构造…

MySQL 调用存储过程

1&#xff1a;存储过程返回值 View Code public void InputOutputParameters() { AdoHelper ado AdoHelper.CreateHelper(DbProvideType.MySql); ado.ExecuteNonQuery(conn, CommandType.Text, "CREATE PROCEDURE spTest1( INOUT strVal V…

佳博热敏条码打印机修改ip_高赋码热转印打印和热敏打印区别

在热转印打印中&#xff0c;热敏打印头给色带加热&#xff0c;油墨熔化在标签材料上以形成图案。色带材料被介质吸收&#xff0c;图案构成了标签的一部分。该技术提供了其他按需式打印技术无法匹敌的图案质量和耐久性。与热敏打印机相比&#xff0c;热转印打印机可接受更多品种…

cocos2d的常用动作及效果总结之五:Animation

这一篇是讲一下如何在cocos2d中实现动画。 实现动画的步骤&#xff1a; 加载帧生成动画对象运行动画加载帧我用过两种方式&#xff1a; 第一种是使用.plist文件&#xff0c;通过CCSpriteFrameCache读取动画帧&#xff0c;并加载到CCAnimation对象中&#xff0c;如下&#xff1a…

雷鸟html签名设置,thunderbird 使用OpenPGP加解密邮件

一、添加插件Enigmail二、进行密钥管理&#xff0c;并创建->新密钥对三、选择账户&#xff0c;选择有无密码&#xff0c;创建密钥。根据提示选择是否要撤销文件。选择无密码创建密钥对速度快。最好选择生成撤销证书&#xff0c;在以后密钥对无用时告诉密钥服务器撤销无效公钥…

男生学计算机哪专业好,男生学计算机科学与技术专业好不好有前途吗

每年都有很多考生在填报志愿的时候&#xff0c;会选择填报计算机科学与技术专业。那么&#xff0c;男生学计算机科学与技术专业好吗&#xff1f;下面和小编一起来看看吧&#xff01;男生适合学计算机科学与技术专业吗计算机科学与技术专业主要培养具有良好的科学素养&#xff0…

字符串不替代_TI-Nspire 系列的字符串操作

本文遵循 CC BY-NC-SA 协议。一 前言在编程中&#xff0c;对字符串进行操作是很常见的。但是TI-Nsipre 对字符进行操作的函数有限&#xff0c;缺少一些如在字符串中插入字符、删除字符等常用功能&#xff0c;给编程带来不便。笔者经过研究&#xff0c;实现了在字符串中插入字符…

如何在Win Server 2008R2环境下,把域帐户加到本地管理员组??

如何在Win Server 2008R2环境下&#xff0c;把域帐户加到本地管理员组 我们在大型企业中&#xff0c;经常不会用域管理员登陆&#xff0c;而是将某个OU下特定用户加入到全局组&#xff0c;或者将某个用户加入到本地管理员组中&#xff0c;但是局域网庞大&#xff0c;不可能一个…

3ds Max 2018 在安装后无法启动或出现不稳定

问题&#xff1a; 安装 3ds Max 2018 后&#xff0c;软件无法正常启动&#xff0c;或在打开后不久出现不稳定和崩溃。 原因&#xff1a; 有多种原因可能会导致这些错误&#xff1a; ▪ 3ds Max、Windows 更新和 ProSound.dlc 声音驱动程序之间发生冲突&#xff1b; ▪ Windows …

怎样用计算机算出别人的出生日期,【怀孕出生日期计算器_怀孕出生日期计算器专题】- 天鹅到家...

很多要想比较技术专业且精确地预测分析自身的排卵期的女性、要想怀孕或避开怀孕的女性&#xff0c;或是要想根据对排卵期的预测分析&#xff0c;根据排卵期時间的不一&#xff0c;对生理学病症做出一些预防的女性&#xff0c;能够运用女性排卵期计算器。女性排卵期计算器可以根…

c++中内敛函数_C/C++求职者必备 23 道面试题,一道试题一份信心

1、 Static有什么用途&#xff1f;(1)函数体内static变量的作用范围是该函数体&#xff0c;该变量的内存只被分配一次&#xff0c;因此它的值在下次调用时不变&#xff1b; (2)模块内的static全局变量同样只能在该模块内的函数访问和调用&#xff0c;不能被模块外的其他函数访问…

不安装oracle客户端如何用plsql连接oracle

https://jingyan.baidu.com/article/d2b1d1029f2bbb5c7e37d4f4.html转载于:https://www.cnblogs.com/feifeicui/p/8949725.html

计算机专业研究生面试英语翻译,20考研复试英语面试最强攻略,都是干货!

原标题&#xff1a;20考研复试英语&面试最强攻略&#xff0c;都是干货&#xff01;考研初试千辛万苦通过&#xff0c;成功就在眼前&#xff0c;可是偏偏中间还有复试英语这个拦路虎&#xff0c;复试中的英语环节每年让不少同学都很紧张&#xff0c;要想对答如流&#xff0c…

克隆虚拟机启动网卡提示错误 Device eth0 does not seem to be present, delaying initialization...

错误原因&#xff1a; 克隆的Linux系统在新的机器上运行&#xff0c;新服务器网卡物理地址已经改变。而/etc/udev/rules.d/70-persistent-net.rules这个文件确定了网卡和MAC地址的信息之间的绑定&#xff0c;克隆后的网卡的MAC已经发生了变化&#xff0c;所以导致系统认为网络设…