45度仰望的压顶砖

45度仰望的压顶砖 查看完整档案

成都编辑西安科技大学  |  资环 编辑Strikingly  |  Java Ruby软件工程师 编辑填写个人主网站
编辑

代码要优雅

个人动态

45度仰望的压顶砖 赞了回答 · 9月1日

解决flex如何设置子元素间距?

使用 justify-content: space-between; 来设置间距。子元素通过 flex-basis 调节被隔开的间距,下面的示例是以百分比为例,也可以指定像素宽度。
https://codepen.io/jylzs369/p...

关注 6 回答 4

45度仰望的压顶砖 赞了回答 · 8月20日

git里面怎样删除某一条commit的记录呢?已经push成功了那种

git rebase

把那一条和其他的压缩到一起,或者删除,再强制推送。

关注 7 回答 6

45度仰望的压顶砖 回答了问题 · 8月19日

解决网站金币兑换码如何设计才简洁且安全?

用md5或者uuid之类的都行吧

关注 4 回答 4

45度仰望的压顶砖 回答了问题 · 8月17日

Oracle 如何判断 某个字段连续下降(根据时间判断)了三次 并且最后一次值小于60?

把学生成绩查出来, 保存到一个list1里面, 然后把list1降序排列得到list2,比较两个list里面的东西顺序是否相等,如果相等,那么is_countine_down=yes,否则为no.
比如从数据库查出来的数据list1=[1,3,2],排序后得到list2=[3,2,1],那么list1和list2里面内容的顺序不相等,说明没有连续下降.

这里面有几个特殊情况,比如成绩不到3次,或者3次成绩都一样, 这个就看你的业务需求怎么处理了

关注 4 回答 3

45度仰望的压顶砖 收藏了文章 · 8月17日

fastjson转换json时,碰到的那些首字母大小写转换的坑!

某年某月的某一天,本汪在某个奇葩的公司,接手了某个奇葩的项目,遇到了一些奇葩的事情,就掉进关于fastjson做bean to json转换时,那些关于首字符大小写的坑。

这个奇葩项目里面,api接口定义的是天马行空、云山雾绕,api里面的字段定义更是五花八门、千奇百怪,完全没有规则可言,都可以开个不符合规范的案例博物馆了。下面3个坑里面举的例子,那些奇葩的名称定义,都是在项目里面真实存在的,如有雷同,纯属不幸。

在api的处理过程里面,bean转换成json时,我们总是希望字段名是什么样的,转换成json就应该是什么样的,然而现实总是残酷的。

坑0x01 全大写的键名

这种情况在api定义里面很常见,如下:

private String TEST = “1”;
public String getTEST() { return this.TEST}

在fastjson转换后,变成:json{ “tEST”: ”1”},与预期不符,首字母变成小写了。

原因:
fastjson在将bean转换为json时,先取出对应的methodName: getTEST,从methodName的第4个字符开始,取出propertyName:TEST,它默认认为这个propertyName的首字母是在定义getter的时候被写成大写的,现在要转成小写:
propertyName = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4);
结果,正确的TEST就被转成错误的tEST了。

解决:
解决这个问题比较简单,在转换前,多加一行代码:
TypeUtils.compatibleWithJavaBean =true;
此时,fastjson会先判断propertyName长度大于1、且头两个字符都是大写时,不做转换:

if(name.length() > 1 && Character.isUpperCase(name.charAt(1)) && Character.isUpperCase(name.charAt(0))){
    return name;
}

也就是说,连续大写开头的propertyName,在转换时,会保持原样。

坑0x02 第一个字符大写,第二个字符不是大写的键名

按坑0x01里面讲的设置TypeUtils.compatibleWithJavaBean=true后,如果有如下的键名,依然会出问题:

private String T_EST = “1”;
private String Test = “2”;
public String getT_EST() { return this.T_EST}
public String getTest() { return this.Test}

在fastjson转换后,变成:{ “t_EST”: ”1”, “test”:”2”},又是一个坑。

原因:
从坑0x01的最后一段代码可以看出,要想fastjson不改首字符,除了需要设置compatibleWithJavaBean外,propertyName的第二个字符也必须是大写。在坑0x02这里,两个propertyName都无法通过判断,首字母就要被转换成小写了。

解决
要解决这个问题,在转换前,必须再加一行代码:
TypeUtils.compatibleWithFieldName = true;
Fastjson里面对应的处理代码如下:

if(compatibleWithFieldName){
    if(!fieldCacheMap.containsKey(propertyName)){
        String tempPropertyName = methodName.substring(fromIdx);
        return fieldCacheMap.containsKey(tempPropertyName) ? tempPropertyName : propertyName;
    }
}

fastjson在按坑0x01所述的过程处理完propertyName后,propertyName的首字符被转换为小写,然后会在bean的的field列表里面找一遍转换后的名称。如果找不到,就从methodName里面重新取get后面的字符串,然后再到field列表里面找一遍,如果找到,就用原propertyName,如果找不到就用首字符被转换的名称。

坑0x03 首字符小写,第二个字符大写的键名

这个坑与lombok相关,严格来说,应该是lombok挖的坑。
如上所述,就算你按坑0x01、0x02设置了,如果在工程里面用lombok时,有如下的键名定义,依然要被坑:

@Getter
private String iPhone = “1”;

在fastjson转换后,变成:{ “IPhone”: ”1” },首字符变大写了,WTF!

原因:
Lombok在自动生成getter的时候,会把propertyName的第一的字母改成大写,等同如下代码:

public String getIPhone(){return this.iPhone;}

问题就出在这个转换上,get后面紧跟的字符变成大写了。在eclipse、idea里面,如果自动生成代码,get后的字符是小写。

如前面坑0x01最后一段代码,fastjson在处理getter时,会判断前两个字符是不是大写,如果是的话,就保持原样,取到的propertyName就成了:IPhone。但是在field列表里面,对应的propertyName是iPhone,就算开启了compatibleWithFieldName,fastjson用从getter中解析出来的IPhone,在field列表里面也找不到对应值,也只能保持IPhone这个名称了。

另外,就算在属性前用了@JSONField(name = “iPhone”)注解,因为fastjson用从getter解析出来的propertyName找不到对应的field,也无法读出该field对应的注解,这个注解也是无效的。

解决

  1. 碰到这种propertyName,就不要用lombok生成的getter/setter了,自己写,保持首字符小写,如:getiPhone/setiPhone,只要在代码里面有这两个方法,lombok就不会自动生成,lombok是按忽略大小写后的propertyName判断的。
  2. 把propertyName第二个字符改成小写,或者重新取个名字,并用@JSONField注明正确的名称,如:

    @Getter
    @ JSONField(name = “iPhone”)
    private String iphone = “1”;

    关于lombok的这个坑,可以参考:http://xxxx.ooo/2017/using-lo...,lombok的作者也是死鸭子嘴硬,被提了n多的issue,就是不改。

以上,就是本汪在项目里面填坑的过程,不要问我为什么api里面的字段名称会定义成这副模样,本汪也不知道,汪!汪!!汪!!!

关于fastjson的代码,都在TypeUtils.computeGetters里面。
@JSONField是个好东西,不嫌麻烦的话,就都加上吧。

查看原文

45度仰望的压顶砖 赞了文章 · 8月17日

fastjson转换json时,碰到的那些首字母大小写转换的坑!

某年某月的某一天,本汪在某个奇葩的公司,接手了某个奇葩的项目,遇到了一些奇葩的事情,就掉进关于fastjson做bean to json转换时,那些关于首字符大小写的坑。

这个奇葩项目里面,api接口定义的是天马行空、云山雾绕,api里面的字段定义更是五花八门、千奇百怪,完全没有规则可言,都可以开个不符合规范的案例博物馆了。下面3个坑里面举的例子,那些奇葩的名称定义,都是在项目里面真实存在的,如有雷同,纯属不幸。

在api的处理过程里面,bean转换成json时,我们总是希望字段名是什么样的,转换成json就应该是什么样的,然而现实总是残酷的。

坑0x01 全大写的键名

这种情况在api定义里面很常见,如下:

private String TEST = “1”;
public String getTEST() { return this.TEST}

在fastjson转换后,变成:json{ “tEST”: ”1”},与预期不符,首字母变成小写了。

原因:
fastjson在将bean转换为json时,先取出对应的methodName: getTEST,从methodName的第4个字符开始,取出propertyName:TEST,它默认认为这个propertyName的首字母是在定义getter的时候被写成大写的,现在要转成小写:
propertyName = Character.toLowerCase(methodName.charAt(3)) + methodName.substring(4);
结果,正确的TEST就被转成错误的tEST了。

解决:
解决这个问题比较简单,在转换前,多加一行代码:
TypeUtils.compatibleWithJavaBean =true;
此时,fastjson会先判断propertyName长度大于1、且头两个字符都是大写时,不做转换:

if(name.length() > 1 && Character.isUpperCase(name.charAt(1)) && Character.isUpperCase(name.charAt(0))){
    return name;
}

也就是说,连续大写开头的propertyName,在转换时,会保持原样。

坑0x02 第一个字符大写,第二个字符不是大写的键名

按坑0x01里面讲的设置TypeUtils.compatibleWithJavaBean=true后,如果有如下的键名,依然会出问题:

private String T_EST = “1”;
private String Test = “2”;
public String getT_EST() { return this.T_EST}
public String getTest() { return this.Test}

在fastjson转换后,变成:{ “t_EST”: ”1”, “test”:”2”},又是一个坑。

原因:
从坑0x01的最后一段代码可以看出,要想fastjson不改首字符,除了需要设置compatibleWithJavaBean外,propertyName的第二个字符也必须是大写。在坑0x02这里,两个propertyName都无法通过判断,首字母就要被转换成小写了。

解决
要解决这个问题,在转换前,必须再加一行代码:
TypeUtils.compatibleWithFieldName = true;
Fastjson里面对应的处理代码如下:

if(compatibleWithFieldName){
    if(!fieldCacheMap.containsKey(propertyName)){
        String tempPropertyName = methodName.substring(fromIdx);
        return fieldCacheMap.containsKey(tempPropertyName) ? tempPropertyName : propertyName;
    }
}

fastjson在按坑0x01所述的过程处理完propertyName后,propertyName的首字符被转换为小写,然后会在bean的的field列表里面找一遍转换后的名称。如果找不到,就从methodName里面重新取get后面的字符串,然后再到field列表里面找一遍,如果找到,就用原propertyName,如果找不到就用首字符被转换的名称。

坑0x03 首字符小写,第二个字符大写的键名

这个坑与lombok相关,严格来说,应该是lombok挖的坑。
如上所述,就算你按坑0x01、0x02设置了,如果在工程里面用lombok时,有如下的键名定义,依然要被坑:

@Getter
private String iPhone = “1”;

在fastjson转换后,变成:{ “IPhone”: ”1” },首字符变大写了,WTF!

原因:
Lombok在自动生成getter的时候,会把propertyName的第一的字母改成大写,等同如下代码:

public String getIPhone(){return this.iPhone;}

问题就出在这个转换上,get后面紧跟的字符变成大写了。在eclipse、idea里面,如果自动生成代码,get后的字符是小写。

如前面坑0x01最后一段代码,fastjson在处理getter时,会判断前两个字符是不是大写,如果是的话,就保持原样,取到的propertyName就成了:IPhone。但是在field列表里面,对应的propertyName是iPhone,就算开启了compatibleWithFieldName,fastjson用从getter中解析出来的IPhone,在field列表里面也找不到对应值,也只能保持IPhone这个名称了。

另外,就算在属性前用了@JSONField(name = “iPhone”)注解,因为fastjson用从getter解析出来的propertyName找不到对应的field,也无法读出该field对应的注解,这个注解也是无效的。

解决

  1. 碰到这种propertyName,就不要用lombok生成的getter/setter了,自己写,保持首字符小写,如:getiPhone/setiPhone,只要在代码里面有这两个方法,lombok就不会自动生成,lombok是按忽略大小写后的propertyName判断的。
  2. 把propertyName第二个字符改成小写,或者重新取个名字,并用@JSONField注明正确的名称,如:

    @Getter
    @ JSONField(name = “iPhone”)
    private String iphone = “1”;

    关于lombok的这个坑,可以参考:http://xxxx.ooo/2017/using-lo...,lombok的作者也是死鸭子嘴硬,被提了n多的issue,就是不改。

以上,就是本汪在项目里面填坑的过程,不要问我为什么api里面的字段名称会定义成这副模样,本汪也不知道,汪!汪!!汪!!!

关于fastjson的代码,都在TypeUtils.computeGetters里面。
@JSONField是个好东西,不嫌麻烦的话,就都加上吧。

查看原文

赞 10 收藏 6 评论 5

45度仰望的压顶砖 回答了问题 · 6月12日

解决Java中覆写父类方法的问题

new SuperClass().outerFunc();

关注 3 回答 2

45度仰望的压顶砖 赞了文章 · 2019-12-23

springboot整合elasticsearch

最近在学习es,起码要先有个es环境吧,然后再是整合到代码中使用一下,毕竟只有实践才会有深刻的记忆,这就是所谓的经验啊,下面开始吧,本文分两部分,第一部分配置es环境,第二部分整合到springboot中进行简单的操作,本人也是初次学习,如有错误欢迎指出修正,
本文示例代码不严谨,仅供参考
一、 docker 安装es
1、 拉取镜像

# 搜索镜像
docker search elasticsearch


# 拉取镜像
docker pull elasticsearch

2、创建本地映射文件,我的本地es映射主目录是/docker/es

cd /docker/es
mkdir data
mkdir plugins
mkdir config
cd config
vim es.yml

3、es.yml文件内容如下

cluster.name: elasticsearch-cluster
node.name: es-node1
network.bind_host: 0.0.0.0
network.publish_host: localhost
http.port: 9200
transport.tcp.port: 9300
http.cors.enabled: true
http.cors.allow-origin: "*"
node.master: true 
node.data: true  

4、创建并启动容器

docker run -e ES_JAVA_OPTS="-Xms256m -Xmx256m" -d  -p 9200:9200 -p 9300:9300 -p 5601:5601 --restart=always -v /docker/es/config/es.yml:/usr/share/elasticsearch/config/elasticsearch.yml -v /docker/es/plugins1:/usr/share/elasticsearch/plugins  -v /docker/es/data1:/usr/share/elasticsearch/data --name my_es elasticsearch
   
# 命令解析
-e 指定运行内存
-d 后台运行
-p 指定端口
--restart=always 启动docker自动启动容器
-v 指定映射文件
--name 容器名称
最后的elasticsearch是刚才拉取的镜像名称

5、浏览器插件访问es,telnet9200可以通说明都可以说明搭建成功, http://127.0.0.1:9200/
二、 整合springboot里面去
1、pom.xml

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
            <version>2.1.3.RELEASE</version>
        </dependency>

2、application.yml

spring:
  data:
    elasticsearch:
      cluster-name: elasticsearch-cluster
      cluster-nodes: 127.0.0.1:9300
      repositories:
        enabled: true

3、 ElasticSearchConfig

@SpringBootConfiguration
public class ElasticSearchConfig {
    @PostConstruct
    void init() {
        System.setProperty("es.set.netty.runtime.available.processors", "false");
    }
}

4、 bean

ESDocument
@Data
@Document(indexName = "poms", type = "content")
public class ESDocument {
    @Id
    private String id;

    @Field(analyzer = "ik_smart", searchAnalyzer = "ik_smart")
    private String name;

    private String projectId;

    public ESDocument(String id, String name, String projectId) {
        this.id = id;
        this.name = name;
        this.projectId = projectId;
    }
  public ESDocument() {
        
    }
}
/**
 * @Document 作用在类,标记实体类为文档对象,一般有两个属性
 * indexName:对应索引库名称
 * type:对应在索引库中的类型
 * shards:分片数量,默认5
 * replicas:副本数量,默认1
 * @Id 作用在成员变量,标记一个字段作为id主键
 * @Field 作用在成员变量,标记为文档的字段,并指定字段映射属性:
 * type:字段类型,是枚举:FieldType,可以是text、long、short、date、integer、object等
 * text:存储数据时候,会自动分词,并生成索引
 * keyword:存储数据时候,不会分词建立索引
 * Numerical:数值类型,分两类
 * 基本数据类型:long、integer、short、byte、double、float、half_float
 * 浮点数的高精度类型:scaled_float
 * 需要指定一个精度因子,比如10或100。elasticsearch会把真实值乘以这个因子后存储,取出时再还原。
 * Date:日期类型
 * elasticsearch可以对日期格式化为字符串存储,但是建议我们存储为毫秒值,存储为long,节省空间。
 * index:是否索引,布尔类型,默认是true
 * store:是否存储,布尔类型,默认是false
 * analyzer:分词器名称,这里的ik_max_word即使用ik分词器
 * 
*/

5、DocumentSearchService

    void save(ESDocument esDocument);
    void delete(String id);
    void getById(String id);
    void getByName(String name,String projectId);

6.DocumentSearchServiceImpl

    @Autowired
    private DocumentSearchRepository documentSearchRepository;

    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

@Override
    public void save(ESDocument esDocument) {
        ESDocument save = documentSearchRepository.save(esDocument);
        System.out.println(save.toString());
    }

    @Override
    public void delete(String id) {
        documentSearchRepository.deleteById(id);
    }

    @Override
    public void getById(String id) {
        ESDocument esDocument = documentSearchRepository.findById(id).orElse(new ESDocument());
        System.out.println(esDocument.toString());

    }

    @Override
    public void getByName(String name, String projectId) {
        SearchQuery searchQuery = new NativeSearchQueryBuilder().withQuery(matchQuery("name",name)).withQuery(matchQuery("projectId",projectId)).build();
        List<ESDocument> esDocuments = elasticsearchTemplate.queryForList(searchQuery, ESDocument.class);
        esDocuments.forEach(e-> System.out.println(e.toString()));
    }

7、DocumentSearchRepository

@Repository
public interface DocumentSearchRepository extends ElasticsearchRepository<ESDocument,String> {
}

8、test

@RunWith(SpringRunner.class)
@SpringBootTest
public class EsTests {

    @Autowired
    private DocumentSearchService documentSearchService;

    @Test
    public void save() {
      documentSearchService.save(new ESDocument(UUID.randomUUID().toString(),"name1","1"));
    }
    @Test
    public void getById(){
        documentSearchService.getById("98c717e2-0e17-4887-86f6-e9cd347f97f7");
    }
    @Test
    public void getByName(){
        documentSearchService.getByName("name1","1");
    }
    @Test
    public void delete(){
        documentSearchService.delete("98c717e2-0e17-4887-86f6-e9cd347f97f7");
    }

}

9、到这基本整合就结束了,展示了一个增删查,没有修改,修改本人暂时也没有看。,待后续学习继续分享经验吧,下面说一下整合过程遇到的坑

坑1

报错信息
java.lang.IllegalStateException: Failed to load ApplicationContext

    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:125)
    at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:108)
    at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:190)
    at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:132)
    at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:246)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:227)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:289)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:291)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:246)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'elasticsearchClient' defined in class path resource [org/springframework/boot/autoconfigure/data/elasticsearch/ElasticsearchAutoConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.elasticsearch.client.transport.TransportClient]: Factory method 'elasticsearchClient' threw exception; nested exception is java.lang.IllegalStateException: availableProcessors is already set to [8], rejecting [8]
    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:590)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1247)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1096)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:535)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:495)
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:317)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199)
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:759)
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:869)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:550)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:762)
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:398)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:330)
    at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:139)
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99)
    at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:117)
    ... 24 more
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.elasticsearch.client.transport.TransportClient]: Factory method 'elasticsearchClient' threw exception; nested exception is java.lang.IllegalStateException: availableProcessors is already set to [8], rejecting [8]
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185)
    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:582)
    ... 41 more
Caused by: java.lang.IllegalStateException: availableProcessors is already set to [8], rejecting [8]
    at io.netty.util.NettyRuntime$AvailableProcessorsHolder.setAvailableProcessors(NettyRuntime.java:51)
    at io.netty.util.NettyRuntime.setAvailableProcessors(NettyRuntime.java:87)
    at org.elasticsearch.transport.netty4.Netty4Utils.setAvailableProcessors(Netty4Utils.java:85)
    at org.elasticsearch.transport.netty4.Netty4Transport.<init>(Netty4Transport.java:140)
    at org.elasticsearch.transport.Netty4Plugin.lambda$getTransports$0(Netty4Plugin.java:93)
    at org.elasticsearch.client.transport.TransportClient.buildTemplate(TransportClient.java:177)
    at org.elasticsearch.client.transport.TransportClient.<init>(TransportClient.java:257)
    at org.springframework.data.elasticsearch.client.TransportClientFactoryBean$SpringDataTransportClient.<init>(TransportClientFactoryBean.java:234)
    at org.springframework.data.elasticsearch.client.TransportClientFactoryBean.buildClient(TransportClientFactoryBean.java:103)
    at org.springframework.data.elasticsearch.client.TransportClientFactoryBean.afterPropertiesSet(TransportClientFactoryBean.java:98)
    at org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration.elasticsearchClient(ElasticsearchAutoConfiguration.java:59)
    at org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration$$EnhancerBySpringCGLIB$$18bf6a65.CGLIB$elasticsearchClient$0(<generated>)
    at org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration$$EnhancerBySpringCGLIB$$18bf6a65$$FastClassBySpringCGLIB$$49c9363b.invoke(<generated>)
    at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:228)
    at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:361)
    at org.springframework.boot.autoconfigure.data.elasticsearch.ElasticsearchAutoConfiguration$$EnhancerBySpringCGLIB$$18bf6a65.elasticsearchClient(<generated>)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154)
    ... 42 more
解决

这个错误解决的时候主要是百度的下面这一行,然后处理思路是把netty的一个东西关闭,具体还没有深入了解

java.lang.IllegalStateException: availableProcessors is already set to [8], rejecting [8]

解决
添加配置类
@SpringBootConfiguration
public class ElasticSearchConfig {
    @PostConstruct
    void init() {
        System.setProperty("es.set.netty.runtime.available.processors", "false");
    }
}

坑2

报错信息
NoNodeAvailableException[None of the configured nodes are available: [{#transport#-1}{b1p9-W8ISSK9ibEqyd3bgg}{127.0.0.1}{127.0.0.1:9200}]
]
    at org.elasticsearch.client.transport.TransportClientNodesService.ensureNodesAreAvailable(TransportClientNodesService.java:347)
    at org.elasticsearch.client.transport.TransportClientNodesService.execute(TransportClientNodesService.java:245)
    at org.elasticsearch.client.transport.TransportProxyClient.execute(TransportProxyClient.java:59)
    at org.elasticsearch.client.transport.TransportClient.doExecute(TransportClient.java:366)
    at org.elasticsearch.client.support.AbstractClient.execute(AbstractClient.java:408)
    at org.elasticsearch.client.support.AbstractClient.execute(AbstractClient.java:397)
    at org.elasticsearch.client.support.AbstractClient$IndicesAdmin.execute(AbstractClient.java:1250)
    at org.elasticsearch.client.support.AbstractClient$IndicesAdmin.exists(AbstractClient.java:1272)
    at org.springframework.data.elasticsearch.core.ElasticsearchTemplate.indexExists(ElasticsearchTemplate.java:650)
    at org.springframework.data.elasticsearch.core.ElasticsearchTemplate.createIndexIfNotCreated(ElasticsearchTemplate.java:945)
    at org.springframework.data.elasticsearch.core.ElasticsearchTemplate.createIndex(ElasticsearchTemplate.java:189)
    at com.cxt.demo.es.EsTests.contextLoads(EsTests.java:19)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
    at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
    at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
    at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
    at org.springframework.test.context.junit4.statements.RunBeforeTestExecutionCallbacks.evaluate(RunBeforeTestExecutionCallbacks.java:73)
    at org.springframework.test.context.junit4.statements.RunAfterTestExecutionCallbacks.evaluate(RunAfterTestExecutionCallbacks.java:83)
    at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
    at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
    at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
    at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:251)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:97)
    at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
    at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
    at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
    at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
    at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
    at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
    at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
    at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
    at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:190)
    at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
    at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
    at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
    at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
    at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
解决

出现这个问题一般就是查看端口,集群名称,ip地址是否写对,这里有个地方 需要注意,
<font color="#FF0000">java链接端口默认9300,不是es的9200</font>

在这里插入图片描述然后其他注意的地方就是cluster-name 这个名称要和es.yml配置文件中cluster.name名称一致
,即都elasticsearch-cluster

在这里插入图片描述
在这里插入图片描述
最后 附上源码地址供参考,本章内容注意切换到es分支
https://github.com/TianPuJun/...

查看原文

赞 1 收藏 0 评论 0

45度仰望的压顶砖 收藏了文章 · 2019-03-23

SpringBoot多模块项目实践(Multi-Module)

序言:
比起传统复杂的单体工程,使用Maven的多模块配置,可以帮助项目划分模块,鼓励重用,防止POM变得过于庞大,方便某个模块的构建,而不用每次都构建整个项目,并且使得针对某个模块的特殊控制更为方便。接下来,本文将重点阐述SpringBoot在Maven环境的多模块构建过程。本项目传送门: SpringBoot-Integration

一、创建聚合父工程

  • 1.首先使用 Spring Initializr 来快速创建好一个Maven工程。然后删除无关的文件,只需保留pom.xml 文件。

聚合父工程

    1. 然后在 pom.xml 里面声明该父工程包含的子模块。(其它信息就不逐一讲述了,诸如继承SpringBoot官方父工程以及统一依赖管理 请查看下面的注释说明)

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <!-- 基本信息 -->
    <description>SpringBoot 多模块构建示例</description>
    <modelVersion>4.0.0</modelVersion>
    <name>springboot-integration</name>
    <packaging>pom</packaging>

    <!-- 项目说明:这里作为聚合工程的父工程 -->
    <groupId>com.hehe</groupId>
    <artifactId>springboot-integration</artifactId>
    <version>1.0.0.RELEASE</version>

    <!-- 继承说明:这里继承SpringBoot提供的父工程 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.7.RELEASE</version>
        <relativePath/>
    </parent>

    <!-- 模块说明:这里声明多个子模块 -->
    <modules>
        <module>mm-web</module>
        <module>mm-service</module>
        <module>mm-repo</module>
        <module>mm-entity</module>
    </modules>

    <!-- 版本说明:这里统一管理依赖的版本号 -->
    <dependencyManagement>
        <dependencies>

            <dependency>
                <groupId>com.hehe</groupId>
                <artifactId>mm-web</artifactId>
                <version>0.0.1-SNAPSHOT</version>
            </dependency>
            <dependency>
                <groupId>com.hehe</groupId>
                <artifactId>mm-service</artifactId>
                <version>0.0.1-SNAPSHOT</version>
            </dependency>
            <dependency>
                <groupId>com.hehe</groupId>
                <artifactId>mm-repo</artifactId>
                <version>0.0.1-SNAPSHOT</version>
            </dependency>
            <dependency>
                <groupId>com.hehe</groupId>
                <artifactId>mm-entity</artifactId>
                <version>0.0.1-SNAPSHOT</version>
            </dependency>

            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>5.1.42</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

</project>

二、创建子模块(module)

注:这里是使用IDEA来创建子模块,使用Eclipse的小伙伴可通过 Spring Initializr 构建,然后复制去进去父工程根目录即可。

  • 1.对着父工程右键 - New - Module - > 输入 mm-web

  • 2.对着父工程右键 - New - Module - > 输入 mm-service

  • 3.对着父工程右键 - New - Module - > 输入 mm-repo

  • 4.对着父工程右键 - New - Module - > 输入 mm-entity

  • 1~4 步骤完成后,分别调整它们的pom.xml 以继承上面的父工程。

例如mm-web模块的pom.xml 需要改造成这样:


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <!-- 基本信息 -->
    <groupId>com.hehe</groupId>
    <artifactId>mm-web</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
    <name>mm-web</name>

    <!-- 继承本项目的父工程 -->
    <parent>
        <groupId>com.hehe</groupId>
        <artifactId>springboot-integration</artifactId>
        <version>1.0.0.RELEASE</version>
    </parent>

    <!-- Web模块相关依赖 -->
    <dependencies>
        <dependency>
            <groupId>com.hehe</groupId>
            <artifactId>mm-service</artifactId>
        </dependency>
        <dependency>
            <groupId>com.hehe</groupId>
            <artifactId>mm-entity</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

</project>

三、编写子模块代码

  • 1. 控制层(mm-web)

结构图

启动类 :MmWebApplication.java (mm-web)

@SpringBootApplication
public class MmWebApplication {

    public static void main(String[] args) {
        SpringApplication.run(MmWebApplication.class, args);
    }
}

控制器:UserController.java (mm-web )

@RestController
@RequestMapping("/user/*")
public class UserController {

    @Autowired
    UserService userService;

    @GetMapping("list")
    public R list() {
        try {
           return R.isOk().data(userService.list());
        } catch (Exception e) {
            return R.isFail(e);
        }

    }

}

配置文件:application.yml (mm-web)

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/socks?useSSL=false
    username: root
    password: root
    driver-class-name: com.mysql.jdbc.Driver
  • 2. 业务层(mm-service)

结构图

实现类:UserServiceImpl.java (mm-service)

@Service
public class UserServiceImpl  implements UserService {

    @Autowired
    UserRepository userRepository;

    @Override
    public List<User> list() {
        return userRepository.findAll();
    }
}
  • 3. 数据层(mm-repo)

结构图

数据层代码:UserRepository.java (mm-repo)

public interface UserRepository extends JpaRepository<User,String> {
}
  • 4. mm-entity (实体模型层)

结构图

R.java 作为统一返回的Bean对象

package com.hehe.integration.common;

import java.io.Serializable;

public class R<T> implements Serializable {

    private static final long serialVersionUID = -4577255781088498763L;
    private static final int OK = 0;
    private static final int FAIL = 1;
    private static final int UNAUTHORIZED = 2;

    private T data; //服务端数据
    private int status = OK; //状态码
    private String msg = ""; //描述信息

    //APIS
    public static R isOk(){
        return new R();
    }
    public static R isFail(){
        return new R().status(FAIL);
    }
    public static R isFail(Throwable e){
        return isFail().msg(e);
    }
    public R msg(Throwable e){
        this.setMsg(e.toString());
        return this;
    }
    public R data(T data){
        this.setData(data);
        return this;
    }
    public R status(int status){
       this.setStatus(status);
       return this;
    }


    //Constructors
    public R() {

    }

    //Getter&Setters
  
}

@Entity
@Table(name = "T_USER")
public class User {

    @Id
    @Column(name = "USERID")
    private String userId;
    @Column(name = "USERNAME")
    private String username;
    @Column(name = "PASSWORD")
    private String password;
   
    //Getter&Setters
}

三、运行项目

为了更好的学习效果,建议先下载本项目,在IDE运行成功之后,然后再由自己手工敲一遍。

具体步骤:

  • 1.首先下载好 springboot-socks,然后打开springboot-integration 工程。

image.png

  • 2.安装Mysql数据库,然后创建数据库socks,并添加表t_user,插入数据如图:

sock.t_user.PNG

    1. 配置好整个项目之后,这里只需要运行mm-web模块下的MmWebApplication的启动类就可以了,如正常启动后,访问http://localhost:8080 可查询到用户列表信息。如下图:

image.png

四、运维部署(多模块打包)

1. 添加打包插件

注意:多模块项目仅仅需要在启动类所在的模块添加打包插件即可!!不要在父类添加打包插件,因为那样会导致全部子模块都使用spring-boot-maven-plugin的方式来打包(例如BOOT-INF/com/hehe/xx),而mm-web模块引入mm-xx 的jar 需要的是裸露的类文件,即目录格式为(/com/hehe/xx)。

本案例的启动模块是 mm-web , 只需在它的pom.xml 添加打包插件(spring-boot-maven-plugin):

  
    <!--多模块打包:只需在启动类所在模块的POM文件:指定打包插件 -->
    <build>
        <plugins>
            <plugin>
                <!--该插件主要用途:构建可执行的JAR -->
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

2. 打包工程

首先在IDE打开Maven插件,然后在聚合父工程spring-boot-integration中点击 clean ,然后点击 package 进行打包。如图:

打包效果如下:

[INFO] ------------------------------------------------------------------------
[INFO] Reactor Summary:
[INFO] 
[INFO] spring-boot-integration ............................ SUCCESS [  0.000 s]
[INFO] mm-entity .......................................... SUCCESS [  1.915 s]
[INFO] mm-repo ............................................ SUCCESS [  0.235 s]
[INFO] mm-service ......................................... SUCCESS [  0.218 s]
[INFO] mm-web ............................................. SUCCESS [  0.891 s]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.798 s
[INFO] Finished at: 2017-10-18T17:17:02+08:00
[INFO] Final Memory: 35M/300M
[INFO] ------------------------------------------------------------------------

打包地址默认在Target目录:

3. 启动项目

通过命令行启动项目:

xx\mm-web\target>java -jar mm-web-0.0.1-SNAPSHOT.jar

启动效果如下:

全文到此,想获取更多关于SpringBoot 有趣的案例 请戳 SpringBoot-从入门到上瘾 .

Github源码: SpringBoot-Integration

查看原文

45度仰望的压顶砖 收藏了文章 · 2018-11-12

Linux TTY/PTS概述

当我们在键盘上敲下一个字母的时候,到底是怎么发送到相应的进程的呢?我们通过ps、who等命令看到的类似tty1、pts/0这样的输出,它们的作用和区别是什么呢?

TTY历史

支持多任务的计算机出现之前

在计算机出来以前,人们就已经在使用一种叫teletype的设备,用来相互之间传递信息,看起来像下面这样:

+----------+     Physical Line     +----------+
| teletype |<--------------------->| teletype |
+----------+                       +----------+

两个teletype之间用线连接起来,线两端可能也有类似于调制解调器之类的设备(这里将它们忽略),在一端的teletype上敲键盘时,相应的数据会发送到另一端的teletype,具体功能是干什么的,我也不太了解。(我脑袋里面想到画面是在一端敲字,另一端打印出来)

这些都是老古董了,完全没接触过,所以只能简单的推测。

支持多任务的计算机出现之后

等到计算机支持多任务后,人们想到把这些teletype连到计算机上,作为计算机的终端,从而可以操作计算机。

使用teletype的主要原因有两个(个人见解):

  • 现实中已经存在了大量不同厂商的teletype,可以充分利用现有资源

  • teletype的相关网络已经比较成熟,连起来方便

于是连接就发展成这样:

                                                                      +----------+ 
+----------+   +-------+     Physical Line     +-------+   +------+   |          |
| Terminal |<->| Modem |<--------------------->| Modem |<->| UART |<->| Computer |
+----------+   +-------+                       +-------+   +------+   |          |
                                                                      +----------+
  • 左边的Terminal就是各种各样的teletype

  • 物理线路两边用上了Modem,就是我们常说的“猫”,那是因为后来网络已经慢慢的变发达了,大家可以共享连接了。(大概推测,可能不对)

  • UART可以理解为将teletype的信号转换成计算机能识别的信号的设备

内核TTY子系统

计算机为了支持这些teletype,于是设计了名字叫做TTY的子系统,内部结构如下:

    +-----------------------------------------------+
    |                    Kernel                     |
    |                                 +--------+    |
    |   +--------+   +------------+   |        |    |       +----------------+
    |   |  UART  |   |    Line    |   |  TTY   |<---------->| User process A |
<------>|        |<->|            |<->|        |    |       +----------------+
    |   | driver |   | discipline |   | driver |<---------->| User process B |
    |   +--------+   +------------+   |        |    |       +----------------+
    |                                 +--------+    |
    |                                               |
    +-----------------------------------------------+
  • UART driver对接外面的UART设备

  • Line discipline主要是对输入和输出做一些处理,可以理解它是TTY driver的一部分

  • TTY driver用来处理各种终端设备

  • 用户空间的进程通过TTY driver来和终端打交道

为了简单起见,后面的介绍中不再单独列出UART driver和Line discipline,可以认为它们是TTY driver的一部分

TTY设备

对于每一个终端,TTY driver都会创建一个TTY设备与它对应,如果有多个终端连接过来,那么看起来就是这个样子的:

                      +----------------+
                      |   TTY Driver   |
                      |                |
                      |   +-------+    |       +----------------+
 +------------+       |   |       |<---------->| User process A |
 | Terminal A |<--------->| ttyS0 |    |       +----------------+
 +------------+       |   |       |<---------->| User process B |
                      |   +-------+    |       +----------------+
                      |                |
                      |   +-------+    |       +----------------+
 +------------+       |   |       |<---------->| User process C |
 | Terminal B |<--------->| ttyS1 |    |       +----------------+
 +------------+       |   |       |<---------->| User process D |
                      |   +-------+    |       +----------------+
                      |                |
                      +----------------+

当驱动收到一个终端的连接时,就会根据终端的型号和参数创建相应的tty设备(上图中设备名称叫ttyS0是因为大部分终端的连接都是串行连接),由于每个终端可能都不一样,有自己的特殊命令和使用习惯,于是每个tty设备的配置可能都不一样。比如按delete键的时候,有些可能是要删前面的字符,而有些可能是删后面的,如果没配置对,就会导致某些按键不是自己想要的行为,这也是我们在使用模拟终端时,如果默认的配置跟我们的习惯不符,需要做一些个性化配置的原因。

后来随着计算机的不断发展,teletype这些设备逐渐消失,我们不再需要专门的终端设备了,每个机器都有自己的键盘和显示器,每台机器都可以是其它机器的终端,远程的操作通过ssh来实现,但是内核TTY驱动这一架构没有发生变化,我们想要和系统中的进程进行I/O交互,还是需要通过TTY设备,于是出现了各种终端模拟软件,并且模拟的也是常见的几种终端,如VT100、VT220、XTerm等。

  1. 可以通过命令toe -a列出系统支持的所有终端类型

  2. 可以通过命令infocmp来比较两个终端的区别,比如infocmp vt100 vt220将会输出vt100和vt220的区别。

程序如何和TTY打交道

在讨论TTY设备是如何被创建及配置之前,我们先来看看TTY是如何被进程使用的:

#先用tty命令看看当前bash关联到了哪个tty
dev@debian:~$ tty
/dev/pts/1

#看tty都被哪些进程打开了
dev@debian:~$ lsof /dev/pts/1
COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
bash     907  dev    0u   CHR  136,1      0t0    4 /dev/pts/1
bash     907  dev    1u   CHR  136,1      0t0    4 /dev/pts/1
bash     907  dev    2u   CHR  136,1      0t0    4 /dev/pts/1
bash     907  dev  255u   CHR  136,1      0t0    4 /dev/pts/1
lsof    1118  dev    0u   CHR  136,1      0t0    4 /dev/pts/1
lsof    1118  dev    1u   CHR  136,1      0t0    4 /dev/pts/1
lsof    1118  dev    2u   CHR  136,1      0t0    4 /dev/pts/1

#往tty里面直接写数据跟写标准输出是一样的效果
dev@dev:~$ echo aaa > /dev/pts/2
aaa

pts也是tty设备,它们的关系后面会介绍到

通过上面的lsof可以看出,当前运行的bash和lsof进程的stdin(0u)、stdout(1u)、stderr(2u)都绑定到了这个TTY上。

下面是tty和进程以及I/O设备交互的结构图:

   Input    +--------------------------+    R/W     +------+
----------->|                          |<---------->| bash |
            |          pts/1           |            +------+
<-----------|                          |<---------->| lsof |
   Output   | Foreground process group |    R/W     +------+
            +--------------------------+                               
  • 可以把tty理解成一个管道(pipe),在一端写的内容可以从另一端读取出来,反之亦然。

  • 这里input和output可以简单的理解为键盘和显示器,后面会介绍在各种情况下input/ouput都连接的什么东西。

  • tty里面有一个很重要的属性,叫Foreground process group,记录了当前前端的进程组是哪一个。process group的概念会在下一篇文章中介绍,这里可以简单的认为process group里面只有一个进程。

  • 当pts/1收到input的输入后,会检查当前前端进程组是哪一个,然后将输入放到进程组的leader的输入缓存中,这样相应的leader进程就可以通过read函数得到用户的输入

  • 当前端进程组里面的进程往tty设备上写数据时,tty就会将数据输出到output设备上

  • 当在shell中执行不同的命令时,前端进程组在不断的变化,而这种变化会由shell负责更新到tty设备中

从上面可以看出,进程和tty打交道很简单,只要保证后台进程不要读写tty就可以了,即写后台程序时,要将stdin/stdout/stderr重定向到其它地方(当然deamon程序还需要做很多其它处理)。

先抛出两个问题(后面有答案):

  • 当非前端进程组里面的进程(后台进程)往tty设备上写数据时,会发生什么?会输出到outpu上吗?

  • 当非前端进程组里面的进程(后台进程)从tty设备上读数据时,会发生什么?进程会阻塞吗?

TTY是如何被创建的

下面介绍几种常见的情况下tty设备是如何创建的,以及input和output设备都是啥。

键盘显示器直连(终端)

先看图再说话:

                   +-----------------------------------------+
                   |          Kernel                         |
                   |                           +--------+    |       +----------------+ 
 +----------+      |   +-------------------+   |  tty1  |<---------->| User processes |
 | Keyboard |--------->|                   |   +--------+    |       +----------------+
 +----------+      |   | Terminal Emulator |<->|  tty2  |<---------->| User processes |
 | Monitor  |<---------|                   |   +--------+    |       +----------------+
 +----------+      |   +-------------------+   |  tty3  |<---------->| User processes |
                   |                           +--------+    |       +----------------+
                   |                                         |
                   +-----------------------------------------+

键盘、显示器都和内核中的终端模拟器相连,由模拟器决定创建多少tty,比如你在键盘上输入ctrl+alt+F1时,模拟器首先捕获到该输入,然后激活tty1,这样键盘的输入会转发到tty1,而tty1的输出会转发到显示器,同理用输入ctrl+alt+F2,就会切换到tty2。

当模拟器激活tty时如果发现没有进程与之关联,意味着这是第一次打开该tty,于是会启动配置好的进程并和该tty绑定,一般该进程就是负责login的进程。

当切换到tty2后,tty1里面的输出会输出到哪里呢?tty1的输出还是会输出给模拟器,模拟器里会有每个tty的缓存,不过由于模拟器的缓存空间有限,所以下次切回tty1的时候,只能看到最新的输出,以前的输出已经不在了。

不确定这里的终端模拟器对应内核中具体的哪个模块,但肯定有这么个东西存在

SSH远程访问

 +----------+       +------------+
 | Keyboard |------>|            |
 +----------+       |  Terminal  |
 | Monitor  |<------|            |
 +----------+       +------------+
                          |
                          |  ssh protocol
                          |
                          ↓
                    +------------+
                    |            |
                    | ssh server |--------------------------+
                    |            |           fork           |
                    +------------+                          |
                        |   ↑                               |
                        |   |                               |
                  write |   | read                          |
                        |   |                               |
                  +-----|---|-------------------+           |
                  |     |   |                   |           ↓
                  |     ↓   |      +-------+    |       +-------+
                  |   +--------+   | pts/0 |<---------->| shell |
                  |   |        |   +-------+    |       +-------+
                  |   |  ptmx  |<->| pts/1 |<---------->| shell |
                  |   |        |   +-------+    |       +-------+
                  |   +--------+   | pts/2 |<---------->| shell |
                  |                +-------+    |       +-------+
                  |    Kernel                   |
                  +-----------------------------+

这里的Terminal可能是任何地方的程序,比如windows上的putty,所以不讨论客户端的Terminal程序是怎么和键盘、显示器交互的。由于Terminal要和ssh服务器打交道,所以肯定要实现ssh的客户端功能。

这里将建立连接和收发数据分两条线路解释,为了描述简洁,这里以sshd代替ssh服务器程序:

建立连接

  • 1.Terminal请求和sshd建立连接

  • 2.如果验证通过,sshd将创建一个新的session

  • 3.调用API(posix_openpt())请求ptmx创建一个pts,创建成功后,sshd将得到和ptmx关联的fd,并将该fd和session关联起来。

#pty(pseudo terminal device)由两部分构成,ptmx是master端,pts是slave端,
#进程可以通过调用API请求ptmx创建一个pts,然后将会得到连接到ptmx的读写fd和一个新创建的pts,
#ptmx在内部会维护该fd和pts的对应关系,随后往这个fd的读写会被ptmx转发到对应的pts。

#这里可以看到sshd已经打开了/dev/ptmx
dev@debian:~$ sudo lsof /dev/ptmx
COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
sshd    1191  dev    8u   CHR    5,2      0t0 6531 /dev/ptmx
sshd    1191  dev   10u   CHR    5,2      0t0 6531 /dev/ptmx
sshd    1191  dev   11u   CHR    5,2      0t0 6531 /dev/ptmx
  • 4.同时sshd创建shell进程,将新创建的pts和shell绑定

收发消息

  • 1.Terminal收到键盘的输入,Terminal通过ssh协议将数据发往sshd

  • 2.sshd收到客户端的数据后,根据它自己管理的session,找到该客户端对应的关联到ptmx上的fd

  • 3.往找到的fd上写入客户端发过来的数据

  • 4.ptmx收到数据后,根据fd找到对应的pts(该对应关系由ptmx自动维护),将数据包转发给对应的pts

  • 5.pts收到数据包后,检查绑定到自己上面的当前前端进程组,将数据包发给该进程组的leader

  • 6.由于pts上只有shell,所以shell的read函数就收到了该数据包

  • 7.shell对收到的数据包进行处理,然后输出处理结果(也可能没有输出)

  • 8.shell通过write函数将结果写入pts

  • 9.pts将结果转发给ptmx

  • 10.ptmx根据pts找到对应的fd,往该fd写入结果

  • 11.sshd收到该fd的结果后,找到对应的session,然后将结果发给对应的客户端

键盘显示器直连(图形界面)

 +----------+       +------------+
 | Keyboard |------>|            |
 +----------+       |  Terminal  |--------------------------+
 | Monitor  |<------|            |           fork           |
 +----------+       +------------+                          |
                        |   ↑                               |
                        |   |                               |
                  write |   | read                          |
                        |   |                               |
                  +-----|---|-------------------+           |
                  |     |   |                   |           ↓
                  |     ↓   |      +-------+    |       +-------+
                  |   +--------+   | pts/0 |<---------->| shell |
                  |   |        |   +-------+    |       +-------+
                  |   |  ptmx  |<->| pts/1 |<---------->| shell |
                  |   |        |   +-------+    |       +-------+
                  |   +--------+   | pts/2 |<---------->| shell |
                  |                +-------+    |       +-------+
                  |    Kernel                   |
                  +-----------------------------+

为了简化起见,本篇不讨论Linux下图形界面里Terminal程序是怎么和键盘、显示器交互的。

这里和上面的不同点就是,这里的Terminal不需要实现ssh客户端,但需要把ssh服务器要干的活也干了(当然ssh通信相关的除外)。

SSH + Screen/Tmux

常用Linux的同学应该对screen和tmux不陌生,通过它们启动的进程,就算网络断开了,也不会受到影响继续执行,下次连上去时还能看到进程的所有输出,还能继续接着干活。

这里以tmux为例介绍其原理:

 +----------+       +------------+
 | Keyboard |------>|            |
 +----------+       |  Terminal  |
 | Monitor  |<------|            |
 +----------+       +------------+
                          |
                          |  ssh protocol
                          |
                          ↓
                    +------------+
                    |            |
                    | ssh server |--------------------------+
                    |            |           fork           |
                    +------------+                          |
                        |   ↑                               |
                        |   |                               |
                  write |   | read                          |
                        |   |                               |
                  +-----|---|-------------------+           |
                  |     ↓   |                   |           ↓
                  |   +--------+   +-------+    |       +-------+  fork   +-------------+
                  |   |  ptmx  |<->| pts/0 |<---------->| shell |-------->| tmux client |
                  |   +--------+   +-------+    |       +-------+         +-------------+
                  |   |        |                |                               ↑
                  |   +--------+   +-------+    |       +-------+               |
                  |   |  ptmx  |<->| pts/2 |<---------->| shell |               |
                  |   +--------+   +-------+    |       +-------+               |
                  |     ↑   |  Kernel           |           ↑                   |
                  +-----|---|-------------------+           |                   |
                        |   |                               |                   |
                        |w/r|   +---------------------------+                   |
                        |   |   |            fork                               |
                        |   ↓   |                                               |
                    +-------------+                                             |
                    |             |                                             |
                    | tmux server |<--------------------------------------------+
                    |             |
                    +-------------+

系统中的ptmx只有一个,上图中画出来了两个,目的是为了表明tmux服务器和sshd都用ptmx,但它们之间又互不干涉。

这种情况要稍微复杂一点,不过原理都是一样的,前半部分和普通ssh的方式是一样的,只是pts/0关联的前端进程不是shell了,而是变成了tmux客户端,所以ssh客户端发过来的数据包都会被tmux客户端收到,然后由tmux客户端转发给tmux服务器,而tmux服务器干的活和ssh的类似,也是维护一堆的session,为每个session创建一个pts,然后将tmux客户端发过来的数据转发给相应的pts。

由于tmux服务器只和tmux客户端打交道,和sshd没有关系,当终端和sshd的连接断开时,虽然pts/0会被关闭,和它相关的shell和tmux客户端也将被kill掉,但不会影响tmux服务器,当下次再用tmux客户端连上tmux服务器时,看到的还是上次的内容。

TTY和PTS的区别

从上面的流程中应该可以看出来了,对用户空间的程序来说,他们没有区别,都是一样的;从内核里面来看,pts的另一端连接的是ptmx,而tty的另一端连接的是内核的终端模拟器,ptmx和终端模拟器都只是负责维护会话和转发数据包;再看看ptmx和内核终端模拟器的另一端,ptmx的另一端连接的是用户空间的应用程序,如sshd、tmux等,而内核终端模拟器的另一端连接的是具体的硬件,如键盘和显示器。

常见的TTY配置

先先来看看当前tty的所有配置:

dev@dev:~$ stty -a
speed 38400 baud; rows 51; columns 204; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = M-^?; eol2 = M-^?; swtch = <undef>; start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; discard = ^O; min = 1; time = 0;
-parenb -parodd -cmspar cs8 -hupcl -cstopb cread -clocal -crtscts
-ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc ixany imaxbel -iutf8
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke -flusho -extproc

stty还可以用来修改tty的参数,用法请参考man stty

只要是有权限的程序,都可以通过Linux提供的API来修改TTY的配置,下面介绍一些常见的的配置项。

rows 51; columns 204;

这个配置一般由终端控制,当终端的窗口大小发生变化时,需要通过一定的手段修改该配置,比如ssh协议里面就有修改窗口大小的参数,sshd收到客户端的请求后,会通过API修改tty的这个参数,然后由tty通过信号SIGWINCH通知前端程序(比如shell或者vim),前端程序收到信号后,再去读tty的这个参数,然后就知道如何调整自己的输出排版了。

intr = ^C

tty除了在终端和前端进程之间转发数据之外,还支持很多控制命令,比如终端输入了CTRL+C,那么tty不会将该输入串转发给前端进程,而是将它转换成信号SIGINT发送给前端进程。这个就是用来配置控制命令对应的输入组合的,比如我们可以配置“intr = ^E”表示用CTRL+E代替CTRL+C。

start = ^Q; stop = ^S;

这是两个特殊的控制命令,估计经常有人会碰到,在键盘上不小心输入CTRL+S后,终端没反应了,即没输出,也不响应任何输入。这是因为这个命令会告诉TTY暂停,阻塞所有读写操作,即不转发任何数据,只有按了CTRL+Q后,才会继续。这个功能应该是历史遗留,以前终端和服务器之间没有流量控制功能,所以有可能服务器发送数据过快,导致终端处理不过来,于是需要这样一个命令告诉服务器不要再发了,等终端处理完了后在通知服务器继续。

该命令现在比较常用的一个场景就是用tail -f命令监控日志文件的内容时,可以随时按CTRL+S让屏幕停止刷新,看完后再按CTRL+Q让它继续刷,如果不这样的话,需要先CTRL+C退出,看完后在重新运行tail -f命令。

echo

在终端输入字符的时候,之所以我们能及时看到我们输入的字符,那是因为TTY在收到终端发过去的字符后,会先将字符原路返回一份,然后才交给前端进程处理,这样终端就能及时的显示输入的字符。echo就是用来控制该功能的配置项,如果是-echo的话表示disable echo功能。

-tostop

如果你在shell中运行程序的时候,后面添加了&,比如./myapp &,这样myapp这个进程就会在后台运行,但如果这个进程继续往tty上写数据呢?这个参数就用来控制是否将输出转发给终端,也即结果会不会在终端显示,这里“-tostop”表示会输出到终端,如果配置为“tostop”的话,将不输出到终端,并且tty会发送信号SIGTTOU给myapp,该信号的默认行为是将暂停myapp的执行。

TTY相关信号

除了上面介绍配置时提到的SIGINT,SIGTTOU,SIGWINCHU外,还有这么几个跟TTY相关的信号

SIGTTIN

当后台进程读tty时,tty将发送该信号给相应的进程组,默认行为是暂停进程组中进程的执行。暂停的进程如何继续执行呢?请参考下一篇文章中的SIGCONT。

SIGHUP

当tty的另一端挂掉的时候,比如ssh的session断开了,于是sshd关闭了和ptmx关联的fd,内核将会给和该tty相关的所有进程发送SIGHUP信号,进程收到该信号后的默认行为是退出进程。

SIGTSTP

终端输入CTRL+Z时,tty收到后就会发送SIGTSTP给前端进程组,其默认行为是将前端进程组放到后端,并且暂停进程组里所有进程的执行。

跟tty相关的信号都是可以捕获的,可以修改它的默认行为

结束语

本文介绍了常见的tty功能和特点,下一篇中将详细介绍和tty密切相关的进程session id,进程组,job,后台程序等,敬请期待。

参考

查看原文

认证与成就

  • 获得 25 次点赞
  • 获得 37 枚徽章 获得 0 枚金徽章, 获得 7 枚银徽章, 获得 30 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

注册于 2015-11-27
个人主页被 795 人浏览