开发者博客 – IT技术 尽在开发者博客

开发者博客 – 科技是第一生产力


  • 首页

  • 归档

  • 搜索

OkHttp3工具

发表于 2021-11-13

「这是我参与11月更文挑战的第11天,活动详情查看:2021最后一次更文挑战」

OkHttp:An HTTP+HTTP/2 client for Android and Java applications.

  1. 基础知识

1.1 简单介绍

OkHttp是一个比较火的Http连接工具三方库,可以说是为高效而生,使用时也是比较简洁方便。

当下普遍使用的是OkHttp3,此版本与之前的OkHttp版本相比有了较大的改进,文章内容的实现都是基于OkHttp3版本。

官网地址

1.2 引入依赖

使用OkHttp需要引入相关的maven依赖信息:

1
2
3
4
5
6
xml复制代码<!--OkHttp3-->
<dependency>
   <groupId>com.squareup.okhttp3</groupId>
   <artifactId>okhttp</artifactId>
   <version>3.10.0</version>
</dependency>
  1. 常用类对象

2.1 OkHttpClient对象

使用HTTP工具时,总是少不了创建Client对象来完成相关功能,OkHttp3中需要创建的是一个OkHttpClient对象,创建时可以直接使用构造函数来创建对象;或者使用OkHttp3提供的建造者模式来创建并返回一个对象。

1
2
3
4
5
java复制代码//①使用构造函数初始化
OkHttpClient okHttpClient = new OkHttpClient();
​
//②使用建造者模式创建并返回对象
OkHttpClient okHttpClient = new OkHttpClient.Builder().build();

两种对象的创建方法本质上是相通的,

  1. 使用构造函数创建时,会默认创建一个建造者对象并作为参数传入,以此来创建OkHttpClient对象;
  2. 使用建造者模式创建时,也是首先创建一个建造者对象,并调用build()方法把自身传入来返回OkHttpClient对象。
1
2
3
4
5
java复制代码//①使用构造函数
public OkHttpClient() { this(new OkHttpClient.Builder());   }
​
//②使用建造者模式
public OkHttpClient build() { return new OkHttpClient(this); }

2.2 Request对象

Request对象是进行HTTP请求时的请求对象,该类在OkHttp3中同样是使用了建造者模式的思想来创建一个请求对象,创建时操作的是Request类中的Builder内部类,并通过Builder内部类将属性传递给Request对象,创建时默认请求类型是GET请求。

1
2
java复制代码//建造者模式创建请求对象,默认是GET请求
Request request = new Request.Builder().build();

如果想要自定义请求类型,Request类中提供了method()方法可以指定请求类型,同时对于常用的请求方式还做了进一步封装,如可以调用.post()方法以POST类型发送请求。

1
2
3
4
5
6
7
8
9
10
11
12
java复制代码//method方法设置请求方式
public Request.Builder method(String method, @Nullable RequestBody body){
  ...
}
​
//封装的get、post方法,调用即可使用相应请求类型
public Request.Builder get() {
   return this.method("GET", (RequestBody)null);
}
public Request.Builder post(RequestBody body) {
   return this.method("POST", body);
}

指定请求路径可以使用Request对象的url()方法,该方法可以传入一个字符串或URL对象。

1
2
3
4
5
6
7
8
9
java复制代码//通过url方法对建造者对象的url属性赋值,其他方法都是间接调用该方法
public Request.Builder url(HttpUrl url) {
   if (url == null) {
       throw new NullPointerException("url == null");
  } else {
       this.url = url;
       return this;
  }
}

除了设置请求类型和路径的方法之外,Request类中还提供了设置请求头部信息的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码//设置请求头
public Request.Builder header(String name, String value) {
   this.headers.set(name, value);
   return this;
}
//追加请求头信息
public Request.Builder addHeader(String name, String value) {
   this.headers.add(name, value);
   return this;
}
//通过头部对象设置请求头信息
public Request.Builder headers(Headers headers) {
   this.headers = headers.newBuilder();
   return this;
}

2.3 Response对象

Response对象是HTTP请求后的响应对象,Response类在OkHttp3中同样是使用了建造者模式。

在HTTP的响应Response对象中,常用的操作就是从响应结果中获取响应数据,Response对象中提供了body()方法来获取响应结果中的响应体内容。

1
2
3
4
5
java复制代码//获取相应体
@Nullable
public ResponseBody body() {
   return this.body;
}
  1. 请求流程

3.1 GET请求

使用OkHttp工具进行GET请求流程如下

  1. 创建OkHttpClient对象
  2. 创建Request请求对象,并设置请求类型、请求路径、参数等信息
  3. 使用OkHttpClient对象执行Request请求对象,并使用Response对象接收返回结果
  4. 从返回结果中获取解析返回数据

代码实现流程可以表示为:

1
2
3
4
5
6
7
8
java复制代码//①创建OkHttpClient对象
OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
//②创建Request请求对象
Request request = new Request.Builder().get().url(url).build();
//③执行请求,获取返回结果
Response response = okHttpClient.newCall(request).execute();
//④获取返回数据
response.body().toString();

3.2 POST请求

POST请求与GET相比,大多数时候都会提交一定的数据信息,流程可以表示为

  1. 创建OkHttpClient对象
  2. 创建请求体对象信息
  3. 创建Request请求对象,并设置请求类型为post并携带需要提交的请求体信息、请求路径、头部参数等信息
  4. 使用OkHttpClient对象执行Request请求对象,并使用Response对象接收返回结果
  5. 从返回结果中获取解析返回数据

具体实现代码为:

1
2
3
4
5
6
7
8
9
10
java复制代码//①创建OkHttpClient对象
OkHttpClient okHttpClient = new OkHttpClient.Builder().build();
//②创建请求体信息
RequestBody requestBody = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), json)
//③创建Request请求对象,post类型并传入请求体
Request request = new Request.Builder().post(requestBody).url(url).build();
//④执行请求,获取返回结果
Response response = okHttpClient.newCall(request).execute();
//⑤获取返回数据
response.body().toString();

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

操作数据库和数据表 MySQL(一)基础

发表于 2021-11-13

「这是我参与11月更文挑战的第11天,活动详情查看:2021最后一次更文挑战」


相关文章

MySQL:MySQL系列


前言:

MySQL是一个关系型数据库管理系统,是目前最好的 RDBMS (Relational Database Management System,关系数据库管理系统) 应用软件之一。

  • 优点:
    • ①开源的数据库软件,关键是免费的!
    • ②体积小,速度快,总体拥有成本低,招人成本比较低。
    • ③一般中小型网站的开发都选择 MySQL 作为网站数据库。

1、数据库

1.1 基本的操作

1.创建数据库

1
sql复制代码CREATE DATABASE IF NOT EXISTS testDataBase;

2.删除数据库

1
sql复制代码DROP DATABASE IF EXISTS testDataBase;

3.使用数据库

1
sql复制代码USE school;

4.查看数据库

1
sql复制代码SHOW DATABASE;

1.2 数据库的列类型

①数值:

1
2
3
4
5
6
7
8
bash复制代码tinyint 	十分小的数据 1个字节
smallint 较小的数据 2个字节
mediumint 中等大小 3个字节
int 标准的整数 4个字节(常用)
bigint 较大的数据 8个字节
float 浮点数 4个字节
double 浮点数 8个字节 (精度问题)
decimal 字符串形式的浮点数,一般用于金融计算的时候

②字符串:

1
2
3
4
bash复制代码char 		字符串固定大小 0-255
varchar 可变字符串 0-65535(常用)
tinytext 微型文本 2^8-1
text 文本串 2^16-1 (保存大文本)

③时间日期:

1
2
3
4
5
bash复制代码date 		YYYY-MM-DD,日期
time HH:mm:ss 时间格式
datetime YYYY-MM-DD HH:mm:ss 最常用的时间格式
timestamp 时间戳 1970.1.1到现在的毫秒数
year 年份表示

④null:

1
2
bash复制代码没有值,未知
注意,不要使用null进行运算,结果为null

1.3 数据库的字段类型

①unsigened:

1
2
bash复制代码无符号的整数
声明该列不能声明负数

②zerofill:

1
2
bash复制代码0填充的
10的长度 1 – 0000000001 不足位数用0 填充

③自增:

1
2
3
bash复制代码通常理解为自增,自动在上一条记录的基础上+1
通常用来设计唯一的主键 index,必须是整数类似
可以自定义设置主键自增的起始值和步长

④非空 NULL not Null

1
2
bash复制代码假设设置为 not null,如何不给他赋值,就会报错
NULL 如果不填写,默认为NULL

⑤默认:

1
bash复制代码设置默认的值!

1.4创建表

①创建:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
sql复制代码--目标:创建一个schoo1数据库
--创建学生表(列,字段)使用SQL 创建
--学号int 登录密码varchar(20)姓名,性别varchar(2),出生日期(datatime),家庭住址,emai1--注意点,使用英文(),表的名称和字段尽量使用括起来
-- AUTO_ INCREMENT 自增
--字符串使用单引号括起来!
--所有的语句后面加,(英文的),最后一个不用加
-- PRIMARY KEY 主键,一般- 一个表只有一个唯一 -的主键!

CREATE DATABASE school
CREATE TABLE IF NOT EXISTS `student` (
`id` INT(4) NOT NULL AUTO_INCREMENT COMMENT '学号',
`name` VARCHAR(30) NOT NULL DEFAULT '匿名' COMMENT '姓名',
`pwd` VARCHAR(20) NOT NULL DEFAULT '123456' COMMENT '密码',
`sex` VARCHAR(2) NOT NULL DEFAULT '男' COMMENT '性别',
`birthday` DATETIME DEFAULT NULL COMMENT '出生日期',
`address` VARCHAR(100) DEFAULT NULL COMMENT '家庭住址',
`email` VARCHAR(50) DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY (`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8

②格式:

1
2
3
4
5
6
sql复制代码CREATE TABLE [IF NOT EXISTS] `表名`(
`字段名` 列类型[属性][索引][注释],
`字段名` 列类型[属性][索引][注释],
...
`字段名` 列类型[属性][索引][注释]
)[表类型][表的字符集设置][注释]

1.5 数据表的类型

①INNODB和MYISAM:

MYISAM INNODB
事务支持 不支持 支持
数据行锁定 不支持 支持
外键约束 不支持 支持
全文索引 支持 不支持
表空间的大小 较小 较大,约为MYISAM的两倍

注意的是:

  • 在MySQL5.6版本以后INNODB也支持了 全文检索,但是只支持英文的检索。有兴趣的小伙伴可以自己去了解下两种表的类型底层存储方式的区别,在此我不多讲。
    常规使用操作:
  • MYISAM 节约空间,速度较快,
  • INNODB 安全性高,事务处理,多表多用户操作

②在物理空间存在的位置:

所有的数据库文件都存在data目录下,一个文件夹就对应一个数据库

本质还是文件的存储

MySQL 引擎在物理文件上的区别

  • innoDB 在数据库表中,只有一个*.frm文件,以及上级目录下的ibdata1文件
  • MYISAM 对应的文件
    • *.frm - 表结构的定义文件
    • *. MYD -数据文件
    • *.MYI 索引文件

③ 设置数据库字符集编码:

1
2
3
sql复制代码CHARTSET=UTF8

12

不设置的话,会是mysql默认的字符集编码-(不支持中文)

可以在my.ini中配置默认的编码

1
sql复制代码character-set-server=utf8

1.5修改删除表

①修改:

1
2
3
4
5
6
7
8
9
10
sql复制代码-- 修改表名 ALTER TABLE 旧表面 AS 新表名
ALTER TABLE student RENAME AS student1
-- 增加表的字段 ALTER TABLE 表名 ADD 字段名 列属性
ALTER TABLE student1 ADD age INT(11)
-- 修改表的字段(重命名,修改约束)
ALTER TABLE student1 MODIFY age VARCHAR(11) -- 修改约束
ALTER TABLE student1 CHANGE age age1 INT(1) -- 字段重命名

-- 删除表的字段
ALTER TABLE student1 DROP age1

② 删除:

1
2
sql复制代码-- 删除表
DROP TABLE IF EXISTS student1

所有的创建和删除操作尽量加上判断,以免报错

③注意点:

  • `` 字段名,使用这个包裹
  • 注释 – /**/
  • sql 关键字大小写不敏感,建议写小写
  • 所有的符号全部用英文

路漫漫其修远兮,吾必将上下求索~

如果你认为i博主写的不错!写作不易,请点赞、关注、评论给博主一个鼓励吧~hahah

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Java多线程_提高锁性能的方法 Java提高锁性能的方法

发表于 2021-11-13

「这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战」

Java提高锁性能的方法

减少锁持有时间

对于使用锁进行并发控制的应用程序来说,如果单个线程特有锁的时间过长,会导致锁的竞争更加激烈,会影响系统的性能.在程序中需要尽可能减少线程对锁的持有时间,如下面代码:

1
2
3
4
5
scss复制代码public synchronized void  syncMethod(){
othercode1();
mutexMethod();
othercode();
}

在syncMethod同步方法中,假设只有mutexMethod()方法是需要同步的, othercode1()方法与othercode2()方法不需要进行同步. 如果othercode1与othercode2这两个方法需要花费较长的CPU时间,在并发量较大的情况下,这种同步方案会导致等待线程的大量增加. 一个较好的优化方案是,只在必要时进行同步,可以减少锁的持有时间,提高系统的吞吐量,如把上面的代码改为:

1
2
3
4
5
6
7
scss复制代码public  void  syncMethod(){
othercode1();
synchronized (this) {
mutexMethod();
}
othercode();
}

只对mutexMethod()方法进行同步,这种减少锁持有时间有助于降低锁冲突的可能性,提升系统的并发能力。

减小锁的粒度

一个锁保护的共享数据的数量大小称为锁的粒度. 如果一个锁保护的共享数据的数量大就称该锁的粒度粗,否则称该锁的粒度细.锁的粒度过粗会导致线程在申请锁时需要进行不必要的等待.减少锁粒度是一种削弱多线程锁竞争的一种手段,可以提高系统的并发性。

在JDK7前,java.util.concurrent.ConcurrentHashMap类采用分段锁协议,可以提高程序的并发性。

使用读写分离锁代替独占锁

使用ReadWriteLock读写分离锁可以提高系统性能, 使用读写分离锁也是减小锁粒度的一种特殊情况. 第二条建议是能分割数据结构实现减小锁的粒度,那么读写锁是对系统功能点的分割。

在多数情况下都允许多个线程同时读,在写的使用采用独占锁,在读多写少的情况下,使用读写锁可以大大提高系统的并发能力。

锁分离

将读写锁的思想进一步延伸就是锁分离.读写锁是根据读写操作功能上的不同进行了锁分离.根据应用程序功能的特点,也可以对独占锁进行分离.如java.util.concurrent.LinkedBlockingQueue类中take()与put()方法分别从队头取数据,把数据添加到队尾. 虽然这两个方法都是对队列进行修改操作,由于操作的主体是链表,take()操作的是链表的头部,put()操作的是链表的尾部,两者并不冲突. 如果采用独占锁的话,这两个操作不能同时并发,在该类中就采用锁分离,take()取数据时有取锁, put()添加数据时有自己的添加锁,这样take()与put()相互独立实现了并发。

粗锁化

为了保证多线程间的有效并发,会要求每个线程持有锁的时间尽量短.但是凡事都有一个度,如果对同一个锁不断的进行请求,同步和释放,也会消耗系统资源.如:

1
2
3
4
5
6
7
8
typescript复制代码public void  method1(){
synchronized( lock ){
同步代码块1
}
synchronized( lock ){
同步代码块2
}
}

JVM在遇到一连串不断对同一个锁进行请求和释放操作时,会把所有的锁整合成对锁的一次请求,从而减少对锁的请求次数,这个操作叫锁的粗化,如上一段代码会整合为:

1
2
3
4
5
6
typescript复制代码public void  method1(){
synchronized( lock ){
同步代码块1
同步代码块2
}
}

在开发过程中,也应该有意识的在合理的场合进行锁的粗化,尤其在循环体内请求锁时,如:

1
2
3
scss复制代码for(int i = 0 ; i< 100; i++){
synchronized(lock){}
}

这种情况下,意味着每次循环都需要申请锁和释放锁,所以一种更合理的做法就是在循环外请求一次锁,如:

1
2
3
scss复制代码synchronized( lock ){
for(int i = 0 ; i< 100; i++){}
}

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

基于事件驱动编程设计

发表于 2021-11-13

「这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战」

事件驱动这个词我们开发人员经常能够听到。那么事件驱动到底代表了什么呢?简单来说就是当一件事情发生,会触发另一件事情的发生,就比如说我们下单商品,支付后会扣款,会改变订单状态,会触发短信通知,会提高会员积分,会通知仓库发货等等。

1 概念

事件驱动架构(Event-driven architecture,EDA)是一种主流的异步分发事件架构模式,常用于设计高度可拓展的应用。事件驱动架构中事件可传输于松散耦合的组件和服务之间。一个事件驱动的系统一般是由事件消费者和事件产生者组成。事件消费者向事件管理器订阅事件,事件产生者向事件管理器发布事件。当事件管理器从事件产生者那接收到一个事件时,事件管理者把这个事件转送给相应的事件消费者。如果这个事件消费者是不可用的,事件管理者将保留这个事件,一段间隔之后再次转送该事件消费者。

其实简单的说,事件驱动架构就是让我们的程序处于被动,由事件触发程序的运行。

2 事件驱动的设计案例

事件驱动的GUI

做过桌面程序的同学感觉肯定尤为强烈,比如我们C#的WinForm程序,就是有一个一个的组件,加一个一个的事件进行设计的,比如窗体加载事件,鼠标单击、双击事件等,当每一种事件发生都会触发一段程序,去进行事件的处理,比如窗体加载事件发生就会触发查询,单击保存事件触发保存数据逻辑等等。如下边的QQ音乐也是如此,上边列表会触发不同类型的查询,展示不同的页面,点击播放会触发播放音乐的操作,这些都是事件驱动。Web前端与其极为类似,也是监听响应的事件,当事件产生进行相应的处理。

image.png

3 事件驱动 实现

3.1 观察者模式

  • 观察者向事件管理器注册
  • 事件管理器监听到消息后通知对应的观察者执行对应的方法

image.png

具体实现:

  • Spring Events

Spring框架的事件机制就是典型的观察者模式

3.2 发布/订阅模式

  • 事件源向抽象的事件管理器中发布消息
  • 抽象的事件管理器会将消息放入对应的topic
  • 订阅了对应topic的接受者接收处理消息

image.png

具体实现:

  • Guava EventBus事件总线image.png
  • 消息队列

消息队列也是基于发布订阅这种思想建立的

  1. 事件驱动的特点

优点:

  • 松耦合;
  • 支持异步;
  • 支持重用,容易并发处理;
  • 有良好的扩展性;

缺点:

  • 削弱对系统的控制能力;
  • 数据交换问题;
  • 系统复杂度上升,排查问题更复杂;

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

RequestParam和PathVariable的区别

发表于 2021-11-13

「这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战」。


  温馨提示: 本文总共1200字,阅读完大概需要1-3分钟,希望阅读本文能够对您有所帮助,如果阅读过程中有什么好的建议、看法,欢迎在文章下方留言或者私信我,您的意见对我非常宝贵。

一: 定义

  1、@RequestParam注解作用:

  获取URL中携带的请求参数的值既URL中“?”后携带的参数,传递参数的格式是:key=value

  如: https://localhost/requestParam/test?key1=value1&key2=value2…

  2、@PathVariable注解作用:

  用于获取URL中路径的参数值,参数名由RequestMapping注解请求路径时指定,常用于restful风格的api中,传递参数格式:直接在url后添加需要传递的值即可

  如: https://localhost/pathVariable/test/value1/value2…

二: 语法

  1、 RequestParam使用案例: @RequestParam(value = “param”,required = false,defaultValue = “test”)String param

  2、参数解析:

  value/name: URL中需要获取的参数名称

  required: true/false,为true时,url中必须携带这个参数(否则会出现: Required String parameter XXX is not present”),为false时,可以选填这个参数。

  defaultValue: 默认值,如果这个url没有携带这个参数时,默认设置的值。

  3、 PathVariable使用案例:

  @RequestMapping(“/pathVariable/test/{param}”)

  @PathVariable(value = “param”,required = false)String param

  4、参数解析:

  5、name/value:RequestMapping注解中url路径绑定参数的名称,如/pathVariable/test/{param},则name的值就为param

  6、required: 为true时,这个参数必选填写,默认是true,为false时:参数可选是否填写

三: 项目结构

在这里插入图片描述

四: 测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
java复制代码/**
* @author
* @version V1.0
* @Description: 注解测试
* @date 2020-7-29
*/
@Controller
public class AnnotationController {

/**
* RequestParam: 用于获取URL中“?”后携带的参数的值,如: http://localhost:8080/requestParam/test?param=xxx中param参数的值
* 相关属性:
* 1、name/value:url中指定参数的名称
* 2、required: 为true时,这个参数必选填写,默认是true,为false时:参数可选是否填写
* 3、defaultValue:参数不填写时的默认值
**/
@RequestMapping("/requestParam/test")
@ResponseBody
public String requestParamTest(@RequestParam(value = "param",required = true)String param){
return "接受到的参数:" + param;
}


/**
* RequestParam: 用于获取URL中路径的参数值,参数名由RequestMapping注解请求路径时指定,常用于restful风格的api中
* 如: http://localhost:8080/pathVariable/test/123 中123的值
* 相关属性:
* 1、name/value:RequestMapping注解中url路径绑定参数的名称,如/pathVariable/test/{param},则name的值就为param
* 2、required: 为true时,这个参数必选填写,默认是true,为false时:参数可选是否填写
**/
@RequestMapping("/pathVariable/test/{param}")
@ResponseBody
public String pathVariableTest(@PathVariable(value = "param",required = false)String param){
return "pathVariable接受到的参数:" + param;
}

}

五: 测试结果

(一) @RequestParam注解测试结果

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

(二) @PathVariable注解测试结果

在这里插入图片描述

在这里插入图片描述

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

若依系统中的分页实现方式-PageHelper

发表于 2021-11-13

这是我参与11月更文挑战的第12天,活动详情查看:2021最后一次更文挑战

在昨天的文章《若依系统中的分页实现方式-数据结构篇》中,我们简单阐述了若依系统中的分页架构,若依系统中的后端分页主要代码接口的三个特点:

  1. 返回值类型为TableDataInfo
  2. 接口第一行代码startPage()
  3. 接口最后一句:getDataTable(list)

获取分页参数

我们已经看到了TableDataInfo的数据结构,就是一个分页的数据结构,有total,有rows数据。

今天我们来看看startPage()时执行了什么逻辑。startPage方法位于基类BaseController中。代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
java复制代码/**
* 设置请求分页数据
*/
protected void startPage()
{
PageDomain pageDomain = TableSupport.buildPageRequest();
Integer pageNum = pageDomain.getPageNum();
Integer pageSize = pageDomain.getPageSize();
if (StringUtils.isNotNull(pageNum) && StringUtils.isNotNull(pageSize))
{
String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy());
Boolean reasonable = pageDomain.getReasonable();
PageHelper.startPage(pageNum, pageSize, orderBy).setReasonable(reasonable);
}
}

我们逐行来分析。

第一行PageDomain pageDomain = TableSupport.buildPageRequest();,引入了一个新的类PageDomain,这个类又是干嘛的呢?

PageDomain的详细结构如下述代码所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
java复制代码/**
* 分页数据
*
* @author ruoyi
*/
public class PageDomain
{
/** 当前记录起始索引 */
private Integer pageNum;

/** 每页显示记录数 */
private Integer pageSize;

/** 排序列 */
private String orderByColumn;

/** 排序的方向desc或者asc */
private String isAsc = "asc";

/** 分页参数合理化 */
private Boolean reasonable = true;

其中包含了pageNum与pageSize分页参数,还有排序列以及排序方式。那么TableSupport.buildPageRequest()是如何获取到这些参数的呢?

其详细代码如下所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
java复制代码    public static PageDomain buildPageRequest()
{
return getPageDomain();
}
}

/**
* 封装分页对象
*/
public static PageDomain getPageDomain()
{
PageDomain pageDomain = new PageDomain();
pageDomain.setPageNum(ServletUtils.getParameterToInt(PAGE_NUM));
pageDomain.setPageSize(ServletUtils.getParameterToInt(PAGE_SIZE));
pageDomain.setOrderByColumn(ServletUtils.getParameter(ORDER_BY_COLUMN));
pageDomain.setIsAsc(ServletUtils.getParameter(IS_ASC));
pageDomain.setReasonable(ServletUtils.getParameterToBool(REASONABLE));
return pageDomain;
}

在方法内部,调用了函数getPageDomain(),在getPageDomain()中,获取请求参数时使用了一个工具类方法:ServletUtils.getParameterToInt,我们再来看看这方法。

1
2
3
4
5
6
7
8
9
10
11
java复制代码public class ServletUtils
{
/**
* 获取String参数
*/
public static String getParameter(String name)
{
return getRequest().getParameter(name);
}
// 其他方法....
}

如何便逐层将前端Request的分页参数获取到了对象pageDomain中。

那么分页参数又是如何转换为sql执行的呢?

代码查找

接着研究startPage()方法,后续一句代码为:

1
java复制代码String orderBy = SqlUtil.escapeOrderBySql(pageDomain.getOrderBy());

我们来看看这个方法的内部。

1
2
3
4
5
6
7
8
9
10
11
java复制代码/**
* 检查字符,防止注入绕过
*/
public static String escapeOrderBySql(String value)
{
if (StringUtils.isNotEmpty(value) && !isValidOrderBySql(value))
{
throw new UtilException("参数不符合规范,不能进行查询");
}
return value;
}

这个方法知识检查order_by sql语句是否合法的。

接着,我们来到了关键的:PageHelper.startPage(pageNum, pageSize, orderBy).setReasonable(reasonable);,其中的startPage方法,我们来看看其执行代码:

1
2
3
4
java复制代码/**抽象类:PageMethod中*/
public static <E> com.github.pagehelper.Page<E> startPage(int pageNum, int pageSize, java.lang.String orderBy) {
return null;
}

一脸懵逼!

哦~经过询问度娘,我们知道,这个是一个与mybatis相关的分页工具,叫PageHelper,孤陋寡闻了。

我们也在pom.xml中找到了配置:

1
2
3
4
5
xml复制代码<!-- pagehelper 分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
</dependency>

在配置文件application.yml中:

1
2
3
4
5
yaml复制代码# PageHelper分页插件
pagehelper:
helperDialect: mysql
supportMethodsArguments: true
params: count=countSql

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

linux之基础符号详解(上)

发表于 2021-11-13

「这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战」

基础符号系列:

1
2
3
4
5
6
7
8
9
csharp复制代码$   --- 用于调用变量信息 $PATH
$3 $NF 在awk中取出相应列信息
# root用户管理系统
$ 普通用户管理系统

! --- 取反操作/排除操作 !好看=吃藕
命令行 !-- 取出最近输入的命令

| --- 表示管道符号,管道前面命令,交给管道后面执行

1.表示文件内容注释符号
2.表示用户命令提示符号 超级用户为# 普通用户为$

引号符号系列:

1
2
3
4
5
6
7
csharp复制代码美元括号:$() --- 表示命令执行结果留下,用于其他命令调用

双引号: "" --- 表示输入内容,就是输入内容,但是部分信息会被解析

单引号: '' --- 表示输入内容,就是输出内容(所见即所得)

反引号: `` --- 表示命令执行结果留下,其他命令调用

定向符号系列:

1
2
3
4
5
6
7
8
csharp复制代码单个小于符号: <  --- 标准输入重定向
两个小于符号: << --- 标准输入追加重定向

单个大于符号: > --- 标准输出重定向 (会覆盖原有内容---)
2> -- 错误输出重定向

两个大于符号: >> --- 标准输出追加重定向
2< -- 错误输出追加重定向

补充:

标准输出重定向符号(会覆盖原有内容–)
echo 123456 >oldboy.txt
1)找到oldboy.txt文件,清空文件中内容
2)向文件重定向输入新的字符内容

1
lua复制代码>oldboy.txt  --- 清空一个文件内容

路径信息系列:

1
2
3
4
5
6
7
csharp复制代码单点符号: .  --- 表示当前目录

双点符号: .. --- 表示上级目录

破浪符号: ~ --- 表示用户家目录信息
超级用户:/root
普通用户:/home/用户名称

逻辑符号系列:

1
2
3
csharp复制代码并且符号: &&  --- 表示前面的名称执行成功,在执行后面的命令

或者符号: || --- 表示前面的名称执行失败,再执行后面的命令

04.系统通配符号说明(主要文件信息)

  • 表示匹配所有内容
    模拟环境:
    mkdir /oldboy -p
    cd /oldboy
    touch oldboy.txt
    touch oldboy.log
    touch oldgirl.log
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
csharp复制代码测试题一:请找出所有以.txt结尾的文件
find /oldboy -type f -name "*.txt"
ll /oldboy/*.txt

测试题二:请找出所有以oldboy命名文件
find /oldboy -type f -name "oldboy.*"
find /oldboy -type f -name "*oldboy*"
ll /oldboy/oldboy*

测试题三:请找出所有以old开头,log结尾的文件
find /oldboy -type f -name "old*.log"
ll /oldboy/old*.log

扩展补充:如果在一个文件中有以下内容
oldboy.txt
oldboy.log
oldgirl.log

请问如用grep命令过滤相应内容:

1
csharp复制代码grep "oldboy.*" oldboy.txt

PS:grep sed awk 三剑客命令(高级命令) 对于低级符号(常见符号 通配符号)
高级命令所经常识别的为高级符号(正则表达式)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
csharp复制代码  
{} 表示构造出一个序列信息
连续系列
构造数字序列 echo {01..10}
构造字母序列 echo {a..z}
构造字母不连续序列 echo {a,c,e,f}
构造数字不连续序列 echo {1,3,5,7,9}

构造多个序列(连续)
[root@fu oldboy]# echo {1..3}{1..3}
11 12 13 21 22 23 31 32 33
[root@fu oldboy]# echo {1..3}{1..3}{1..3}
111 112 113 121 122 123 131 132 133 211 212 213 221 222 223 231 232 233 311 312 313 321 322 323 331 332 333

构造多个连续序列(不连续)
[root@fu oldboy]# echo {1,3,5}{2,4,6}
12 14 16 32 34 36 52 54 56
[root@fu oldboy]# echo {r,-,-}{-,w-,-}{-,-,x}
r-- r-- r-x rw-- rw-- rw-x r-- r-- r-x --- --- --x -w-- -w-- -w-x --- --- --x --- --- --x -w-- -w-- -w-x --- --- --x

单个字符串和序列的组合
[root@fu oldboy]# echo A{A,B}
AA AB
[root@fu oldboy]# echo A{,B}
A AB
[root@fu oldboy]# echo oldboy{,.bak}
oldboy oldboy.bak
[root@fu oldboy]# cp oldboy.txt{,.bak}
[root@fu oldboy]# ll
total 24
drwxr-xr-x 3 root root 4096 Jul 4 19:28 ext
drwxr-xr-x 2 root root 4096 Jul 5 19:27 oldboy
-rw-r--r-- 1 root root 0 Jul 5 20:24 oldboy.log
-rw-r--r-- 3 root root 0 Jul 5 20:24 oldboy.txt
-rw-r--r-- 1 root root 0 Jul 5 21:15 oldboy.txt.bak

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

spring加载xml文件异常解决方案记录

发表于 2021-11-13

Spring版本: 5.3.x

问题描述

在测试Spring Bean工厂加载XML文件的时候,报出如下异常:

1
2
3
4
5
6
7
8
9
log复制代码Passed-in Resource [InputStream resource [resource loaded through InputStream]] contains an open stream: cannot determine validation mode automatically. Either pass in a Resource that is able to create fresh streams, or explicitly specify the validationMode on your XmlBeanDefinitionReader instance.
org.springframework.beans.factory.BeanDefinitionStoreException: Passed-in Resource [InputStream resource [resource loaded through InputStream]] contains an open stream: cannot determine validation mode automatically. Either pass in a Resource that is able to create fresh streams, or explicitly specify the validationMode on your XmlBeanDefinitionReader instance.
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.detectValidationMode(XmlBeanDefinitionReader.java:468)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.getValidationModeForResource(XmlBeanDefinitionReader.java:449)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadDocument(XmlBeanDefinitionReader.java:433)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.doLoadBeanDefinitions(XmlBeanDefinitionReader.java:390)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:338)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions(XmlBeanDefinitionReader.java:310)
at org.springframework.beans.factory.xml.XmlBeanDefinitionReaderTests.testSimpleBeanLoad(XmlBeanDefinitionReaderTests.java:70)

先贴出源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
xml复制代码@Test
public void testSimpleBeanLoad() {
//新版本XML的Bean工厂
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
//获取配置文件
Resource resource = new InputStreamResource(getClass().getResourceAsStream("test.xml"));
//加载配置文件到Bean工厂
new XmlBeanDefinitionReader(factory).loadBeanDefinitions(resource);
//获取其中的一个Bean配置
TestBean bean = factory.getBean("rod", TestBean.class);
assertThat(bean).isNotNull();
assertThat(bean.getName()).isNotNull();
}

问题分析

  • 直接原因:
    org.springframework.beans.factory.xml.XmlBeanDefinitionReader#detectValidationMode方法进行验证模式检测时,首先会检查Resource的idOpen(),如果返回的是 true, 则会抛出上述的BeanDefinitionStoreException异常。
1
2
3
4
5
6
7
8
9
10
java复制代码protected int detectValidationMode(Resource resource) {
if (resource.isOpen()) {
throw new BeanDefinitionStoreException(
"Passed-in Resource [" + resource + "] contains an open stream: " +
"cannot determine validation mode automatically. Either pass in a Resource " +
"that is able to create fresh streams, or explicitly specify the validationMode " +
"on your XmlBeanDefinitionReader instance.");
}
//...
}
  • 根本原因
    isOpen()的值是Resource实现本身写定的,我们使用的InputStreamResource刚好定义的是true, 故而报出此异常。
1
2
3
java复制代码public boolean isOpen() {
return true;
}

解决方案

其实问题的解决方案在InputStreamResource中已经注释出来了。

1
2
3
4
5
scss复制代码给定InputStream Resource实现。
仅当没有其他特定的Resource实现适用时才应使用。
特别是,在可能的情况下,更喜欢ByteArrayResource或任何基于文件的Resource实现。
与其他Resource实现相反,这是一个已经打开的资源的描述符 - 因此从isOpen()返回true 。
如果需要将资源描述符保留在某处,或者需要多次从流中读取,请不要使用InputStreamResource 。

image.png

而相关的实现有很多, 如下列出的:

1
2
3
4
5
6
7
8
markdown复制代码* WritableResource
* ContextResource
* UrlResource
* FileUrlResource
* FileSystemResource
* ClassPathResource
* ByteArrayResource
* InputStreamResource

最终的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
scss复制代码@Test
public void testSimpleBeanLoad() {
//新版本XML的Bean工厂
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
//获取配置文件
Resource resource = new FileSystemResource(getClass().getResource("test.xml").getPath());
//加载配置文件到Bean工厂
new XmlBeanDefinitionReader(factory).loadBeanDefinitions(resource);
//获取其中的一个Bean配置
TestBean bean = factory.getBean("multiAliased", TestBean.class);
assertThat(bean).isNotNull();
assertThat(bean.getName()).isNotNull();
}

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

Redis分布式锁实战方案 减库存场景 分布式锁在高并发场景

发表于 2021-11-13

「这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战」。

减库存场景

分布式锁,锁的是资源,我们先设定一个场景,按照这个场景去一步步说这个分布式锁。

假设我们有个减库存的场景,将库存存到redis当中,每当来个减库存的请求,就进行减库存,更新redis

如果是单机场景就是只用一个服务器部署一个单机项目,在高并发场景下多个线程同时去减库存,这时候

可以用redis的setnx命令,这个命令会返回true或false,如果当前key已经存在,就会返回false,不存在就会返回true,通过这个可以实现锁的机制,但是也会出现问题,如果执行过程出现异常,没有来及删除锁,就会出现死锁,所以这时可以设置一个过期时间,在这个时间内如果还没释放锁,锁就会自动被释放,为了保证设置锁和设置过期时间是原子性的,可以使用lua脚本来实现,也可以用syrnchronized来实现,但是syrnchronized在单实例场景下没有问题,在分布式场景,集群部署场景下就不可以了。

这里我们用springboot 集成redis来演示下基于redis的setnx加过期时间的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
csharp复制代码
@GetMapping("/lock")

public String TestRedis() {

String clientId = UUID.randomUUID().toString();

try {

Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent("stock", clientId, 10, TimeUnit.SECONDS);

if (!aBoolean) {

return "error";

}

redisslock.lock(30, TimeUnit.SECONDS);

int stock = Integer.parseInt(redisTemplate.opsForValue().get("stock"));

if (stock > 0) {

stock = stock - 1;

redisTemplate.opsForValue().set("stock", stock + "");

System.out.println("扣减成功,剩余库存" + stock);

} else {

System.out.println("扣减失败,剩余库存" + stock);

}

} catch (Exception e) {

e.printStackTrace();

} finally {

if (clientId.equals(redisTemplate.opsForValue().get("stock"))) {

// 释放锁

redisTemplate.delete("stock");

}

}

这种方式在设置锁时,value用了一个uuid生成的随机数,为什么要这样做呢,假设一个场景,key设置的过期时间到了,但是线程1还没执行完,这时候线程2就会拿到锁,此时线程1执行完,释放锁,释放的就是线程2的锁,为了让每个线程释放自己的锁,所以会设置一个惟一的value,每次释放锁的时候判断value是不是自己的value,是的话再释放锁。

这种方式实现分布式锁,存在过期时间到线程还没执行完的情况,所以为了让线程1执行完前锁不会过期,我们可以采用Redission来实现分布式锁。

Redisson

首先看下具体的实例代码,看下Redisson怎么实现分布式锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
csharp复制代码
@Autowired

private Redisson redisson;

@GetMapping("/lock")

public String TestRedis() {

String clientId = UUID.randomUUID().toString();

RLock redisslock = redisson.getLock("stocks");

try {

// Boolean aBoolean = redisTemplate.opsForValue().setIfAbsent("stock", clientId, 10, TimeUnit.SECONDS);

// if (!aBoolean) {

// return "error";

// }

redisslock.lock(30, TimeUnit.SECONDS);

int stock = Integer.parseInt(redisTemplate.opsForValue().get("stock"));

if (stock > 0) {

stock = stock - 1;

redisTemplate.opsForValue().set("stock", stock + "");

System.out.println("扣减成功,剩余库存" + stock);

} else {

System.out.println("扣减失败,剩余库存" + stock);

}

} catch (Exception e) {

e.printStackTrace();

} finally {

redisslock.unlock();

// if (clientId.equals(redisTemplate.opsForValue().get("stock"))) {

// // 释放锁

// redisTemplate.delete("stock");

// }

}

很简单,几步就完成了分布式锁的实现,来看下它的运行流程

image.png

image.png

加锁机制

客户端要加锁,如果该客户端面对的是一个redis cluster集群,首先会根据hash节点来选择一台机器。

紧接着会发送一段lua脚本到redis上。

image.png

使用lua是因为将一堆业务封装在lua脚本发送给redis,会保证这段复杂的业务逻辑执行的原子性。

来看看这段lua的意思。

  • KEYS[1]代表加锁的那个key:

RLock lock=redisson.getLock(“mylock”);

这里自己设置了加锁的那个锁key就是“mylock”,

  • ARGV[1] 代表的是锁key默认的生存时间,默认30秒。
  • ARGV[2] 代表1加锁客户端ID,是个唯一值,用来保证只释放自己设置的锁key.

第一段if语句是判断加锁的那个key存不存在,如果不存在,就进行加锁。

加锁命令:

hset myLock 8743c9c0-0795-4907-87fd-6c719a6b4586:1 1

可以看到是使用hash来进行设置加锁key,最后这个1代表该线程对这个key加锁的次数,下面讲到可重入锁会用到。

接着会执行 “pexpire myLock 30000”命令,对key设置过期时间。

这个加锁过程就算完成了。

锁互斥机制

这时候,如果客户端2来尝试加锁,执行了同样一段lua脚本,首先会判断当前的key是否存在,如果存在,接着判断当前key的value是否是客户端2的客户ID。

如果不是,接着获取这个锁key的过期时间,然后该客户端会进入一个while循环,不停的尝试加锁。

watch log 自动延期机制

客户端1加锁默认过期时间是30秒,如果想在超过了30秒,客户端1还想一直持有锁,这时就可以用这个自动延期机制,只要客户端1加锁成功,redis就会启动一个wattch log,它是一个后台线程,每隔10秒检查一下,这个具多少时间检查一下,一般是根据过期时间的三分之一,如果客户端1还持有锁,就会不断延长锁key的生存时间。

可重入加锁机制

如果客户端1已经持有这把锁,可重入的加锁如何处理的呢?

image.png

我们再回过头看下那段lua脚本,第二个if判断如果成立,因为mylock会包含客户端的ID,如果ID相等,此时就会执行可重入锁逻辑,

incrby mylock,通过这个命令对客户端1加锁次数加1

image.png

可以看到最后那个2就代表加锁次数。

是否锁机制

lock.unlock来释放锁,就是对mylock的那个加锁次数减1,㝉加锁次数变为0了,就会 del mylock,来删除这个key.

Redisson缺点

如果Redis是主从结构,如果对主加点写入了这个mylock锁,此时会异步复制给从节点,如果在这个过程主节点宕机,还没来得及复制给从节点,主备切换,从节点变为主节点,就会导致客户端2也会加锁成功,而客户端1也以为自己成功加了锁,就会导致多个客户端对一个分布式锁完成加锁。

RedLock

现在假设有5个Redis主节点(大于3的奇数个),这样基本保证了他们不会同时都宕机,获取锁和释放锁的过程,客户端会执行以下操作。

  • 获取当前Unix时间,以毫秒为单位
  • 依次尝试从5个实例,使用相同的key和具有唯一性的value获取锁

当向Redis请求获取锁时,客户端应该设置一个网络连接和响应超时时间,这个超时时间应该小于锁的失效时间,这样可以避免客户端死等

  • 客户端使用当前时间减去开始获取锁时间就得到获取锁使用的时间,当且仅当从半数以上的Redis节点取到锁,并且使用的时间小于锁失效时间,锁才算获取成功
  • 如果获取到锁,key真正的有效时间等于有效时间减去获取锁所使用的时间
  • 如果因为某些原因,获取锁失败(没有在半数以上实例取到锁或者取锁时间已经超过了有效时间)

,客户端会在所有Redis实例上进行解锁,无论Redis实例是否加锁成功,都会去解锁。

失败的重试

当一个客户端获取锁失败时,这个客户端应该在一个随机延时后进行重试,之所以采用随机延时是为了避免不同客户端同时重试导致谁都无法拿到锁的情况出现。同样的道理客户端越快尝试在大多数Redis节点获取锁,出现多个客户端同时竞争锁和重试的时间窗口越小,可能性就越低,所以最完美的情况下,客户端应该用多路传输的方式同时向所有Redis节点发送SET命令。 这里非常有必要强调一下客户端如果没有在多数节点获取到锁,一定要尽快在获取锁成功的节点上释放锁,这样就没必要等到key超时后才能重新获取这个锁(但是如果网络分区的情况发生而且客户端无法连接到Redis节点时,会损失等待key超时这段时间的系统可用性)

分布式锁在高并发场景下的问题及解决方案

对同一个商品的下单请求,会导致所有客户端都必须对同一个商户库存锁key进行加锁。

同一个商品多用户同时下单的时候,会基于分布式锁串行化处理,导致没法同时处理同一个商品大量下单请求。

如何优化

优化理念可以借鉴ConcurrentHashMap的分段锁思想。把数据分成很多段,每个段是一个单独的锁,多个线程过来可以并发修改不同端的数据。

image.png

image.png
假如iphone有1000个库存,可以给拆成20个库存段,每个库存段是50个库存。这样的话同时可以有20个下单请求一起执行。如果发现库存段里库存不足,会自动释放锁,立马换下一个分段库存,再次尝试加锁处理。

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

每天一个 Linux 命令(8)—— route 命令简介

发表于 2021-11-13

这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战

命令简介

route 命令 用于显示或修改系统内核的 IP 路由表,设置到达特定主机或网络的静态路由。 。

要实现两个不同的子网之间的通信,需要一台连接两个网络的路由器,或者同时位于两个网络的网关来实现。

注意,route 命令将会废弃,替换的命令是 ip route。

命令格式

1
css复制代码route [-f] [-p] [Command [Destination] [mask Netmask] [Gateway] [metric Metric]] [if Interface]]

命令参数

参数 解释
-A family 使用指定的地址系列,如 inet,表示 IPv4。
-F 直接操作 Linux 系统内核的路由表。这是默认的处理方式。
-C 直接操作 Linux 系统内核的路由缓存区。
-v 详细显示模式。
-n 直接显示 IP 地址,无须转换成主机名。
-e 采用 netstat 命令的格式显示路由表。利用 -ee 选项,可以生成更多的输出内容,其中包括路由表能够提供的所有参数。
-f 清除所有网关入口的路由表。
add 增加新路由。
del 删除现有的路由。
-net target 表示指定的路由目的是网络。target 是目的网络的网络名,或网络的 IP 地址。如果未指定 -net 或 -host,target 可以是关键字 default,表示默认的路由。
-host target 表示指定的路由目的是主机。target 是目的主机的主机名,或主机的 IP 地址。
netmask nm 在增加网络路由时,使用 nm 作为子网掩码。
gw gw 通过指定的网关发送路由分组数据。注意,指定的网关必须是能够直达的主机,负责转发路由分组数据,或者直接提供路由服务。
metric n 设置路由表中的网络距离度量字段,供路由守护进程使用。
mss m 设置当前路由上 TCP 的最大数据段容量(Maximum Segment Size),m 的单位是字节。默认值是网络接口支持的最大传输单位 MTU 减去 TCP 头信息长度之后的结果。
window w 设置当前路由上 TCP 确认窗口的大小(单位是字节)。主要用于 AX.25 网络。
irtt i 设置当前路由上 TCP 连接的初始往返传输时间,取值范围是 1~12000,单位是毫秒,默认值是 300 毫秒。主要用于 AX.25 网络。
reject 安装一个封锁路由,禁止外部网络检索路由。利用这个功能选项,可以在使用默认路由之前屏蔽外部网络。
Command 指定您想运行的命令 (Add/Change/Delete/Print)。
Destination 指定该路由的网络目标。

输出字段

输出字段 解释
Destination 路由的目的网络或目的主机。
Gateway 网关地址( *表示未设置网关)。
Genmask 确定到达目的网络或主机的路由时使用的子网掩码。当根据给定的 IP 地址寻找适当的路由时,系统内核将会利用子网掩码对IP地址进行逻辑与运算,然后依次检索每一个路由表项,从中找出匹配的路由。
Flags 可能出现的路由标志包括:U:表示相应的路由已经建立。H:表示路由的目的地是一个主机。G:表示路由是利用网关实现的。R:表示路由是重新恢复的动态路由。D:表示路由是由路由守护进程或 ICMP 重定向消息动态建立的。M:表示路由已根据路由守护进程或 ICMP 重定向消息发生了变动。A:表示安装的路由。C:表示缓存的路由。!: 拒绝路由查询。
Metric 从本地系统到目的网络或主机的网络“距离”,即网间的跳转数量。
Ref 引用当前路由的数量。
Use 路由检索计数。如果指定了 -F 选项,显示的是检索时缓存中不存在相应路由的次数,如果指定的是 -C 选项,显示的是检索时缓存中存在相应路由的次数。
Iface 发送路由分组数据的网络接口。
MSS TCP 连接默认的最大数据段的字节数量,也是系统内核通过相应路由能够传输的最大数据报的大小。
Window TCP 连接通过相应路由一次能够传输的最大数据量。
irtt “initial round trip time”的缩写,表示初始的往返传输时间。在 TCP 协议传输数据期间,如果数据传输有误,TCP 将会重传丢失的数据报。TCP 协议采用一个计数器,记录数据报传输到远程主机花费的时间,以及收到一个确认需要多长时间,因而知道在重传数据报之前需等待多长时间。这个时间称作数据报传输与确认的往返时间。在开始建立网络连接时,TCP协议将会采用一个初始的往返时间作为默认值。对于大多数网络而言, TCP 协议采用的默认往返时间值是适当的,但对于一些速度较慢的网络,如果这个时间太短,将会引起不必要的数据报重传。必要的话可以设置 irtt 值。如果这个字段值为 0,意味着采用默认值。

应用实例

  1. 假定本地系统的网络接口(其 IP 地址为192.168.90.101)连接到主机(其内部网络接口的 IP 地址为192.168.90.100),主机的另外一个网络接口(其 IP 地址为153.78.26.145)连接到外部网络。为使本地系统能够访问 153.78.26.0 网络中的任何主机,可以使用 route 命令,增加一个静态路由。
1
csharp复制代码route add -net 153.78.26.0 netmask 255.255.255.0 gw 192.168.90.100
  1. 在没有其他匹配的路由可用时,可以使用默认的路由。在上述情况下,192.168.90.0 网段上的主机均可仅仅增加一个简单的默认路由设置。
1
csharp复制代码route add default gw 192.168.90.100

参考文档

  • route命令
  • 《Linux 常用命令简明手册》—— 邢国庆编著

本文转载自: 掘金

开发者博客 – 和开发相关的 这里全都有

1…351352353…956

开发者博客

9558 日志
1953 标签
RSS
© 2025 开发者博客
本站总访问量次
由 Hexo 强力驱动
|
主题 — NexT.Muse v5.1.4
0%