核心源码-服务端启动流程 核心服务器英文
lipiwang 2024-10-31 15:23 15 浏览 0 评论
前言
Dubbo是阿里巴巴开源的基于Java实现的高性能、透明化的RPC框架。深入了解Dubbo源码,有助于快速定位问题、高效实现自定义拓展。本文以Dubbo服务端初始化过程为例,分析Dubbo怎么从配置转化成可被调用的服务。
以典型的服务端结合Spring配置为例:
<!-- 提供方应用信息,用于计算依赖关系 -->
<dubbo:application name="demo-provider"/>
<!-- 用dubbo协议在20880端口暴露服务 -->
<dubbo:protocol name="dubbo" port="20880"/>
<!-- 使用zookeeper注册中心暴露服务地址 -->
<dubbo:registry address="zookeeper://127.0.0.1:1234" id="registry"/>
<!-- 默认的服务端配置 -->
<dubbo:provider registry="registry" retries="0" timeout="5000"/>
<!-- 和本地bean一样实现服务 -->
<bean id="demoService" class="com.alibaba.dubbo.demo.provider.DemoServiceImpl"/>
<!-- 声明需要暴露的服务接口 -->
<dubbo:service interface="com.alibaba.dubbo.demo.DemoService" ref="demoService"/>
在Dubbo命名空间下定义了一系列XML节点,如:application、protocol、registry、provider、service 等,Dubbo通过实现Spring提供的 NamespaceHandler 接口,向Spring注册 BeanDefinition 解析器,使Spring能识别Dubbo命名空间下的节点,并且通过实现 BeanDefinitionParser 接口,使Spring能解析各节点的具体配置。
DubboNamespaceHandler#init() ,源码如下:
public void init() {
registerBeanDefinitionParser("application", new DubboBeanDefinitionParser(ApplicationConfig.class, true));
registerBeanDefinitionParser("module", new DubboBeanDefinitionParser(ModuleConfig.class, true));
registerBeanDefinitionParser("registry", new DubboBeanDefinitionParser(RegistryConfig.class, true));
registerBeanDefinitionParser("monitor", new DubboBeanDefinitionParser(MonitorConfig.class, true));
registerBeanDefinitionParser("provider", new DubboBeanDefinitionParser(ProviderConfig.class, true));
registerBeanDefinitionParser("consumer", new DubboBeanDefinitionParser(ConsumerConfig.class, true));
registerBeanDefinitionParser("protocol", new DubboBeanDefinitionParser(ProtocolConfig.class, true));
registerBeanDefinitionParser("service", new DubboBeanDefinitionParser(ServiceBean.class, true));
registerBeanDefinitionParser("reference", new DubboBeanDefinitionParser(ReferenceBean.class, false));
registerBeanDefinitionParser("annotation", new DubboBeanDefinitionParser(AnnotationBean.class, true));
}
由以上代码可以看出,各个节点最终被转化为各种Bean,配置的各种属性也被转化为Bean的属性。从Bean的类型可以看出,大部分Bean只用于提供Dubbo的运行参数,只有 ServiceBean 才是本文服务发布分析入口。
1 ServiceBean 核心入口
Dubbo服务提供者由 dubbo:service 来定义的,从前面可以看到,Spring把 dubbo:service 解析成一个ServiceBean,ServiceBean实现了 ApplicationListener 和 InitializingBean 接口,ServiceBean有个核心方法 export,在这个方法中初始化服务提供者并且暴露远程服务。这个方法在bean初始化或容器中所有bean刷新完毕时被调用,根据 provider 的延迟设置决定,如果设置了延迟( delay 属性)则在bean初始化结束之后调用,否则在刷新事件中被调用,默认会延迟 export,即在所有bean的刷新结束被调用。
在 com.alibaba.dubbo.config.spring.ServiceBean 类,源码如下:
public class ServiceBean<T> extends ServiceConfig<T> implements InitializingBean, DisposableBean, ApplicationContextAware, ApplicationListener, BeanNameAware {
...
public void afterPropertiesSet() {}
...
public void onApplicationEvent(ApplicationEvent event) {}
...
public void destroy() {}
}
ServiceBean 实现了Spring的 InitializingBean、DisposableBean、 ApplicationListener 等接口,实现了 afterPropertiesSet()、 destroy()、 onApplicationEvent() 等典型方法,这里便是Dubbo和Spring整合的关键,一般第三方框架基本都是通过这几个接口和Spring整合的。
afterPropertiesSet() 主要用来注入各种 ConfigBean,便于服务注册过程中各种参数的获取,注意看最后关于延迟发布的几行代码,大意是如果不延迟,就立即注册和暴露服务。
ServiceBean#afterPropertiesSet(),源码如下:
public void afterPropertiesSet() throws Exception {
// @ step1
if (getProvider() == null) {
// BeanFactoryUtils.beansOfTypeIncludingAncestors 究竟做了什么?
// 返回指定类型和子类型的所有bean,若该bean factory 是一个继承类型的beanFactory,这个方法也会获取祖宗factory中定义的指定类型的bean。
Map<String, ProviderConfig> providerConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ProviderConfig.class, false, false);
if (providerConfigMap != null && providerConfigMap.size() > 0) {
Map<String, ProtocolConfig> protocolConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ProtocolConfig.class, false, false);
if ((protocolConfigMap == null || protocolConfigMap.size() == 0)
&& providerConfigMap.size() > 1) { // backward compatibility
List<ProviderConfig> providerConfigs = new ArrayList<ProviderConfig>();
for (ProviderConfig config : providerConfigMap.values()) {
if (config.isDefault() != null && config.isDefault()) {
providerConfigs.add(config);
}
}
if (!providerConfigs.isEmpty()) {
setProviders(providerConfigs);
}
} else {
ProviderConfig providerConfig = null;
for (ProviderConfig config : providerConfigMap.values()) {
if (config.isDefault() == null || config.isDefault()) {
if (providerConfig != null) {
throw new IllegalStateException("Duplicate provider configs: " + providerConfig + " and " + config);
}
providerConfig = config;
}
}
if (providerConfig != null) {
setProvider(providerConfig);
}
}
}
}
// @ step2
if (getApplication() == null
&& (getProvider() == null || getProvider().getApplication() == null)) {
Map<String, ApplicationConfig> applicationConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ApplicationConfig.class, false, false);
if (applicationConfigMap != null && applicationConfigMap.size() > 0) {
ApplicationConfig applicationConfig = null;
for (ApplicationConfig config : applicationConfigMap.values()) {
if (config.isDefault() == null || config.isDefault()) {
if (applicationConfig != null) {
throw new IllegalStateException("Duplicate application configs: " + applicationConfig + " and " + config);
}
applicationConfig = config;
}
}
if (applicationConfig != null) {
setApplication(applicationConfig);
}
}
}
// @ step3
if (getModule() == null
&& (getProvider() == null || getProvider().getModule() == null)) {
Map<String, ModuleConfig> moduleConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ModuleConfig.class, false, false);
if (moduleConfigMap != null && moduleConfigMap.size() > 0) {
ModuleConfig moduleConfig = null;
for (ModuleConfig config : moduleConfigMap.values()) {
if (config.isDefault() == null || config.isDefault()) {
if (moduleConfig != null) {
throw new IllegalStateException("Duplicate module configs: " + moduleConfig + " and " + config);
}
moduleConfig = config;
}
}
if (moduleConfig != null) {
setModule(moduleConfig);
}
}
}
// @ step4
if ((getRegistries() == null || getRegistries().isEmpty())
&& (getProvider() == null || getProvider().getRegistries() == null || getProvider().getRegistries().isEmpty())
&& (getApplication() == null || getApplication().getRegistries() == null || getApplication().getRegistries().isEmpty())) {
Map<String, RegistryConfig> registryConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, RegistryConfig.class, false, false);
if (registryConfigMap != null && registryConfigMap.size() > 0) {
List<RegistryConfig> registryConfigs = new ArrayList<RegistryConfig>();
for (RegistryConfig config : registryConfigMap.values()) {
if (config.isDefault() == null || config.isDefault()) {
registryConfigs.add(config);
}
}
if (!registryConfigs.isEmpty()) {
super.setRegistries(registryConfigs);
}
}
}
// @ step5
if (getMonitor() == null
&& (getProvider() == null || getProvider().getMonitor() == null)
&& (getApplication() == null || getApplication().getMonitor() == null)) {
Map<String, MonitorConfig> monitorConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, MonitorConfig.class, false, false);
if (monitorConfigMap != null && monitorConfigMap.size() > 0) {
MonitorConfig monitorConfig = null;
for (MonitorConfig config : monitorConfigMap.values()) {
if (config.isDefault() == null || config.isDefault()) {
if (monitorConfig != null) {
throw new IllegalStateException("Duplicate monitor configs: " + monitorConfig + " and " + config);
}
monitorConfig = config;
}
}
if (monitorConfig != null) {
setMonitor(monitorConfig);
}
}
}
// @ step6
if ((getProtocols() == null || getProtocols().isEmpty())
&& (getProvider() == null || getProvider().getProtocols() == null || getProvider().getProtocols().isEmpty())) {
Map<String, ProtocolConfig> protocolConfigMap = applicationContext == null ? null : BeanFactoryUtils.beansOfTypeIncludingAncestors(applicationContext, ProtocolConfig.class, false, false);
if (protocolConfigMap != null && protocolConfigMap.size() > 0) {
List<ProtocolConfig> protocolConfigs = new ArrayList<ProtocolConfig>();
for (ProtocolConfig config : protocolConfigMap.values()) {
if (config.isDefault() == null || config.isDefault()) {
protocolConfigs.add(config);
}
}
if (!protocolConfigs.isEmpty()) {
super.setProtocols(protocolConfigs);
}
}
}
// @ step7
if (getPath() == null || getPath().length() == 0) {
if (beanName != null && beanName.length() > 0
&& getInterface() != null && getInterface().length() > 0
&& beanName.startsWith(getInterface())) {
setPath(beanName);
}
}
// @ step8
if (!isDelay()) {
export();
}
}
Step1:如果 provider 为空,说明 dubbo:service 标签未设置 provider 属性,则尝试从 BeanFactory 中查询 dubbo:provider 实例,如果存在一个 dubbo:provider 标签,则取该实例,如果存在多个 dubbo:provider 配置则 provider 属性不能为空,否则抛出异常:“Duplicate provider configs”。
Step2:如果 application 为空,说明 dubbo:service 标签未设置 application 属性,则尝试从 BeanFactory 中查询 dubbo:application 实例,如果存在一个 dubbo:application 标签,则取该实例,如果存在多个 dubbo:application 配置,则抛出异常:“Duplicate application configs”。
Step3:如果 module 为空,说明 dubbo:service 标签未设置 module 属性,则尝试从 BeanFactory 中查询 dubbo:module 实例,如果存在一个 dubbo:module 标签,则取该实例,如果存在多个 dubbo:module,则抛出异常:“Duplicate module configs”。
Step4:(逻辑同上)尝试从 BeanFactory 中加载所有的注册中心,注意 ServiceBean 的 List registries 属性,为注册中心集合。
Step5:(逻辑同上)尝试从 BeanFacotry 中加载一个监控中心,填充 ServiceBean 的 MonitorConfig monitor 属性,如果存在多个 dubbo:monitor 配置,则抛出”Duplicate monitor configs: “。
Step6:(逻辑同上)尝试从 BeanFactory 中加载所有的协议,注意: ServiceBean 的 List protocols 是一个集合,也即一个服务可以通过多种协议暴露给消费者。
Step7:(逻辑同上)设置 ServiceBean 的 path 属性,path 属性存放的是 dubbo:service 的 beanName(dubbo:service id)。
Step8:如果为启用延迟暴露机制,则调用 export 暴露服务。首先看一下 isDelay 的实现,然后重点分析 export 的实现原理(服务暴露的整个实现原理)。
ServiceBean#isDelay(),源码如下:
private boolean isDelay() {
Integer delay = getDelay();
ProviderConfig provider = getProvider();
if (delay == null && provider != null) {
delay = provider.getDelay();
}
return supportedApplicationListener && (delay == null || delay == -1);
}
先从 ServiceConfig 获取 delay 属性,如果为 null,则获取 ProviderConfig 的 delay 属性,最后如果还是 null 或配置为 -1 表示延迟暴露服务。可见Dubbo获取运行参数的层级,便于更精确化的配置各种参数。
通过 supportedApplicationListener 可以猜到服务延迟暴露是通过Spring容器的监听器触发的。个人更倾向于明确设置 delay=-1 或者所有层级都不配置,因为如果提早暴露服务,此时其他的Spring bean可能还未初始化完成,而暴露出去的服务大部分情况下依赖于Spring的其他bean来实现业务功能,如果提早接收到客户端的请求,难免会出现各种异常。
ServiceBean#onApplicationEvent(),源码如下:
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
if (isDelay() && !isExported() && !isUnexported()) {
if (logger.isInfoEnabled()) {
logger.info("The service ready on spring started. service: " + getInterface());
}
export();
}
}
如果有设置 dubbo:service 或 dubbo:provider 的属性 delay,或配置 delay 为 -1,都表示启用延迟机制,单位为毫秒,设置为 -1,表示等到Spring容器初始化后再暴露服务。
从这里也可以看出,Dubbo暴露服务的处理入口为:ServiceBean#export --> ServiceConfig#export。
2 ServiceConfig 暴露服务
从前一节代码分析可知,最后一步是调用 ServiceBean 的父类 ServiceConfig#export方法暴露服务。
2.1 第一步:ServiceConfig#export 暴露服务
调用链:ServiceBean#afterPropertiesSet --> ServiceConfig#export
public synchronized void export() {
if (provider != null) {
if (export == null) {
export = provider.getExport();
}
if (delay == null) {
delay = provider.getDelay();
}
}
if (export != null && !export) { // @ step1
return;
}
if (delay != null && delay > 0) { // @ step2
delayExportExecutor.schedule(new Runnable() {
@Override
public void run() {
doExport();
}
}, delay, TimeUnit.MILLISECONDS);
} else {
doExport(); // @ step3
}
}
Step1:判断是否暴露服务,由 dubbo:service export=“true|false” 来指定。
Step2:如果启用了 delay 机制,如果 delay 大于0,表示延迟多少毫秒后暴露服务,使用 ScheduledExecutorService 延迟调度,最终调用 doExport 方法。
Step3:执行具体的暴露逻辑 doExport,需要大家留意:delay=-1 的处理逻辑( 基于Spring事件机制触发 )。
2.2 第二步:ServiceConfig#doExport 暴露服务
调用链:ServiceBean#afterPropertiesSet --> ServiceConfig#export --> ServiceConfig#doExport
protected synchronized void doExport() {
if (unexported) {
throw new IllegalStateException("Already unexported!");
}
if (exported) {
return;
}
exported = true;
if (interfaceName == null || interfaceName.length() == 0) {
throw new IllegalStateException("<dubbo:service interface=\"\" /> interface not allow null!");
}
checkDefault(); // @ step1
if (provider != null) {
if (application == null) {
application = provider.getApplication();
}
if (module == null) {
module = provider.getModule();
}
if (registries == null) {
registries = provider.getRegistries();
}
if (monitor == null) {
monitor = provider.getMonitor();
}
if (protocols == null) {
protocols = provider.getProtocols();
}
}
if (module != null) {
if (registries == null) {
registries = module.getRegistries();
}
if (monitor == null) {
monitor = module.getMonitor();
}
}
if (application != null) {
if (registries == null) {
registries = application.getRegistries();
}
if (monitor == null) {
monitor = application.getMonitor();
}
}
if (ref instanceof GenericService) { // @ step2
interfaceClass = GenericService.class;
if (StringUtils.isEmpty(generic)) {
generic = Boolean.TRUE.toString();
}
} else {
try {
interfaceClass = Class.forName(interfaceName, true, Thread.currentThread()
.getContextClassLoader());
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
checkInterfaceAndMethods(interfaceClass, methods);
checkRef();
generic = Boolean.FALSE.toString();
}
if (local != null) { // @ step3
if ("true".equals(local)) {
local = interfaceName + "Local";
}
Class<?> localClass;
try {
localClass = ClassHelper.forNameWithThreadContextClassLoader(local);
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
if (!interfaceClass.isAssignableFrom(localClass)) {
throw new IllegalStateException("The local implementation class " + localClass.getName() + " not implement interface " + interfaceName);
}
}
if (stub != null) { // @ step4
if ("true".equals(stub)) {
stub = interfaceName + "Stub";
}
Class<?> stubClass;
try {
stubClass = ClassHelper.forNameWithThreadContextClassLoader(stub);
} catch (ClassNotFoundException e) {
throw new IllegalStateException(e.getMessage(), e);
}
if (!interfaceClass.isAssignableFrom(stubClass)) {
throw new IllegalStateException("The stub implementation class " + stubClass.getName() + " not implement interface " + interfaceName);
}
}
checkApplication(); // @ step5
checkRegistry();
checkProtocol();
appendProperties(this);
checkStubAndMock(interfaceClass); // @ step6
if (path == null || path.length() == 0) {
path = interfaceName;
}
doExportUrls(); // @ step7
ProviderModel providerModel = new ProviderModel(getUniqueServiceName(), ref, interfaceClass); // @ step8
ApplicationModel.initProviderModel(getUniqueServiceName(), providerModel);
}
private void checkDefault() { // @ step1
if (provider == null) {
provider = new ProviderConfig();
}
appendProperties(provider);
}
Step1:如果 dubbo:servce 标签也就是 ServiceBean 的 provider 属性为空,调用 appendProperties 方法,填充默认属性,其具体加载顺序:
1. 从系统属性加载对应参数值,参数键:dubbo.provider.属性名,System.getProperty。
2. 从属性配置文件加载对应参数值,可通过系统属性指定属性配置文件: dubbo.properties.file,如果该值未配置,则默认取 dubbo.properties 属性配置文件。
Step2:校验 ref 与 interface 属性。如果 ref 是 GenericService,则为Dubbo的泛化实现,然后验证 interface 接口与 ref 引用的类型是否一致。
Step3:dubbo:service local机制,已经废弃,被 stub属性所替换。
Step4:处理本地存根 Stub。
Step5:校验 ServiceBean 的 application、registry、protocol 是否为空,并从系统属性(优先)、资源文件中填充其属性。系统属性、资源文件属性的配置如下:
application dubbo.application.属性名,例如 dubbo.application.name
registry dubbo.registry.属性名,例如 dubbo.registry.address
protocol dubbo.protocol.属性名,例如 dubbo.protocol.port
service dubbo.service.属性名,例如 dubbo.service.stub
Step6:校验 stub、mock 类的合理性,是否是 interface 的实现类。
Step7:执行 doExportUrls() 方法暴露服务,接下来会重点分析该方法。
Step8:将服务提供者信息注册到 ApplicationModel 实例中。
2.3 第三步:ServiceConfig#doExportUrls 暴露服务
调用链:ServiceBean#afterPropertiesSet --> ServiceConfig#export --> ServiceConfig#doExport --> ServiceConfig#doExportUrls
private void doExportUrls() {
List<URL> registryURLs = loadRegistries(true); // @ step1
for (ProtocolConfig protocolConfig : protocols) {
doExportUrlsFor1Protocol(protocolConfig, registryURLs); // @ step2
}
}
Step1:首先遍历 ServiceBean 的 List registries(所有注册中心的配置信息),然后将地址封装成URL对象,关于注册中心的所有配置属性,最终转换成url的属性(?属性名=属性值),loadRegistries(true),参数的意思:true,代表服务提供者,false:代表服务消费者,如果是服务提供者,则检测注册中心的配置,如果配置了 register=“false”,则忽略该地址,如果是服务消费者,并配置了 subscribe=“false” 则表示不从该注册中心订阅服务,故也不返回。
Step2:然后遍历配置的所有协议,根据每个协议,向注册中心暴露服务,接下来重点分析 doExportUrlsFor1Protocol 方法的实现细节。
所以,从上面代码,可以看出 Dubbo同一个服务支持多种服务协议、支持向多种注册中心注册,很方便同一功能由各种不同实现方式的客户端调用。
最近整理了java架构文档和学习笔记文件以及架构视频资料和高清架构进阶学习导图免费分享给大家(包括Dubbo、Redis、Netty、zookeeper、Spring cloud、分布式、高并发等架构技术资料),希望能帮助到您面试前的复习且找到一个好的工作,也节省大家在网上搜索资料的时间来学习,也可以关注我一下以后会有更多干货分享。
添加小助手VX:xuanwo008 领取资料备注好“头条”信息!
相关推荐
- 一个简单便捷搭建个人知识库的开源项目(MDwiki)
-
这里我通过自动翻译软件,搬运总结MDwiki官网的部署和使用方法。第一步:下载编译好的后MDwiki文件,只有一个HTML文件“mdwiki.html”。第二步:在mdwiki.html同级目录创建“...
- 强大、简洁、快速、持续更新 PandaWiki新一代 AI 驱动的开源知识库
-
PandaWiki是什么PandaWiki是一款AI大模型驱动的开源知识库搭建系统,帮助你快速构建智能化的产品文档、技术文档、FAQ、博客系统,借助大模型的力量为你提供AI创作、AI问答...
- DeepWiki-Open: 开源版Deepwiki,可自己构建github文档库
-
Deepwiki是Devin团队开发的github文档库,用户能免费使用,但代码不是开源,而DeepWiki-Open侧是开源版本的实现。DeepWiki-Open旨在为GitHub和GitLa...
- 最近爆火的wiki知识管理开源项目PandaWiki
-
项目介绍PandaWiki是一款AI大模型驱动的开源知识库搭建系统,帮助你快速构建智能化的产品文档、技术文档、FAQ、博客系统,借助大模型的力量为你提供AI创作、AI问答、AI搜索等...
- 轻量级开源wiki系统介绍(轻量开源论坛系统)
-
wiki系统有很多DokuWiki、MediaWiki、MinDoc等等都是开源的wiki系统。商业版的wiki,像很多企业在用的confluence等。今天我们讲的是一款轻量级且开源的文档管理系统:...
- DNS解析错误要怎么处理(dns解析状态异常怎么办)
-
在互联网时代,网络已经成为人们生活和工作中不可或缺的一部分。然而,当遇到DNS解析错误时,原本畅通无阻的网络访问会突然陷入困境,让人感到十分困扰。DNS,即域名系统,它如同互联网的电话簿,将人们易于...
- 网页加载慢?这些方法让你秒开网页!
-
打开浏览器,信心满满地准备查资料、看视频或者追剧,却发现网页怎么都打不开!是不是瞬间感觉手足无措?别慌,这个问题其实挺常见,而且解决起来并没有你想象的那么复杂。今天就来聊聊网页打不开究竟是怎么回事,以...
- windows11 常用CMD命令大全(windows11msdn)
-
Windows11中的命令提示符(CMD)是一个强大的工具,可以通过命令行执行各种系统操作和管理任务。以下是一些常用的CMD命令,按功能分类整理,供你参考:一、系统信息与状态systeminfo显...
- 电脑提示DNS服务器未响应怎么解决?
-
我们在使用电脑的时候经常会遇到各种各样的网络问题,例如最近就有Win11电脑用户在使用的时候遇到了DNS未响应的问题,遇到这种情况我们应该怎么解决呢? 方法一:刷新DNS缓存 1、打开运行(W...
- 宽带拨号错误 651 全解析:故障定位与修复方案
-
在使用PPPoE拨号连接互联网时,错误651提示「调制解调器或其他连接设备报告错误」,通常表明从用户终端到运营商机房的链路中存在异常。以下从硬件、系统、网络三层维度展开排查:一、故障成因分类图...
- 如何正确清除 DNS 缓存吗?(解决你访问延时 )
-
DNS缓存是一个临时数据库,用于存储有关以前的DNS查找的信息。换句话说,每当你访问网站时,你的操作系统和网络浏览器都会保留该域和相应IP地址的记录。这消除了对远程DNS服务器重复查询的...
- 网络配置命令:ipconfig和ifconfig,两者有啥区别?
-
在计算机网络的世界里,网络接口就像是连接你电脑和外部网络的桥梁,而网络配置则是确保这座桥梁稳固、通信顺畅的关键。提到网络配置工具,ipconfig和ifconfig绝对是两个绕不开的名字。它们一...
- 救急的命令 你会几个?(救急一下)
-
很多人都说小编是注册表狂魔,其实不完全是,小编常用的命令行才是重点。其实所谓的命令行都是当初DOS时代的标准操作方式,随着Windows不断演化,DOS的命令早已成为Windows的一部分了——开始菜...
- 电脑有网却访问不了GitHub原来是这样
-
当满心欢喜打开电脑,准备在GitHub这个“开源宝藏库”里挖掘点超酷的项目,却遭遇了网页无法访问的尴尬。看着屏幕上那令人无奈的提示,原本高涨的热情瞬间被泼了一盆冷水,是不是感觉世界都不美好了...
- rockstargames更新慢| r星更新速度 怎么办 解决办法
-
rockstargames更新慢|r星更新速度怎么办解决办法说到RockstarGames,那可是游戏界的大佬,作品个顶个的经典。但话说回来,每当新内容更新时,那蜗牛般的下载速度,真是让人急得...
你 发表评论:
欢迎- 一周热门
- 最近发表
- 标签列表
-
- maven镜像 (69)
- undefined reference to (60)
- zip格式 (63)
- oracle over (62)
- date_format函数用法 (67)
- 在线代理服务器 (60)
- shell 字符串比较 (74)
- x509证书 (61)
- localhost (65)
- java.awt.headless (66)
- syn_sent (64)
- settings.xml (59)
- 弹出窗口 (56)
- applicationcontextaware (72)
- my.cnf (73)
- httpsession (62)
- pkcs7 (62)
- session cookie (63)
- java 生成uuid (58)
- could not initialize class (58)
- beanpropertyrowmapper (58)
- word空格下划线不显示 (73)
- jar文件 (60)
- jsp内置对象 (58)
- makefile编写规则 (58)