profile
viewpoint
Wu YunCheng wuyc Hangzhou, China http://wuyuncheng.com/ 𝓛𝓲𝓯𝓮 𝓲𝓼 𝓯𝓪𝓷𝓽𝓪𝓼𝓽𝓲𝓬 🖖

startedspring-projects/spring-data-redis

started time in 10 hours

startedspring-projects/spring-security

started time in 10 hours

startedspring-projects/spring-data-jpa

started time in 10 hours

startedhibernate/hibernate-orm

started time in 10 hours

created repositorywuyc/bodhi

created time in 10 hours

created repositorywuyc/brahma

created time in 11 hours

created repositorywuyc/nirvana

created time in 11 hours

created repositorywuyc/prajna

created time in 11 hours

startedrancher/k3s

started time in 21 hours

started0voice/interview_internal_reference

started time in 21 hours

startedazl397985856/leetcode

started time in 21 hours

startedvugu/vugu

started time in 21 hours

startedryanhanwu/How-To-Ask-Questions-The-Smart-Way

started time in a day

startedUnitech/pm2

started time in a day

startedruby/ruby

started time in a day

startedgoby-lang/goby

started time in a day

startedhantmac/Mastering_Go_ZH_CN

started time in 2 days

push eventwuyc/blog

wuyc

commit sha 03e6ac07c4ec39d9bbd6dd3074760dcaa3c7fdeb

create issue_template.md

view details

push time in 2 days

push eventwuyc/blog

Wu YunCheng

commit sha f6d5c4e04fe6ccfee2ac893d02fc61f4e003fd78

Update issue_template.md

view details

push time in 2 days

startedPuerkitoBio/goquery

started time in 2 days

startedfouber/blog

started time in 2 days

startedlifesinger/blog

started time in 2 days

fork wuyc/blog-1

随写工作中遇到的问题、学习的心得,于己总结回顾,与他分享讨论。。。

fork in 3 days

startedjasonGeng88/blog

started time in 3 days

push eventwuyc/wuyc.github.io

Wu YunCheng

commit sha 239c3ff89030f6c59ff86401236be53b73d0736b

Update _config.yml

view details

push time in 3 days

fork wuyc/personal-website

Code that'll help you kickstart a personal website that showcases your work as a software developer.

https://github.dev

fork in 3 days

issue openedwuyc/blog

Springboot 整合 Swagger2

添加依赖

<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger2</artifactId>
    <version>2.9.2</version>
</dependency>
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-swagger-ui</artifactId>
    <version>2.9.2</version>
</dependency>

配置 Bean

@Configuration
@EnableSwagger2 // 启用 Swagger2
public class AppConfig {
    @Bean
    public Docket createRestAPI() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com.example.springbootswagger2"))
                .paths(PathSelectors.any())
                .build();
    }
    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("Swagger2 API 文档")
//                .description("Github: https://github.com/")
                .termsOfServiceUrl("https://github.com/")
                .contact(new Contact("Your name", "https://github.com/", "xxx@gmail.com"))
                .version("1.0")
                .build();
    }
}

开始使用

上面的配置完成后就可以在 SpringBoot 中使用 Swagger2 了。
使用很简单,我们只需要在 Controller 的类和方法上写上相关注解,Swagger2 就会自动帮我们生成 API 文档。

常用注解

  • @Api 用于类
@Api(value="用户Controller",tags={"用户操作接口"})
public class UserController {}
  • @ApiOperation 用于方法
  1. value 用于方法描述
  2. notes用于提示内容
  3. tags可以重新分组(视情况而用)
@ApiOperation("获取用户列表")
public List<User> getUserList() {}
  • @ApiParam 用于方法的参数,字段说明
@ApiOperation(value="获取用户信息", tags={"获取用户信息copy"}, notes="注意问题点")
public User getUserInfo(@ApiParam(name="id",value="用户id",required=true) Long id){}
  • @ApiIgnore 用于类或者方法上,表示不被 Swagger2 显示在页面上

  • @ApiModel 表示对类进行说明,用于参数用实体类接收

  • @ApiModelProperty 表示对 model 属性的说明或者数据操作更改

启动 SpringBoot 程序,访问http://localhost:8080/swagger-ui.html查看 API

参考:

  • http://blog.didispace.com/springbootswagger2/
  • https://github.com/swagger-api/swagger-core/wiki
  • https://www.ibm.com/developerworks/cn/java/j-using-swagger-in-a-spring-boot-project/index.html

created time in 3 days

issue openedwuyc/blog

SpringBoot 使用 Redis 管理 Session

开始使用

使用 Redis 管理 Session 在 SpringBoot 中很简单。首先添加依赖。

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.session</groupId>
        <artifactId>spring-session-data-redis</artifactId>
    </dependency>
</dependencies>

然后在标注了@Configuration的配置类上再加一个@EnableRedisHttpSession的注解,表示我们要使用 Redis 管理 Session,不再使用Web容器提供的 Session。
上面的配置完成后,就直接使用javax.servlet.http.HttpSession操作Session,这个类本来是 Servlet 操作Web容器的 Session,现在变成了操作 Redis 中的 Session,还是使用原来的 API。

前后端分离

在服务端设置一个 Session,就会向前端设置一个 Cookie,这个 Cookie 相当于存了一个 Session ID,在前后端分离中,我们不把 Session ID 存在 Cookie 中,而是放到 HTTP Headers 中给服务端验证。
想要改变 Session ID 存储的位置,我们需要配置一个叫HttpSessionIdResolver的 Bean 放到 Spring 容器中。

@Configuration
@EnableRedisHttpSession
public class AppConfiguration {

    @Bean
    public HttpSessionIdResolver httpSessionStrategy() {
        return HeaderHttpSessionIdResolver.xAuthToken();
    }

}

加入这个 Bean 后,Session ID 就不会放到 Cookie,而是放到 HTTP Headers 中。

参考:

  • https://www.infoq.cn/article/Next-Generation-Session-Management-with-Spring-Session

created time in 3 days

issue openedwuyc/blog

SpringBoot 整合 Redis

添加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <!--基于注解使用缓存-->
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

配置 Bean

@Configuration // SpringBoot 配置注解
@EnableCaching // 开启 Spring Cache
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class AppConfig {

    /**
     * 配置操作 Redis 的工具 RedisTemplate
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        // 使用 StringRedisSerializer 来序列化和反序列化 Redis 的 key 值
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(jackson2JsonRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(jackson2JsonRedisSerializer());
        template.afterPropertiesSet();
        return template;
    }

    /**
     * 配置 Redis 序列化
     */
    @Bean
    public RedisSerializer<Object> jackson2JsonRedisSerializer() {
        // 使用 Jackson2JsonRedisSerializer 来序列化和反序列化 Redis 的 value 值
        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        serializer.setObjectMapper(mapper);
        return serializer;
    }

    /**
     * 配置缓存 key: 包名 + 方法名 + 参数列表
     * 
     */
    @Bean
    public KeyGenerator keyGenerator() {
        return (target, method, objects) -> {
            StringBuilder sb = new StringBuilder();
            sb.append(target.getClass().getName());
            sb.append("::" + method.getName() + ":");
            for (Object obj : objects) {
                sb.append(obj.toString());
            }
            return sb.toString();
        };
    }

    /**
     * 配置 Spring Cache 的缓存管理器,Spring Cache 将使用 Redis 作为缓存数据库
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        // 生成一个默认配置,通过 config 对象即可对缓存进行自定义配置
        RedisCacheConfiguration config = RedisCacheConfiguration
                .defaultCacheConfig()
                // 设置缓存的默认过期时间,也是使用 Duration 设置
                .entryTtl(Duration.ofMinutes(10))
                // 设置 key 为 String 序列化
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                // 设置 value 为 json 序列化
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer()))
                // 不缓存空值
                .disableCachingNullValues();
        // 使用自定义的缓存配置初始化一个 CacheManager
        RedisCacheManager redisCacheManager = RedisCacheManager.builder(redisConnectionFactory)
                .cacheDefaults(config)
                .build();
        return redisCacheManager;
    }

}

使用 RedisTemplate 操作

SpringBoot 把对 Redis 的操作封装成了一个 RedisTemplate,在 Spring 容器中配置 RedisTemplate 后,我们就可以在其它类注入 RedisTemplate 用来操作 Redis。
不过我们在自己的业务逻辑中使用 RedisTemplate 实现缓存,这样耦合度高,不易于维护。

public String get(String key) {
    String value = userMapper.selectById(key);
    if (value != null) {
        cache.put(key,value);
    }
    return value;
}

还记得我们配置了spring-boot-starter-cache吗,有了这个 starter 我们就可以抛弃 RedisTemplate,使用注解的方式操作缓存。
使用spring-boot-starter-cache操作缓存降低的耦合度,还可以随意切换 Cache Client,不需要修改业务代码。

注解操作缓存

Spring Cache 提供了三个操作缓存的注解。

@Cacheable(根据方法的请求参数对其结果进行缓存)

  • key: 缓存的 key,可以为空,如果指定要按照 SpEL 表达式编写,如果不指定,则缺省按照方法的所有参数进行组合(如:@Cacheable(value="user",key="#userName"))
  • value: 缓存的名称,必须指定至少一个(如:@Cacheable(value="user") 或者 @Cacheable(value={"user1","use2"}))
  • condition: 缓存的条件,可以为空,使用 SpEL 编写,返回 true 或者 false,只有为 true 才进行缓存(如:@Cacheable(value = "user", key = "#id",condition = "#id < 10"))

@CachePut(根据方法的请求参数对其结果进行缓存,和 @Cacheable 不同的是,它每次都会触发真实方法的调用)

  • key: 同上
  • value: 同上
  • condition: 同上

@CachEvict(根据条件对缓存进行清空)

  • key: 同上
  • value: 同上
  • condition: 同上
  • allEntries: 是否清空所有缓存内容,缺省为 false,如果指定为 true,则方法调用后将立即清空所有缓存(如:@CacheEvict(value = "user", key = "#id", allEntries = true))
  • beforeInvocation: 是否在方法执行前就清空,缺省为 false,如果指定为 true,则在方法还没有执行的时候就清空缓存,缺省情况下,如果方法执行抛出异常,则不会清空缓存(如:@CacheEvict(value = "user", key = "#id", beforeInvocation = true))

参考:

  • https://www.qikegu.com/spring-boot-tutorial/1320
  • https://www.ibm.com/developerworks/cn/opensource/os-cn-spring-cache/
  • https://docs.spring.io/spring/docs/4.3.15.RELEASE/spring-framework-reference/html/cache.html
  • https://blog.battcn.com/2018/05/13/springboot/v2-cache-redis/

created time in 3 days

issue openedwuyc/blog

SpringBoot 整合 RabbitMQ

RabbitMQ 是什么

RabbitMQ 即一个消息队列,主要是用来实现应用程序的异步和解耦,同时也能起到消息缓冲,消息分发的作用。
比如有这样一个场景:
用户下单购买后,通过短信发送订单通知到用户的手机,然后还要将订单信息记录到日志数据库。
那么下单模块内就需要调用短信接口和日志接口,三者互相依赖,出现耦合。
如果使用消息队列,下单后把订单信息放到消息队列中,短信模块和日志模块自己到消息队列中取得订单信息,模块之间就解耦了。

简单使用

  1. 新建SpringBoot项目,在pom.xml中添加RabbitMQ依赖。
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
  1. 配置application.properties,添加RabbitMQ地址、端口和用户信息。
spring.rabbitmq.host=localhost
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest
  1. 新建RabbitMQ队列,并添加到Spring容器中。
@Configuration
public class RabbitConfig {

    @Bean
    public Queue Queue() {
        return new Queue("hello");
    }

}
  1. 新建消息发送者
@component
public class HelloSender {

    @Autowired
    private AmqpTemplate rabbitTemplate;

    public void send() {
        rabbitTemplate.convertAndSend("hello", "this is my msg...");
    }

}
  1. 新建消息接收者
@Component
@RabbitListener(queues = "hello")
public class HelloReceiver {

    @RabbitHandler
    public void process(String hello) {
        System.out.println("Receiver: " + hello);
    }

}
  1. 测试
@RunWith(SpringRunner.class)
@SpringBootTest
public class RabbitMqHelloTest {

    @Autowired
    private HelloSender helloSender;

    @Test
    public void hello() throws Exception {
        helloSender.send();
    }

}
output:
"Receiver: this is my msg...

高级使用

交换机(Exchange)主要的功能是就收发送者的消息,并转发到交换机绑定的队列,交换机不保存消息。在启用ack模式后,交换机找不到队列会返回错误。
交换机有四种类型:Direct, topic, Headers and Fanout

  • Direct:direct 类型的行为是“先匹配,再投送”,即在绑定时设定一个 routing_key,消息的 routing_key 匹配时,才会被交换器投送到绑定的队列中去。
  • Topic:按规则转发消息(最灵活),根据 routing_key 的规则绑定到不同的队列中。
  • Headers:设置 header attribute 参数类型的交换机。
  • Fanout:转发消息到所有绑定队列,Fanout 就是我们熟悉的广播模式或者订阅模式,给 Fanout 交换机发送消息,绑定了这个交换机的所有队列都收到这个消息。

created time in 3 days

issue openedwuyc/blog

SpringBoot异常处理

处理 HTTP 错误

在 Java Web 开发中,如果服务端产生 HTTP 错误,如 403、404、500 等等,Spring Boot 把错误信息默认映射到/error上,所以我们只需要编写/error控制器的代码,就可以做到对错误的处理。
首先创建一个实现了 ErrorController 接口的 Controller,然后重写getErrorPath()方法,方法返回/error,最后自己写一个处理/errorRequestMapping处理错误信息就行了。

@RestController
public class MyErrorController implements ErrorController {

    @RequestMapping("/error")
    public ResponseEntity error(HttpServletResponse response) {
        int statusCode = response.getStatus();
        return ResponseEntity
                .status(statusCode)
                .body(HttpStatus.valueOf(statusCode).getReasonPhrase());
    }

    @Override
    public String getErrorPath() {
        return "/error";
    }

}

tips: 也可以直接写一个 Controller 处理 /error

处理Java异常

当后端抛出未处理的异常后,会产生 HTTP 500 错误,如果不单独处理的话,会直接被/error处理。 如果需要单独处理Java异常,新建一个类写上@ControllerAdvice或者@RestControllerAdvice注解,表示这是一个处理Java异常的类。在这个类中,标注了@ExceptionHandler的方法就是处理异常的方法。

@RestControllerAdvice
public class GlobalExceptionHandler {

    // 处理抛出的 Exception.class 异常
    @ExceptionHandler(Exception.class)
    public ResponseEntity exceptionHandler() {
        return ResponseEntity
                .status(HttpStatus.INTERNAL_SERVER_ERROR)
                .body("Exception Handler.");
    }

}

created time in 3 days

startedwuyc/blog

started time in 3 days

push eventwuyc/blog

Wu YunCheng

commit sha cc0a08120711afe6ce6880df8b2924f0cf170eae

Create issue_template.md

view details

push time in 3 days

push eventwuyc/notebook

wuyc

commit sha 1468b93fbb478292d34db70d337b54a8794e7a06

change to issues blog

view details

push time in 3 days

issue openedwuyc/notebook

跨站请求伪造CSRF

什么是CSRF

如果某个 WebApp 存在 CSRF 漏洞,当受害者登录该页面后,黑客可以用受害者的身份执行恶意代码。
比如一个转账接口如下:

// GET 请求,向 bob 转账 100 元
GET http://www.mybank.com/transfer?username=bob&amount=100

另外一个黑客制作的恶意页面中包含以下代码:

<img src="http://www.mybank.com/transfer?username=hack&amount=999" width="500" height="300" />

当受害者之前登录过 www.mybank.com,然后访问黑客制作的恶意页面,浏览器就会发送 GET 请求到 http://www.mybank.com/transfer?username=hack&amount=999,这时就会向用户 hack 转账 999 元。
黑客伪造受害者的身份进行操作,服务端没有识别是不是这个用户触发的。

如何防范

1. 尽量使用 POST

GET 请求太容易被利用,POST 可以降低被攻击的风险,但不是万无一失。

2. 加入验证码

加入验证码后服务端可以识别是不是真正的用户,不过有些验证码也可以被破解,这也不是万无一失的解决方案。

3. 验证 Referer

Referer 是 HTTP 请求头中的一个字段,它记录了当前请求来源的地址。通过 Referer 可以识别当前请求是不是合法的。这种方法操作方便,但也不是万无一失,因为 Referer 可能会被篡改。

4. Anti CSRF Token

服务端每次都生成一个一次性随机 Token 存到 Session/Redis 中,然后传给浏览器,浏览器请求 API 时带上 Token,服务端收到请求后验证 Token 是不是之前生成的。这个 Token 在浏览器可以存在 head 的 meta 标签中,也可以存在表单中,还可以存在 HTTP 响应头中传递给浏览器。

CSRF 和 CORS 有什么区别

CORS 机制的目的是为了解决脚本的跨域资源请求问题,不是为了防止 CSRF。
CORS 在同源策略的限制下,响应会被拦截,请求还是发送到了服务端。
CSRF 只是向服务器发送请求,并不需要获取数据。

参考

  • https://zh.wikipedia.org/zh-cn/跨站请求伪造
  • https://www.bilibili.com/video/av33502871

created time in 3 days

issue openedwuyc/notebook

跨域资源共享 CORS

CORS 全称是 Cross-origin resource sharing (跨域资源共享),它允许浏览器跨源请求数据,克服了 AJAX 只能同源使用的限制。

同源策略

对于协议相同,域名相同,端口相同的被视为同一个源,在同一个源内可以获取本域中的 Cookie,可以 AJAX 请求本域中的资源。同源策略下无法获取其它域的资源。
同源策略是一个很重要的安全机制用来隔离那些有潜在安全隐患。保证用户信息的安全,防止恶意的网站窃取数据。

CORS

我们在 www.a.com 内通过 AJAX 请求 www.b.com 中的资源,浏览器在 HTTP 请求头中会自动加上下面的字段↓

Origin: https://www.a.com // 当前请求所在的域
Referer: https://www.a.com // 当前请求所在页面的 URL

上面的两条请求头信息是浏览器自动加上的,我们无法控制。
请求发出后,此时浏览器会报错↓

Access to XMLHttpRequest at 'https://www.b.com/' from origin 'https://www.a.com' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

www.a.com 接收到请求后,发现请求头中有 OriginReferer 两个字段,它就知道了这是一个跨域请求,是其它域发来的。默认情况下会拒绝跨域请求。所以我们无法跨域获取 www.b.com 中的数据。这是浏览器的安全策略,保证了用户信息的安全。

我想要跨域获取数据怎么办?

要想在其它域获取 www.b.com 中的数据,我们需要在 www.b.com 响应头中设置一些字段

Access-Control-Allow-Origin: https://www.a.com
Access-Control-Allow-Credentials: true
  • Access-Control-Allow-Origin: https://www.a.com
    该字段必选。它的值要么是请求时 Origin 字段的值,要么是一个 *,表示接受任意域名的请求。
  • Access-Control-Allow-Credentials: true
    该字段可选。它的值是一个布尔值,默认 false,表示是否允许发送 Cookie 或 HTTP 认证信息,比如 cookies, authorization headers 或 TLS client certificates。

除了以上字段,还有一些跨域相关的字段,它们都以 Access-Control- 开头。

推荐阅读

  • http://www.ruanyifeng.com/blog/2016/04/cors.html
  • https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Access_control_CORS

created time in 3 days

issue openedwuyc/notebook

TCP的三次握手和四次挥手

TCP 是一种面向连接的、可靠的、基于字节流的传输层通信协议。TCP 建立连接需要经过三次握手的过程,断开连接需要经过四次挥手。

TCP建立连接和断开连接流程

tcp-three-way-handshake

三次握手

  • 第一次握手(SYN=1,seq=x)

客户端发送连接请求报文段,将 SYN 位置为 1(SYN=1),Sequence Number为x(seq=x,x是ISN,它是随机值),发送完毕后,客户端进入 SYN_SEND 状态,等待服务器的确认。

  • 第二次握手(SYN=1,ACK=1,seq=y,ACKnum=x+1)

服务器收到数据后发回确认包(ACK)应答,即 SYN 标志位和 ACK 标志位均为1(SYN=1,ACK=1);
服务器端选择自己 ISN 序列号,放到 seq 域里(seq=y);
同时将确认序号(Acknowledgement Number)设置为客户的 ISN 加 1(ACKnum=x+1)。发送完毕后,服务器进入 SYN_RCVD 状态。

  • 第三次握手(ACK=1,ACKnum=y+1)

客户端再次发送确认包(ACK),SYN 标志位为0,ACK 标志位为1,并且把服务器发来 ACK 的序号字段 +1,放在确定字段中发送给对方。发送完毕后,客户端和服务器端都进入 ESTABLISHED 状态,TCP 握手结束。

四次挥手

  • 第一次挥手(FIN=1,seq=x)

客户端发送一个 FIN 标志位置为 1,seq=x 的包,表示自己已经没有数据可以发送了,但是仍然可以接受数据。发送完毕后,客户端进入 FIN_WAIT_1 状态。

  • 第二次挥手(ACK=1,ACKnum=x+1)

向客户端回一个 ACK 报文段(ACK=1),确认序号 ACKnum=x+1,表明自己接受到了客户端关闭连接的请求,但是还没准备好关闭连接(理论上:有可能还有数据向客户端传送)。
发送完毕后,服务器端进入 CLOSE_WAIT 状态,客户端接收到这个确认包之后,进入 FIN_WAIT_2 状态,等待服务器端关闭连接。

  • 第三次挥手(FIN=1, seq=y)

服务器端准备好关闭连接时,向客户端发送结束连接请求,FIN 置为1,seq=y。
发送完毕后,服务器端进入 LAST_ACK 状态,等待来自客户端的最后一个 ACK。

  • 第四次挥手(ACK=1,ACKnum=y+1)

客户端接收到来自服务器端的关闭请求,发送一个确认包 ACK=1 和 ACKnum=y+1,并进入 TIME_WAIT 状态,等待可能出现的要求重传的 ACK 包。
服务器端接收到这个确认包之后,关闭连接,进入 CLOSED 状态。
客户端等待了某个固定时间(两个最大段生命周期,2MSL,2 Maximum Segment Lifetime)之后,没有收到服务器端的 ACK ,认为服务器端已经正常关闭连接,于是自己也关闭连接,进入 CLOSED 状态。

为什么建立连接是三次握手,而关闭连接却是四次挥手呢?

这是因为服务端在 LISTEN 状态下,收到建立连接请求的 SYN 报文后,把 ACK 和 SYN 放在一个报文里发送给客户端。
而关闭连接时,当收到对方的 FIN 报文时,仅仅表示对方不再发送数据了但是还能接收数据,服务端是否现在关闭发送数据通道,需要上层应用来决定,因此,己方 ACK 和FIN一般都会分开发送。

ISN

三次握手的一个重要功能是客户端和服务端交换 ISN(Initial Sequence Number), 以便让对方知道接下来接收数据的时候如何按序列号组装数据。简单的可以理解为一个随机数。
如果 ISN 是固定的,攻击者很容易猜出后续的确认号。
ISN = M + F(localhost, localport, remotehost, remoteport)
M是一个计时器,每隔4毫秒加1。
F是一个 Hash 算法,根据源IP、目的IP、源端口、目的端口生成一个随机数值。要保证 hash 算法不能被外部轻易推算得出。

推荐阅读

  • https://github.com/jawil/blog/issues/14
  • https://www.zhoulujun.cn/html/theory/network/2015_0708_65.html
  • https://hit-alibaba.github.io/interview/basic/network/TCP.html

created time in 3 days

issue openedwuyc/notebook

Java内存模型

Java内存结构

jvm-memory-structure

线程共享

  • **方法区:**也称非堆区,存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。
  • **堆:**Java 虚拟机所管理的内存中最大的一块,**几乎所有的对象实例都在这里分配内存。堆是垃圾回收器主要管理的区域。Java 堆中还可以细分为:新生代老年代和~~永久代~~(在 Java8 中,永久代已经被移除,被一个称为“元空间”(Metaspace)**的区域所取代。元空间的本质和永久代类似,都是对 JVM 规范中方法区的实现。不过元空间与永久代之间最大的区别在于:元空间并不在虚拟机中,而是使用本地内存。);再细致一点的有 Eden 空间、From Survivor 空间、To Survivor 空间等。

线程私有

  • **Java虚拟机栈:****描述 Java 方法执行的内存模型。**每个方法执行的时候都会创建一个栈帧,方法执行完毕后栈帧就会出栈,栈帧内存储了局部变量表、操作数栈等信息。局部变量表存储了 Java 的基本数据类型和引用变量,这是我们初学 Java 接触过的。
  • **本地方法栈:**Java 虚拟机执行 Native 方法(C/C++实现的方法)的服务。
  • **程序计数器:****当前线程所执行的字节码的行号指示器。**字节码解释器工作时就是通过改变这个计数器的值来选取下一条需要执行的字节码指令,分支、循环、跳转、异常处理、线程恢复等基础功能都需要依赖这个计数器来完成。如果线程正在执行的是一个 Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是 Native 方法,这个计数器值则为空(Undefined)。

Java内存模型

Java 线程之间的通信由 Java 内存模型(Java Memory Model)控制。**JMM 是 JVM 的一种规范,定义了 JVM 的内存模型。JMM 的目的是解决由于多线程通过共享内存进行通信时,存在的本地内存数据不一致、编译器会对代码指令重排序、处理器会对代码乱序执行等带来的问题。**可以保证并发编程场景中的原子性、可见性和有序性。 JMM 定义了线程与主内存之间的抽象关系:线程之间共享的变量存在主内存中,每个线程都有一个工作内存。线程不直接操作主内存中的数据,而是拷贝一份主内存中的数据到工作内存,线程在工作内存中操作,操作完成后刷新到主内存中。 这种线程对内存的操作方式会导致数据对其它线程不可见,例如线程A拷贝了一份主内存中的数据操作完成后还没刷新到主内存中,然后线程B开始向主内存中获取数据,这时线程B获取的数据不是最新的,所以为了保证数据的可见性,Java 可以使用 volatile 修饰变量,对 volatile 修饰过的变量操作不会把拷贝一份到工作内存,而是直接在主内存中操作。volatile 保证了数据的可见性,不过效率会有所下降。 java-memory-model

推荐阅读:

  • https://yq.aliyun.com/articles/651049
  • https://www.cnblogs.com/ityouknow/p/5610232.html
  • http://www.jiangxinlingdu.com/concurrent/2019/02/16/java-memory-model.html

created time in 3 days

issue openedwuyc/notebook

Java多线程总结

什么是线程

线程是程序的执行单元,它包含在进程之中。

什么是进程

进程是线程的集合,一个线程内至少包含一个进程。

创建线程

1. 继承 Thread 类

public class MyThread extends Thread {

    public void run() {
        // 线程代码块
    }

    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start(); // 开启线程并等待 CPU 分配时间片
    }
}

2. 实现 Runnable 接口

public class MyThread implements Runnable {

    @Override
    public void run() {
        // 线程代码块
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new MyThread());
        thread.start(); // 开启线程并等待 CPU 分配时间片
    }
}

tips: 在实际开发中建议使用 Runnable 接口创建线程,因为符合面向接口编程的思想,实现 Runnable 接口后可以实现其它接口,还可以继承其它类。

sleep() 和 wait() 的区别

  1. sleep() 是 Thread 类中的方法, wait() 是 Object 类中的方法。
  2. Thread.sleep() 不会改变当前锁的行为,如果当前线程有锁,Thread.sleep() 不会释放当前的锁。
  3. wait() 方法会释放当前线程持有的锁,执行 wait() 后需要执行 notify() 或 notifyAll() 进行唤醒,唤醒后线程进入就绪状态,等待 CPU 调度。如果不唤醒,此线程会一直处于等待状态。

什么是守护线程

守护线程的作用是为其它线程的运行提供服务。简单的理解为:任何一个守护线程都是整个 JVM 中所有非守护线程的保姆。当程序只剩下守护线程的时候(也就是主线程运行结束),程序就会退出。例如 GC线程 就是守护线程。

synchronized 关键字

synchronized 可以修饰方法和代码块。 修饰实例方法表示锁定当前方法所属的对象;修饰静态方法表示锁定当前方法所属的 class 类。两者不会出现互斥现象。 修饰代码块时指定锁定的数据,可以锁定类,也可以锁定实例对象。锁定类只对类有效,对实例对象无效。

volatile 关键字

要了解 volatile 关键字,首先要了解主内存和工作内存,在每个 Java 线程中,线程会把要操作的数据从主内存拷贝一份到线程内部的工作内存中,然后线程操作的是工作内存中的数据,数据处理完成后再刷新到主内存中。这就可能造成一个线程在工作内存中修改了一个变量的值,而另外一个线程还继续使用它在主内存中的变量值的拷贝,造成数据的不一致。 要解决这种问题,就需要引入 volatile 关键字,这就指示 JVM 这个变量是不稳定的,每次使用它都到主存中进行读取。数据不经过线程内的工作内存。volatile 关键字的主要作用就是保证变量的可见性然后还有一个作用是防止指令重排序。

synchronized 和 java.util.concurrent.locks.Lock 的区别

Lock 是 Java 5 引入的新的 API,它使用 Java API 的方式进行线程加锁,Lock 能完成 synchronized 所实现的所有功能。Lock 有比 synchronized 更精确的线程语义和更好的性能,而且不强制性的要求一定要获得锁。synchronized 会自动释放锁,而 Lock 一定要求程序员手工释放,并且最好在 finally 块中释放(这是释放外部资源的最好的地方)。

created time in 3 days

issue openedwuyc/notebook

Java线程的状态

新建(New)

实例化了一个线程对象,但还没有调用 start() 方法。

就绪(Ready)

调用了 start() 方法,但 CPU 还未执行此线程。

运行中(Running)

线程获取了 CPU 的使用权,正在执行此线程。

限期等待(Timed Waiting)

线程不会被分配到 CPU 的执行时间,在指定的时间后,线程会自动被唤醒。如 Thread.sleep() 方法。

无限期等待(Waiting)

线程不会被分配到 CPU 的执行时间,它们要等待被显式地唤醒,否则会处于无限期等待的状态。

阻塞(Blocked)

线程仍处于活动状态但当前没有资格运行,暂时停止运行。如 synchronized 关键字修饰方法或代码块(获取锁)时的状态。

结束(Terminated)

线程执行完毕,此线程生命周期结束。

created time in 3 days

issue openedwuyc/notebook

Java浅克隆与深克隆

浅克隆

浅克隆只复制当前对象的所有基本数据类型,以及相应的引用变量,但没有复制引用变量指向的实际对象,也就是只复制了引用变量的内存地址。

重写Object的clone方法,然后实现Cloneable接口。被克隆的类必须实现Cloneable接口,否则如果我们将在对象上调用clone()时,JVM将抛出CloneNotSupportedException。

Main.java

public class Main {
    public static void main(String[] args) {
        Person source = new Person();
        Person target = null;
        try {
            target = source.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        System.out.println("被克隆对象: " + source.hashCode() + "\n" +
                "被克隆对象内的dog属性: " + source.getDog().hashCode());
        System.out.println("===============================");
        System.out.println("克隆出来的对象: " + target.hashCode() + "\n" +
                "克隆出来的对象内的dog属性: " + target.getDog().hashCode());
    }
}

Person.java

public class Person implements Cloneable {
    private Dog dog;

    public Dog getDog() {
        return dog;
    }

    public Person() {
        this.dog = new Dog();
    }

    @Override
    protected Person clone() throws CloneNotSupportedException {
        return (Person) super.clone();
    }
}

输出

被克隆对象: 1163157884
被克隆对象内的dog属性: 1956725890
===============================
克隆出来的对象: 356573597
克隆出来的对象内的dog属性: 1956725890

我们发现两个对象的dog属性在内存中的标识并没有改变,因为浅克隆只拷贝了引用变量所指向堆内存的内存地址。

深克隆

深克隆彻底复制了当前对象,此对象与母对象在任何引用路径上都不存在共享的实例对象。也就是说深克隆把所有引用变量所指向的变量都拷贝了一份。

先把对象序列化到流中,然后再把对象从流中取出来,取出来的对象和原来的对象就没有联系了,引用变量所指向的地址也和原来的对象不同,这样就达到了深克隆的目的。不过这种方式的效率会比较低。 除了通过序列化成流进行深克隆,还可以通过手动设置属性的值实现克隆。 下面演示通过序列化成流进行深克隆。

Main.java

public class Main {
    public static void main(String[] args) {
        Person source = new Person();
        Person target = (Person) CloneUtils.clone(source);
        System.out.println("被克隆对象: " + source.hashCode() + "\n" +
                "被克隆对象内的dog属性: " + source.getDog().hashCode());
        System.out.println("===============================");
        System.out.println("克隆出来的对象: " + target.hashCode() + "\n" +
                "克隆出来的对象内的dog属性: " + target.getDog().hashCode());
    }
}

Person.java

public class Person implements Serializable {
    private Dog dog;

    public Dog getDog() {
        return dog;
    }

    public Person() {
        this.dog = new Dog();
    }
}

Dog.java

public class Dog implements Serializable {
    private String name;

    public Dog() {
        this.name = "大黄";
    }
}

CloneUtils.java

public class CloneUtils {

    public static Object clone(Object obj) {
        Object cloneObj = null;
        try {
            //写入字节流
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            ObjectOutputStream obs = new ObjectOutputStream(out);
            obs.writeObject(obj);
            obs.close();

            //分配内存,写入原始对象,生成新对象
            ByteArrayInputStream ios = new ByteArrayInputStream(out.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(ios);

            //返回生成的新对象
            cloneObj = ois.readObject();
            ois.close();

        } catch (Exception e) {
            e.printStackTrace();
        }
        return cloneObj;
    }
}

输出

被克隆对象: 1836019240
被克隆对象内的dog属性: 325040804
===============================
克隆出来的对象: 363771819
克隆出来的对象内的dog属性: 2065951873

由输出结果可以看出引用变量dog的值已经指向了另外的一个堆内存地址,即通过序列化成流实现了深克隆。

created time in 3 days

issue openedwuyc/notebook

Java中静态方法能否被重写

在Java中,子类可继承父类中的方法,而不需要重新编写相同的方法。但有时子类并不想原封不动地继承父类的方法,而是想作一定的修改,这就需要采用方法的重写(Override)。方法重写又称方法覆盖。

public class Test {
    public static void main(String[] args) {
        /**
         * 结论:
         * 静态方法可以被继承,但是不能被覆盖,即不能重写。
         */
        Son.staticMethod(); // 运行结果:Father staticMethod
    }
}

class Father {
    public static void staticMethod() {
        System.out.println("Father staticMethod");
    }
}

class Son extends Father {
}
public class Test {
    public static void main(String[] args) {
        Father.staticMethod(); // 运行结果:Father staticMethod
        /**
         * 结论:
         * 类执行了自己申明的静态方法。
         * 该子类实际上只是将父类中的同名静态方法进行了隐藏,而非重写。
         */
        Son.staticMethod(); // 运行结果:Son staticMethod
        Father father = new Son();
        /**
         * 结论:
         * 父类引用指向子类对象时,只会调用父类的静态方法。
         * 父类和子类中含有的其实是两个没有关系的方法,它们的行为也并不具有多态性。
         */
        father.staticMethod(); // 运行结果:Father staticMethod
    }
}

class Father {
    public static void staticMethod() {
        System.out.println("Father staticMethod");
    }
}

class Son extends Father {
    public static void staticMethod() {
        System.out.println("Son staticMethod");
    }
}

总结:

  1. 在Java中静态方法可以被继承,但是不能被覆盖,即不能重写。
  2. 如果子类中也含有一个返回类型、方法名、参数列表均与之相同的静态方法,那么该子类实际上只是将父类中的该同名方法进行了隐藏,而非重写。
  3. 父类引用指向子类对象时,只会调用父类的静态方法。所以,它们的行为也并不具有多态性。

created time in 3 days

push eventwuyc/personal-website

Wu YunCheng

commit sha 7dabacc5195c1391071c6ec931abcb61f7b6b998

update hello-world

view details

push time in 3 days

fork wuyc/personal-website

Code that'll help you kickstart a personal website that showcases your work as a software developer.

https://github.dev

fork in 3 days

startedgithub/personal-website

started time in 3 days

startedjekyll/jekyll

started time in 3 days

startedTryGhost/Ghost

started time in 3 days

startedytdl-org/youtube-dl

started time in 3 days

push eventwuyc/spring-boot-example

wuyc

commit sha f96da2d57ecfaab62d1735210f783d984df4f73b

update README.md

view details

push time in 3 days

push eventwuyc/spring-boot-example

wuyc

commit sha 7ac38fd773dd63fefc266425ede4eeb548cdd3f8

add spring-boot-swagger2

view details

push time in 3 days

startedswagger-api/swagger-core

started time in 3 days

push eventwuyc/xblog

wuyc

commit sha 6916ba78f1cbcf9bdef06c704f2ae587cd749f4f

add spring-cache and swagger2

view details

push time in 3 days

startedvuejs/vue-devtools

started time in 4 days

push eventwuyc/spring-boot-example

wuyc

commit sha 3088f00e7e9b1186a8d8f6f8edbef15d091dba9b

update README.md

view details

push time in 4 days

starteddianping/cat

started time in 4 days

startedsherlock-project/sherlock

started time in 4 days

startedfacebook/hermes

started time in 4 days

push eventwuyc/spring-boot-example

wuyc

commit sha 82d8474e570e0fd6871a9777e18b0aabb07307fc

add spring-boot-redis

view details

push time in 4 days

push eventwuyc/spring-boot-example

wuyc

commit sha 41ce93f98f0a4dc1a42e3f460848cff3b3488fa1

rename projects

view details

push time in 4 days

startedeclipse/org.aspectj

started time in 4 days

startedfxsjy/jieba

started time in 6 days

startedysc/word

started time in 6 days

startedrockyzhengwu/FoolNLTK

started time in 6 days

startedlionsoul2014/jcseg

started time in 6 days

startedNLPchina/ansj_seg

started time in 6 days

startedhuichen/sego

started time in 6 days

startedpwxcoo/chinese-xinhua

started time in 6 days

startedchinese-poetry/chinese-poetry

started time in 6 days

startedlenve/VBlog

started time in 6 days

startedUnknwon/go-fundamental-programming

started time in 7 days

startedgo-gitea/gitea

started time in 7 days

startediikira/BaiduPCS-Go

started time in 7 days

startedb3log/30-seconds-zh_CN

started time in 7 days

startedb3log/wide

started time in 7 days

startedb3log/pipe

started time in 7 days

started1c7/chinese-independent-developer

started time in 7 days

startedit-ebooks/it-ebooks-archive

started time in 7 days

startedwizardforcel/eloquent-js-3e-zh

started time in 7 days

startedKivy-CN/ml-for-humans-zh

started time in 7 days

startedwizardforcel/modern-java-zh

started time in 7 days

startedKivy-CN/Stanford-CS-229-CN

started time in 7 days

startedwizardforcel/think-dast-zh

started time in 7 days

startedwizardforcel/think-os-zh

started time in 7 days

startedwizardforcel/sorecol

started time in 7 days

startedwizardforcel/data-science-notebook

started time in 7 days

push eventwuyc/vue2-learning

wuyc

commit sha 1c037799347be96345bfdf9c21b06b5d4f8587f9

add 条件渲染

view details

push time in 7 days

startedmuan/emojilib

started time in 7 days

startedmuan/emoji

started time in 7 days

push eventwuyc/vue2-learning

wuyc

commit sha f7102d90d8f2b0e6aaef9286ce56a3826432bbeb

rename folder

view details

push time in 7 days

push eventwuyc/vue2-learning

wuyc

commit sha 46910bfcef6deaad9ada1040b402340c1a2d9f3d

add Class与Style绑定

view details

push time in 7 days

push eventwuyc/vue2-learning

wuyc

commit sha 559af3b299f08e8005c4bf21011d795a30fabb67

add computed

view details

push time in 7 days

startedpapercss/papercss

started time in 7 days

push eventwuyc/vue2-learning

wuyc

commit sha cbc10702f4f03255e7b67911b97507712bd6b819

add 模版语法

view details

push time in 7 days

push eventwuyc/vue2-learning

wuyc

commit sha 09aff6a2e84ada970c67528acc8923ddf942d7f0

add lifecycle hooks

view details

push time in 7 days

push eventwuyc/vue2-learning

wuyc

commit sha 1c4eb154af03df809528c80fb55226637bb859da

add vue watch

view details

push time in 7 days

push eventwuyc/vue2-learning

wuyc

commit sha 3c0a2c1aa9ee0a54026722121f41789653e15580

add Object.freeze()

view details

push time in 7 days

push eventwuyc/vue2-learning

wuyc

commit sha 4dd3e2cbe4c9fbed1f288be82fcfd18087bfef47

add component

view details

push time in 7 days

push eventwuyc/vue2-learning

wuyc

commit sha badef2ece03eea0355b7cfd7cfc465e6f739c551

add v-model

view details

push time in 7 days

push eventwuyc/vue2-learning

wuyc

commit sha 74e4e7396e2a1bbb868b4cf9914ad9742ed4e7cd

add v-on

view details

push time in 7 days

push eventwuyc/vue2-learning

wuyc

commit sha 431aa7d703025967650d2e82f1765c870b2ca9a2

add v-for

view details

push time in 7 days

more