又一款抓包分析软件 wireshark

Wireshark 是一款网络分析工具,也是学习网络协议的工具,原先介绍过的 Charlesmitmproxy 等 HTTP 抓包工具,都局限于 HTTP/HTTPS 请求,对于更底层的 TCP/IP,UDP 等协议就无能为力了。Wireshark 可以抓取网卡上的网络包,并实时展示,Wireshark 包括了过滤器,协议显示等等工具。

Wireshark 和其他工具的区别,比如 Charles,mitmproxy,Fiddler 等。Charles, mitmproxy,Fiddler 是专门用来捕获 HTTP,HTTPS 请求的。Wireshark 能获取 HTTP,也能获取 HTTPS,但是不能解密 HTTPS,所以 Wireshark 看不懂 HTTPS 中的内容。总结,如果是处理 HTTP,HTTPS 还是用 Charles, mitmproxy, Fiddler 等,其他协议比如 TCP,UDP,IP,ICMP 等就用 Wireshark

安装

各大系统的安装文件:https://www.wireshark.org/download.html

Linux 下可以使用 PPA

sudo add-apt-repository ppa:wireshark-dev/stable && sudo apt-get update
sudo apt-get install wireshark

安装过程中选择 YES,让普通用户也能够抓包。

如果启动之后遇到

couldn't run /usr/bin/dumpcap in child process: Permission Denied.

这样的问题,

sudo dpkg-reconfigure wireshark-common

选择 “YES”, 将当前用户添加到 group

sudo adduser $USER wireshark

然后再登出登入 1

简单使用

打开 Wireshark 就可以看到很多网络硬件可以选择,任选其中一块网卡就能够抓取经过这个网卡的所有流量包。常见的设备名字,或者网卡名字有这样几个:

  • eth0 物理网卡,一般连接网线会获取到 IP 地址
  • eth1 第二块网卡
  • wlan0 是无线网卡,一般连接无线网会获取到 IP 地址
  • lo 设备虚拟端口,自身回环,一般指向 127.0.0.1

还有一些设备名字可以参考之前的文章。比如我笔记本使用无线网卡连接了 WIFI,那么进入 Wireshark 之后选择 wlan0 设备,自动进入抓包,可以看到经过无线网卡的所有请求包。

wireshark-windows

Wireshark 的界面大致可以分成三个部分,最上面的部分为原始数据包预览,可以在该面板中看到抓取的包大致内容,包括序号,耗时,原始地址,目标地址,协议,长度,基本信息等等,分别使用不同的颜色标记了,这个颜色可以在设置 View -> Coloring Rules 中设置,根据不同的协议,或者自定义一些过滤规则,将关心的内容以不同的颜色标记出。

面板中间是封包详细信息 (Packet Details Pane),这个面板是最重要的,用来查看协议中的每一个字段。各行信息分别为

  • Frame: 物理层的数据帧概况
  • Ethernet II: 数据链路层以太网帧头部信息
  • Internet Protocol Version 4: 互联网层 IP 包头部信息
  • Transmission Control Protocol: 传输层 T 的数据段头部信息,此处是 TCP
  • Hypertext Transfer Protocol: 应用层的信息,此处是 HTTP 协议

面板最下面一栏是数据包真正传输的内容,以十六进制和 ASCII 显示出来。

过滤器

https://wiki.wireshark.org/CaptureFilters

两种过滤器的目的是不同的。

捕捉过滤器(CaptureFilters):用于决定将什么样的信息记录在捕捉结果中。需要在开始捕捉前设置。捕捉过滤器是数据经过的第一层过滤器,它用于控制捕捉数据的数量,以避免产生过大的日志文件。

显示过滤器(DisplayFilters):在捕捉结果中进行详细查找。他们可以在得到捕捉结果后随意修改。显示过滤器是一种更为强大(复杂)的过滤器。它允许您在日志文件中迅速准确地找到所需要的记录。

两种过滤器使用的语法是完全不同的。

http://openmaniak.com/cn/wireshark_filters.php

三次握手

Wireshark 实际分析下三次握手的过程

在 wireshark 中输入 http 过滤, 然后选中 GET /tankxiao HTTP/1.1 的那条记录,右键然后点击”Follow TCP Stream”,

这样做的目的是为了得到与浏览器打开网站相关的数据包

可以看到 wireshark 截获到了三次握手的三个数据包。第四个包才是 HTTP 的, 这说明 HTTP 的确是使用 TCP 建立连接的。

第一次握手数据包

客户端发送一个 TCP,标志位为 SYN,序列号为 0, 代表客户端请求建立连接。

第二次握手的数据包

服务器发回确认包,标志位为 SYN,ACK. 将确认序号 (Acknowledgement Number) 设置为客户的 ISN 加 1 以. 即 0+1=1

第三次握手的数据包

客户端再次发送确认包 (ACK) SYN 标志位为 0,ACK 标志位为 1. 并且把服务器发来 ACK 的序号字段 +1, 放在确定字段中发送给对方. 并且在数据段放写 ISN 的 +1

  1. https://askubuntu.com/a/778170/407870 


2018-01-16 wireshark , charles , mitmproxy , proxy

使用 Chevereto 自建照片分享

Chevereto 是一款分享照片的程序,可以非常轻松得在自己的服务器上搭建照片分享程序,功能强大,外观精美。Chevereto 本身是收费使用的,一次性付费,终身使用,但是其开源版本可以免费使用。

目前 Chevereto 的价格是 $39,可以免费升级到 V4 版本。作者已经发生声明,在 2021 年底将终止 Free 版本的维护。

Docker 安装

推荐使用 Docker 安装 Chevereto:

安装

在安装之前请先检查需要的系统配置,至少保证 VPS 安装有

  • nginx 或者 Apache web server
  • MySQL
  • PHP

安装依赖

apt-get install nginx mysql-server php7.0 php7.0-common php7.0-curl php7.0-mysql php7.0-gd php7.0-xml php7.0-mbstring

从官网下载最新版本 压缩包

wget https://github.com/Chevereto/Chevereto-Free/archive/1.0.9.tar.gz

Nginx 配置

新建虚拟主机,修改域名 A 记录指向 VPS,然后配置对应的 vim /etc/nginx/sites-enabled/photo.einverne.info

由于 chevereto 默认提供基于 Apache 环境的伪静态规则,故 nginx 的配置是不能用的,需要自己添加规则

server {
    listen 80;
    listen [::]:80;

    root /var/www/photo.einverne.info/html;
    index index.php index.html index.htm;

    server_name server_domain_or_IP;

    location / {
        try_files $uri $uri/ =404;
    }

    location ~ \.php$ {
        include snippets/fastcgi-php.conf;
        fastcgi_pass unix:/run/php/php7.0-fpm.sock;
    }

    location ~ /\.ht {
        deny all;
    }
}

nginx 配置中还要注意一个 vim /etc/nginx/nginx.conf 配置中增加:

server { client_max_body_size 20M; //other lines… }

修改完重新加载 Nginx 配置 /etc/init.d/nginx reload

配置 MySQL

安装完 MySQL 之后需要为 Chevereto 新建一个数据库:

mysql -u root -p          # 登录mysql
create database photo;    # 创建 photo 数据库

修改 PHP 配置

默认的PHP上传大小在配置中略有不同,如果想要增大每张照片上传的大小,不仅上面 Nginx 中需要配置,同理 PHP 配置中也需要修改如下 vim /etc/php/7.0/fpm/php.ini

max_execution_time
max_input_time
memory_limit
post_max_size
upload_max_filesize

修改完重新加载PHP配置 /etc/init.d/php7.0-fpm reload

在做完这一系列配置之后,将之前下载的压缩包,在 /var/www/photo.einverne.info/html/ 目录下解压,然后使用域名访问。如果一切都没有问题,那么 Chevereto 会显示要求数据配置。要求填写:数据库名、数据库用户名、数据库用户密码,还有数据库表头。

这几项在前面安装时都已经完成,新建的数据库名,还有 MySQL 的用户名和密码,最后的数据表头名可以不变。然后下一步会填写管理员的一些信息,最后完成就好。

使用

设置中文,网上很多说需要修改密码,其实,在设置管理员面板中能够直接修改语言为中文。

修改图片存储路径:默认是在/images文件夹内,修改方法为在config.php修改define(‘DIR_IM’,’images/‘); ,这一步其实现在也能够在设置中直接修改。

Error

如果上传遇到问题,界面上显示

Server error (Internal server error)

一般的情况就是 Nginx 或者 PHP 的上传大小设置不对,上传的图片大于了 Nginx 或者 PHP 能够处理的大小,这是时候调整上传的大小就可以。调试方法如下;

没有给出任何信息,查看 nginx 错误日志

tailf /var/log/nginx/error.log

发现如下错误

2018/02/05 19:21:12 [error] 2693#2693: *8 client intended to send too large body: 1318270 bytes, client: 172.xxx.xxx.xxx, server: photo.einverne.info, request: "POST /json HTTP/1.1", host: "photo.einverne.info", referrer: "http://photo.einverne.info/dashboard/settings/system"

解决办法

vi /etc/nginx/nginx.conf

添加

client_max_body_size 20M;

然后 /etc/init.d/nginx reload 重新加载 nginx 服务配置。

reference


2018-01-15 php , photo , google , flickr , chevereto , self-hosted

关于游戏的一些想法

最近国内又火了一个答题游戏,回答正确12题平分多少多少万奖金。先不说他们都抄袭 HQ Trivia 这款App,但我这两天一直在想一个问题,为什么这一类的应用能火起来,难道就是因为最后平分100万,200万,现金带来的刺激吗?我想答案一定不是的,除了最开始的玩家或许还能分到几十块钱,在入场玩家越来越多的情况下,每个人瓜分到的奖励一定是越来越少的。那到底是什么能让一个应用一夜之间火到大江南北?

很多人之前说过是主播带火了”吃鸡”游戏,这一点我是认同的,但这应该只是能火的其中一个因素,也就是在正常的宣传情况下,带来的口碑一层层的堆叠,能少能有游戏能够做好老少咸宜,并且也能够在不同人群中拥有大多数的好评。所以游戏本身的可玩性,娱乐性是应该需要得到保证的,玩家能够在游戏中得到乐趣,并且通过游戏提供的场景能够扩展出来不同的玩法,而这些玩法又足够好玩。这里就要说道在吃鸡游戏前面1分钟左右的等待时间,有个叫平底锅的主播用来喊麦,还有人在游戏中组队交友。

然后这就说道了能够火,并且能够让绝大部分人能够接受的第二大原因,就是这个游戏需要有足够的社交属性,不管是陌生人社交,还是熟悉的好友的社交。在足够话题性的同时,能够让三五好友聚到一起开黑,说语音,开黑。这是这个时代的需要,在吃鸡火起来之前的“王者荣耀”我想也是因为能够拉上三五亲朋好友组个队,带个节奏,尤其在过年短短的几天时间内,好多亲朋好友从五湖四海聚到一家,每个人从事的职业和活动没有太多交集,唯有一局王者荣耀才能让气氛足够的活跃起来。所以这里也要提到另一个能够流行的原因了,也就是让绝大部分人获取的方法足够简单。

Steam 上的绝地求生或许正是因为他对设备要求过高,而过滤掉很大一部分玩家,才有了网易在国内能够通过荒野行动,快速的聚集了近2亿的玩家,让足够多的人,只用下载一个App,不到几分钟的时间就上手玩,才有能力让这个游戏走的足够远。

再其次,如果一个游戏想要不被这个时代淘汰,不断的进化,并且有能力让超越游戏的内容,比如游戏竞技,或者游戏的Cosplay出现,才能够立于不败之地,回想以前玩过的War 3,孵化出了Dota,Dota2,还有 LOL,可是暴雪自己却没有赶上,也略遗憾。而其实这一点也很难做到,无数的手机益智类游戏,火玩一波,再来一波,愤怒的小鸟,植物大战僵尸都已经存在回忆中了。

说道这里再回头来看答题分奖金这个应用,他能火正是也正是因为他足够有话题性,也足够有社交属性,失散多年的好友,同学,亲戚可能都被拉来一起玩,并且我从来没有看到过玩一个答题游戏,能够在短短几天时间内,开黑群,语音发答案,自动搜索答案助手等等延伸品出现。再这个游戏也足够被绝大多数人接受,官方打的宣传语就是,答题不仅能赢钱还能学习知识。这一些因素促成了这个游戏必定成为2018年开年的热点,但其实呢?我也再想另外一个问题,这个模式能够持续多长时间,爆红之后带来的用户如何沉淀。

首先这个应用能够吸引足够的目标,并且能吸引一定的广告主,这是一定的,但如果到足够需要花半个小时时间来赢取2 、 3 元的奖励,到最后还能剩下多少衷心的用户。我觉得如果使用这样的方式来吸引用户来吸取内容,未免不是一个极好的宣传方式,但如果频繁开场次,绝大多数的用户会厌倦了每天9点定时打开手机等待一串废话。

2018年的农历新年马上就要到来了,可想而知这样一个应用必定还有爆发的一次机会,但以后如何走,就只能拭目以待了。


2018-01-14 思考 , app , game , hq

Android 电视盒子可用的应用备份

用盒子也已经很多年了,几年来家里,自己用,也积累了一些常用的应用。这两天又拿到了 T1 盒子,又才想起来整理这样一份单子,这样不用每一次都一遍一遍的尝试了。记得以前 VST ,泰捷视频都还很不错的时候,再后来广电发了禁令,再后来这片市场混乱发展,各家大型网站优酷,爱奇艺又不敢公开大搞,却又在背后偷偷摸摸。再到现在几乎被什么芒果,CIBN 垄断,内容没什么可看,却什么都要收费。我始终抱有一个观点,如果电视盒子这一块开放发展,国内的厂家完全能够占领全世界的盒子市场,好几年前用的 Android 盒子就已经能够满足我的大部分需求,并且应用设计也早 Google 自己推出 Android TV 盒子以及规范 Android TV 应用好多年。可惜这一块市场被一道禁令打到了地下。

Android 盒子安装应用的方法,大概可以分为这几个:

  • 局域网拷贝 APK
  • 用 U 盘连接电视
  • 沙发管家,或者当贝市场中安装
  • 局域网 adb 安装

当然对于一个新的设备,通过 adb 安装一个应用市场,然后通过应用市场下载其他应用是最简单的方式。

市场

Apple 有 App Store, Google 有 Play Store,电视盒子直到现在依然还在乱斗:

  • 奇珀市场 http://down.7po.com/
  • 沙发管家 http://www.shafa.com/
  • 当贝市场 智能电视应用市场
  • 欢视商店
  • 蜜蜂市场

地址就不都给了,Google 搜一下很快。

直播

电视直播的应用,以前用 VST 和 泰捷还行,不过现在已经废了,然后现在 HDP 做的也还不错。

2018 年 3 月 3 号更新,发现了一款叫做 超级直播 的应用,非常好用。使用超级直播的时候,在播放页面点击设置,然后选择 “二维码扫一扫开启更多功能”,并在二维码显示之后,连续多次点击出现的二维码多次,即可解锁隐藏功能。隐藏功能开启 6000 以后的频道,包括可以查看多个台湾、香港的频道,频道范围在 6xxx 可能上下有些偏差,65xx 开始会有抢先观看的电影。

一下排名按照易用程度:

  • 超级直播
  • HDP 直播
  • 直播狗
  • 小薇直播
  • 电视家 3.0
  • 枫蜜直播

放一张截图

斐讯 t1_直播

网络视频

网络视频是我用的最多的了,我本人用 哔哩哔哩 最多

  • bilibili,哔哩哔哩 tv 版本有两个,其中一个叫做云视听小电视,这个是无法观看 UP 主上传的视频的,你需要一个超级老的版本才可以,后文自行寻找
  • 人人影视 TV 版
  • 云视听小电视
  • 银河奇异果
  • 云视听极光
  • CIBN 高清影视
  • CIBN 聚精彩
  • VST
  • 泰捷
  • 南瓜电影
  • 蜜蜂视频

本地视频

盒子带 samba ,能读局域网内 samba 共享的视频,那就需要一个本地播放器,最好支持的解码格式越多越好,在 Android 手机上我买过 MX Player Pro,不过免费的 KMPlayer 也不错。

  • Kodi,不得不请出这个大杀器了,All in one 既能作为本地播放器,也能使用 Samba,NFS 等等协议播放局域网中的视频,同样能够作为 DLNA 服务端作为投屏工具
  • ES 文件管理器
  • 小白文件管理器 v1.2 版本
  • MX Player Pro

系统工具

说到乐播投屏这个应用,还是我去实体店,然后有一个店员向我展示一个只有魔方大小的投影仪时,用的应用,将手机的屏幕投影到投影仪上,我就记住了这个投屏应用,回来发现,iOS 投屏还是不错的。

  • 当贝桌面
  • 乐播投屏,老版本无广告,非常清爽
  • 悟空遥控

其他

其他体验还不错的应用:

  • Keep TV
  • 小鸡模拟器 TV 版

在 Android TV 上使用 YouTube

今天突然想到,我平时看的最多的 YouTube ,是有电视版的啊,最近又把路由器更新了一下,局域网使用 YouTube 完全没问题啊,然后就下载了几个 YouTube for TV 的应用,发现只有下面这一个不需要依赖 Play Service,然后,在侧边栏设置中,有一个关联码,和手机关联,然后就可以非常轻松的将手机上的 YouTube 视频投送到电视盒子上,然后再到投影仪上。太舒服了。

YouTube for TV

版本:1.12.10 发布时间:2018-01-12

下载地址: https://apkpure.com/youtube-tv-watch-record-live-tv/com.google.android.apps.youtube.unplugged

网上下载不到的应用备份


2018-01-13 android , tv , adb , packages , apk , applications

okhttp 使用

OkHttp是一个非常高效的HTTP客户端,默认情况下:

  • 支持HTTP/2,允许对同一主机的请求共用一个套接字。
  • 如果HTTP/2 不可用,连接池会减少请求延迟。
  • 透明的GZIP可以减少下载流量。
  • 响应的缓存避免了重复的网络请求。

同步 GET 方法

阻塞请求

private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
    Request request = new Request.Builder()
        .url("http://publicobject.com/helloworld.txt")
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

    Headers responseHeaders = response.headers();
    for (int i = 0; i < responseHeaders.size(); i++) {
      System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
    }

    System.out.println(response.body().string());
}

异步 GET 请求

响应可读时回调 Callback 接口,读取响应时阻塞当前线程

private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
    Request request = new Request.Builder()
        .url("http://publicobject.com/helloworld.txt")
        .build();

    client.newCall(request).enqueue(new Callback() {
      @Override public void onFailure(Request request, Throwable throwable) {
        throwable.printStackTrace();
      }

      @Override public void onResponse(Response response) throws IOException {
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

        Headers responseHeaders = response.headers();
        for (int i = 0; i < responseHeaders.size(); i++) {
          System.out.println(responseHeaders.name(i) + ": " + responseHeaders.value(i));
        }

        System.out.println(response.body().string());
      }
    });
}

POST 提交信息

public static final MediaType MEDIA_TYPE_MARKDOWN
  = MediaType.parse("text/x-markdown; charset=utf-8");

private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
    String postBody = ""
        + "Releases\n"
        + "--------\n"
        + "\n"
        + " * _1.0_ May 6, 2013\n"
        + " * _1.1_ June 15, 2013\n"
        + " * _1.2_ August 11, 2013\n";

    Request request = new Request.Builder()
        .url("https://api.github.com/markdown/raw")
        .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, postBody))
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

    System.out.println(response.body().string());
}

流方式提交请求体

流的方式提交请求体

public static final MediaType MEDIA_TYPE_MARKDOWN
      = MediaType.parse("text/x-markdown; charset=utf-8");

private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
    RequestBody requestBody = new RequestBody() {
      @Override public MediaType contentType() {
        return MEDIA_TYPE_MARKDOWN;
      }

      @Override public void writeTo(BufferedSink sink) throws IOException {
        sink.writeUtf8("Numbers\n");
        sink.writeUtf8("-------\n");
        for (int i = 2; i <= 997; i++) {
          sink.writeUtf8(String.format(" * %s = %s\n", i, factor(i)));
        }
      }

      private String factor(int n) {
        for (int i = 2; i < n; i++) {
          int x = n / i;
          if (x * i == n) return factor(x) + " × " + i;
        }
        return Integer.toString(n);
      }
    };

    Request request = new Request.Builder()
        .url("https://api.github.com/markdown/raw")
        .post(requestBody)
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

    System.out.println(response.body().string());
}

POST 提交文件

POST 方式提交文件

public static final MediaType MEDIA_TYPE_MARKDOWN
  = MediaType.parse("text/x-markdown; charset=utf-8");

private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
    File file = new File("README.md");

    Request request = new Request.Builder()
        .url("https://api.github.com/markdown/raw")
        .post(RequestBody.create(MEDIA_TYPE_MARKDOWN, file))
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

    System.out.println(response.body().string());
}

提交表单

public void run() throws Exception {
    RequestBody formBody = new FormEncodingBuilder()
        .add("search", "Jurassic Park")
        .build();
    Request request = new Request.Builder()
        .url("https://en.wikipedia.org/w/index.php")
        .post(formBody)
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

    System.out.println(response.body().string());
}

分块POST提交

private static final String IMGUR_CLIENT_ID = "...";
private static final MediaType MEDIA_TYPE_PNG = MediaType.parse("image/png");

private final OkHttpClient client = new OkHttpClient();

public void run() throws Exception {
    // Use the imgur image upload API as documented at https://api.imgur.com/endpoints/image
    RequestBody requestBody = new MultipartBuilder()
        .type(MultipartBuilder.FORM)
        .addPart(
            Headers.of("Content-Disposition", "form-data; name=\"title\""),
            RequestBody.create(null, "Square Logo"))
        .addPart(
            Headers.of("Content-Disposition", "form-data; name=\"image\""),
            RequestBody.create(MEDIA_TYPE_PNG, new File("website/static/logo-square.png")))
        .build();

    Request request = new Request.Builder()
        .header("Authorization", "Client-ID " + IMGUR_CLIENT_ID)
        .url("https://api.imgur.com/3/image")
        .post(requestBody)
        .build();

    Response response = client.newCall(request).execute();
    if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

    System.out.println(response.body().string());
}

2018-01-12 okhttp

每天学习一个命令:awk 处理文本

awk 是一个强大的文本分析工具,它是 Linux 中功能强大的数据处理引擎之一,awk 可以非常轻松地处理比如每行都是相同格式的文本,比如日志,csv 格式等等。相对于 grep 的查找,sed 的编辑,awk 在其对数据分析并生成报告时,显得尤为强大。

当使用 awk 命令处理一个或者多个文件时,它会依次读取文件的每一行内容,然后对其进行处理,awk 命令默认从 stdio 标准输入获取文件内容,awk 使用一对单引号来表示一些可执行的脚本代码,在可执行脚本代码里面,使用一对花括号来表示一段可执行代码块,可以同时存在多个代码块。awk 的每个花括号内同时可以有多个指令,每一个指令用分号分隔,awk 其实就是一个脚本编程语言。

awk 命令和 sed 命令结构相同,通常情况下,awk 将每个输入行解释为一条记录而每一行中的内容(由空格或者制表符分隔)解释为每一个字段,一个或者多个连续空格或者制表符看做定界符。awk 中 $0 代表整个记录。

awk ' /MA/ { print $1 }' list

解释:打印包含 MA 的行中的第一个单词。再举一个具体的例子,比如

echo 'this is one world\nthat is another world' | awk '{print $1}'

那么输出就是 awk 处理之后的每一行第一个字符也就是:

this
that

基本格式

awk 命令的基本格式

awk [options] 'script' file

options 这个表示一些可选的参数选项,script 表示 awk 的可执行脚本代码(一般被{} 花括号包围),这个是必须的。file 这个表示 awk 需要处理的文件,注意需要是纯文本文件(意味着 awk 能够处理)。

awk 自定义分隔符

之前提到的,awk 默认的分割符为空格和制表符,awk 会根据这个默认的分隔符将每一行分为若干字段,依次用 $1, $2,$3 来表示,可以使用 -F 参数来指定分隔符

awk -F ':' '{print $1}' /etc/passwd

解释:使用 -F 来改变分隔符为 : ,比如上面的命令将 /etc/passwd 文件中的每一行用冒号 : 分割成多个字段,然后用 print 将第 1 列字段的内容打印输出

在 awk 中同时指定多个分隔符,比如现在有这样一个文件 some.log 文件内容如下

Grape(100g)1980
raisins(500g)1990
plum(240g)1997
apricot(180g)2005
nectarine(200g)2008

现在我们想将上面的 some.log 文件中按照 “水果名称(重量)年份” 来进行分割

$ awk -F '[()]' '{print $1, $2, $3}' some.log
Grape 100g 1980
raisins 500g 1990
plum 240g 1997
apricot 180g 2005
nectarine 200g 2008

-F 参数中使用一对方括号来指定多个分隔符,awk 处理 some.log 文件时就会使用 “(“ 或者 “)” 来对文件的每一行进行分割。

awk 内置变量的使用

awk 除了 $ 和数字表示字段还有一些其他的内置变量:

  • $0 这个表示文本处理时的当前行,$1 表示文本行被分隔后的第 1 个字段列,$2 表示文本行被分割后的第 2 个字段列,$3 表示文本行被分割后的第 3 个字段列,$n 表示文本行被分割后的第 n 个字段列
  • NR 表示文件中的行号,表示当前是第几行
  • NF 表示文件中的当前行被分割的列数,可以理解为 MySQL 数据表里面每一条记录有多少个字段,所以 $NF 就表示最后一个字段,$(NF-1) 就表示倒数第二个字段
  • FS 表示 awk 的输入分隔符,默认分隔符为空格和制表符,可以对其进行自定义设置
  • OFS 表示 awk 的输出分隔符,默认为空格,也可以对其进行自定义设置
  • FILENAME 表示当前文件的文件名称,如果同时处理多个文件,它也表示当前文件名称
  • RS 行分隔符,用于分割行,默认为换行符
  • ORS 输出记录的分隔符,默认为换行符

比如我们有这么一个文本文件 fruit.txt 内容如下,用它来演示如何使用 awk 命令工具

peach    100   Mar  1997   China
Lemon    150   Jan  1986   America
Pear     240   Mar  1990   Janpan
avocado  120   Feb  2008   china

awk '{print $0}' fruit.txt   # 表示打印输出文件的每一整行的内容
awk '{print $1}' fruit.txt   # 表示打印输出文件的每一行的第 1 列内容
awk '{print $1, $2}' fruit.txt

文件的每一行的每一列的内容除了可以用 print 命令打印输出以外,还可以对其进行赋值

awk '{$2 = "***"; print $0}' fruit.txt

上面的例子就是表示通过对 $2 变量进行重新赋值,来隐藏每一行的第 2 列内容,并且用星号 * 来代替其输出

在参数列表中加入一些字符串或者转义字符之类的东东

awk '{print $1 "\t" $2 "\t" $3}' fruit.txt

像上面这样,你可以在 print 的参数列表中加入一些字符串或者转义字符之类的东东,让输出的内容格式更漂亮,但一定要记住要使用双引号。

awk 内置 NR 变量表示每一行的行号

awk '{print NR "\t" $0}' fruit.txt

1   peach    100   Mar  1997   China
2   Lemon    150   Jan  1986   America
3   Pear     240   Mar  1990   Janpan
4   avocado  120   Feb  2008   china

awk 内置 NF 变量表示每一行的列数

awk '{print NF "\t" $0}' fruit.txt

5   peach    100   Mar  1997   China
5   Lemon    150   Jan  1986   America
5   Pear     240   Mar  1990   Janpan
5   avocado  120   Feb  2008   china

awk 中 $NF 变量的使用

awk '{print $NF}' fruit.txt

上面这个 $NF 就表示每一行的最后一列,因为 NF 表示一行的总列数,在这个文件里表示有 5 列,然后在其前面加上 $ 符号,就变成了 $5 ,表示第 5 列

awk '{print $(NF - 1)}' fruit.txt

1997
1986
1990
2008

上面 $(NF-1) 表示倒数第 2 列, $(NF-2) 表示倒数第 3 列,依次类推。

awk 'NR % 6'        # 打印出了 6 倍数行之外的其他行
awk 'NR > 5'        # 打印第 5 行之后内容,类似 `tail -n +6` 或者 `sed '1,5d'`
awk 'NF >= 6'       # 打印大于等于 6 列的行
awk '/foo/ && /bar/'    # 打印匹配 `/foo/` 和 `/bar/` 的行
awk '/foo/ && !/bar/'   # 打印包含 `/foo/` 不包含 `/bar/` 的行
awk '/foo/ || /bar/'    # 或
awk '/foo/,/bar/'       # 打印从匹配 `/foo/` 开始的行到 `/bar/` 的行,包含这两行

awk 内置函数

awk 还提供了一些内置函数,比如:

  • toupper() 用于将字符转为大写
  • tolower() 将字符转为小写
  • length() 长度
  • substr() 子字符串
  • sin() 正弦
  • cos() 余弦
  • sqrt() 平方根
  • rand() 随机数

更多的方法可以参考:man awk

awk 同时处理多个文件

awk '{print FILENAME "\t" $0}' demo1.txt demo2.txt

当你使用 awk 同时处理多个文件的时候,它会将多个文件合并处理,变量 FILENAME 就表示当前文本行所在的文件名称。

BEGIN 关键字的使用

在脚本代码段前面使用 BEGIN 关键字时,它会在开始读取一个文件之前,运行一次 BEGIN 关键字后面的脚本代码段, BEGIN 后面的脚本代码段只会执行一次,执行完之后 awk 程序就会退出

awk 'BEGIN {print "Start read file"}' /etc/passwd

awk 脚本中可以用多个花括号来执行多个脚本代码,就像下面这样

awk 'BEGIN {print "Start read file"} {print $0}' /etc/passwd

END 关键字使用方法

awk 的 END 指令和 BEGIN 恰好相反,在 awk 读取并且处理完文件的所有内容行之后,才会执行 END 后面的脚本代码段

awk 'END {print "End file"}' /etc/passwd
awk 'BEGIN {print "Start read file"} {print $0} END {print "End file"}' /etc/passwd

在 awk 中使用变量

可以在 awk 脚本中声明和使用变量

awk '{msg="hello world"; print msg}' /etc/passwd

awk 声明的变量可以在任何多个花括号脚本中使用

awk 'BEGIN {msg="hello world"} {print msg}' /etc/passwd

在 awk 中使用数学运算,在 awk 中,像其他编程语言一样,它也支持一些基本的数学运算操作

awk '{a = 12; b = 24; print a + b}' company.txt

上面这段脚本表示,先声明两个变量 a = 12 和 b = 24,然后用 print 打印出 a 加上 b 的结果。

请记住 awk 是针对文件的每一行来执行一次单引号 里面的脚本代码,每读取到一行就会执行一次,文件里面有多少行就会执行多少次,但 BEGIN 和 END 关键字后脚本代码除外,如果被处理的文件中什么都没有,那 awk 就一次都不会执行。

awk 还支持其他的数学运算符

+ 加法运算符
- 减法运算符
* 乘法运算符
/ 除法运算符
% 取余运算符

在 awk 中使用条件判断

比如有一个文件 company.txt 内容如下

yahoo   100 4500
google  150 7500
apple   180 8000
twitter 120 5000

如果要判断文件的第 3 列数据,也就是平均工资小于 5500 的公司,然后将其打印输出

awk '$3 < 5500 {print $0}' company.txt

上面的命令结果就是平均工资小于 5500 的公司名单,$3 < 5500 表示当第 3 列字段的内容小于 5500 的时候才会执行后面的 {print $0} 代码块

awk '$1 == "yahoo" {print $0}' company.txt

awk 还有一些其他的条件操作符如下

< 小于
<= 小于或等于
== 等于
!= 不等于
> 大于
>= 大于或等于
~ 匹配正则表达式
!~ 不匹配正则表达式

使用 if 指令判断来实现上面同样的效果

awk '{if ($3 < 5500) print $0}' company.txt

上面表示如果第 3 列字段小于 5500 的时候就会执行后面的 print $0

在 awk 中使用正则表达式

比如现在我们有这么一个文件 poetry.txt 内容如下:

This above all: to thine self be true
There is nothing either good or bad, but thinking makes it so
There’s a special providence in the fall of a sparrow
No matter how dark long, may eventually in the day arrival

使用正则表达式匹配字符串 “There” ,将包含这个字符串的行打印并输出

awk '/There/{print $0}' poetry.txt

There is nothing either good or bad, but thinking makes it so
There’s a special providence in the fall of a sparrow

使用正则表达式配一个包含字母 t 和字母 e ,并且 t 和 e 中间只能有任意单个字符的行

awk '/t.e/{print $0}' poetry.txt

There is nothing either good or bad, but thinking makes it so
There’s a special providence in the fall of a sparrow
No matter how dark long, may eventually in the day arrival

如果只想匹配单纯的字符串 “t.e”, 那正则表达式就是这样的 /t.e/ ,用反斜杠来转义 . 符号 因为 . 在正则表达式里面表示任意单个字符。

使用正则表达式来匹配所有以 “The” 字符串开头的行

awk '/^The/{print $0}' poetry.txt

在正则表达式中 ^ 表示以某某字符或者字符串开头。

使用正则表达式来匹配所有以 “true” 字符串结尾的行

awk '/true$/{print $0}' poetry.txt

在正则表达式中 $ 表示以某某字符或者字符串结尾。

awk '/m[a]t/{print $0}' poetry.txt

No matter how dark long, may eventually in the day arrival

上面这个正则表达式 /m[a]t/ 表示匹配包含字符 m ,然后接着后面包含中间方括号中表示的单个字符 a ,最后包含字符 t 的行,输出结果中只有单词 “matter” 符合这个正则表达式的匹配。因为正则表达式 [a] 方括号中表示匹配里面的任意单个字符。

继续上面的一个新例子如下

awk '/^Th[ie]/{print $0}' poetry.txt

这个例子中的正则表达式 /^Th[ie]/ 表示匹配以字符串 “Thi” 或者 “The” 开头的行,正则表达式方括号中表示匹配其中的任意单个字符。

再继续上面的新的用法

awk '/s[a-z]/{print $0}' poetry.txt

正则表达式 /s[a-z]/ 表示匹配包含字符 s 然后后面跟着任意 a 到 z 之间的单个字符的字符串,比如 “se”, “so”, “sp” 等等。

正则表达式 [] 方括号中还有一些其他用法比如下面这些

[a-zA-Z]  表示匹配小写的 a 到 z 之间的单个字符,或者大写的 A 到 Z 之间的单个字符
[^a-z]    符号 `^` 在方括号里面表示取反,也就是非的意思,表示匹配任何非 a 到 z 之间的单个字符

正则表达式中的星号 * 和加号 + 的使用方法,* 表示匹配星号前字符串 0 次或者多次,+ 和星号原理差不多,只是加号表示任意 1 个或者 1 个以上,也就是必须至少要出现一次。

正则表达式问号 ? 的使用方法,正则中的问号 ? 表示它前面的字符只能出现 0 次 或者 1 次。

正则表达式中的 {} 花括号用法,花括号 {} 表示规定它前面的字符必须出现的次数,像这个 /go{2}d/ 就表示只匹配字符串 “good”,也就是中间的字母 “o” 必须要出现 2 次。

正则表达式中的花括号还有一些其他的用法如下

/go{2,10}d/   表示字母 "o" 只能可以出现 2 次,3 次,4 次,5 次,6 次 ... 一直到 10 次
/go{2,}d/     表示字母 "o" 必须至少出现 2 次或着 2 次以上

正则表达式中的圆括号表示将多个字符当成一个完整的对象来看待。比如 /th(in){1}king/ 就表示其中字符串 “in” 必须出现 1 次。而如果不加圆括号就变成了 /thin{1}king/ 这个就表示其中字符 “n” 必须出现 1 次。

使用 AWK 移除行中特定模式

接上面正则使用,比如文件中有行数据

/abc/def/123 456
/abc/def/222 456

想要移除 123,保留之前的字母和后面的数字,则可以使用

awk 'sub(/[0-9]+/,"",$1)' /path/to/file

一些组合使用

使用 awk 过滤 history 输出,找到最常用的命令

history | awk '{a[$2]++}END{for(i in a){print a[i] " " i}}' | sort -rn | head

过滤文件中重复行

awk '!x[$0]++' <file>

将一行长度超过 72 字符的行打印

awk 'length>72' file

查看最近哪些用户使用系统

last | grep -v "^$" | awk '{ print $1 }' | sort -nr | uniq -c

假设有一个文本,每一行都是一个 int 数值,想要计算这个文件每一行的和,可以使用

awk '{s+=$1} ENG {printf "%.0f", s}' /path/to/file

reference

  • http://www.ruanyifeng.com/blog/2018/11/awk.html

2018-01-12 linux , command , awk , ed , editor

斐讯 T1 盒子去除广告

斐讯投资送硬件这路子是着魔了,所以在斐讯 K2P 之后又入手了这个 T1 的盒子,配置 2G RAM,16G Sdcard,配置还不错。接口有 USB,网口,HDMI 口,还有 一个 AV 输出口,日常使用是没有任何问题的,初尝试一下非常流畅,不过让我不爽的是,第一次进入竟然需要验证手机,说是 CIBN 盒子的验证,如果不注册还不让进入。

配置

CPU:Amlogic S912 八核 Cortex-A53 CPU up to 2.0GHz GPU:ARM Mali-T820MP3 GPU up to 750MHz

2G RAM+16G ROM,双频 WIFI,且支持 ac,但只是单天线

删除不需要的应用

首先,进入盒子的设置 – 高级 – 远程调试打开,然后下载 adb, 开发过 Android 的人,或者大概熟悉 Android 的人一定用过这个命令,我之前也写过文章,总结 adb 常用的命令

将盒子通电,将盒子联网(可以使用网线,也可以使用无线网络),只要能够在局域网中有一个 IP 地址,然后进路由器里记录,找到盒子的 IP(192.168.x.x)地址。

adb connect 192.168.x.x        (盒子 IP)
adb shell
pm list packages # 可以用来查看安装的应用列表
pm uninstall -k --user 0 com.feixun.dangbeimarket     #自带 cibn 当贝市场
pm uninstall -k --user 0 com.tianci.ad                #广告
pm uninstall -k --user 0 com.feixun.qiyiguo           #自带爱奇艺
pm uninstall -k --user 0 cn.cibn.health               #健康中国
pm uninstall -k --user 0 com.pptv.tvsports.preinstall #CIBN 体育
pm uninstall -k --user 0 com.phicomm.phiweather       #天气
pm uninstall -k --user 0 com.pplive.atv               #聚精彩
pm uninstall -k --user 0 cn.cibntv.ott                #CIBN 高清影视
pm uninstall -k --user 0 com.phicomm.update           #斐讯 OTA 升级
pm uninstall -k --user 0 com.phicomm.datatracker      #斐讯数据收集

如果想要恢复最原始的状态,在设置选项里,恢复出厂设置,还是最原来的状态。

添加过滤规则

如果路由器支持过滤广告,可以添加如下两条规则:

asimgs.cp61.ott.cibntv.net
hoisin.coocaatv.com

安装应用

简单的方法,只通过遥控器操作,先用系统自带的当贝市场,找到“视频加速器”这个应用,安装然后运行,就会在视频加速器运行界面的下方,看到有“当贝市场”,再安装这个当贝市场。

如果你有需要安装本地文件的 APK 安装包,有两种方式,推荐使用 adb 方式安装,先将想要安装的 apk,下载到本地文件,然后 adb 连接盒子

adb connect <ip>
adb install /path/to/app.apk   # 安装本地路径下的应用

直接将本地的应用安装到盒子。

如果安装不成功,记得禁用安装验证,在 adb 连接的情况下

settings put global package_verifier_enable 0

或者用“悟空遥控器”这个应用,当贝市场里可以搜索它来安装,但也要在手机上下载安装“悟空遥控器”APP,里面有个“本地 APK 推送”,用它就可以安装你想要装的应用了,只要有 APK 安装包。

最后要强烈推荐一个投屏 App,乐播投屏,因为斐讯 T1 并没有 DLNA 的功能,但幸而只要一个 App 就能够解决,这样就能够将手机爱奇艺上的内容快速的投屏到盒子上。

斐讯 t1_home_screen

su 密码

恩山最近的帖子更新了 T1 su 的密码,记录一下

31183118

最后这篇文章 是我使用过并且觉得不错的 Android TV 的应用,可以参考。

reference


2018-01-10 android , box , adb , apk , ads , phicomm , 斐讯 , tv , 电视盒子

Linux 下查看内存使用

在 Linux 下,命令行就是一切,GUI 不是任何时候都可用的,所以掌握一定的常用命令,能够方便日常使用,比如查看进程,查看内存占用,等等,这篇文章就总结了一下 Linux 下查看当前系统占用内存的命令。

free

free 命令是最常用的查看系统使用内存及剩余可用内存的命令。free 命令能够显示系统使用和可用的物理内存,同时能够显示 swap 分区内容使用量。free 命令也能够显示被 kernel 使用的 buffers 和 caches。free 命令通过解析 /proc/meminfo 来收集信息。

free -m
              total        used        free      shared  buff/cache   available
Mem:          15911        8803        1100         886        6007        3750
Swap:         16246        3100       13146

-m 选项表示使用 MB 来显示数据,上面表示总共 15911 MB 内存,使用了 8803 M 内存。

A buffer is something that has yet to be “written” to disk. A cache is something that has been “read” from the disk and stored for later use.

上面的解释非常清晰, buffer 用于存放要输出到 disk 的数据,cache 是存放 disk 上读取的数据,这两者都为了提高 IO 性能由 OS 控制管理的。

还有一些其他比较有用的参数,比如 free -h 显示比较可读 h for human,还有 free -g 使用 GB 为单位显示信息。

/proc/meminfo

第二种方法就是直接读取 /proc/meminfo 文件, /proc 文件系统并不是真实的文件系统,不真正包含文件,他是虚拟文件系统,存储的是当前内核运行状态的一系列特殊文件。

运行命令 cat /proc/meminfo 检查其中的 MemTotal, MemFree, Buffers, Cached, SwapTotal, SwapFree 这几个字段,显示的内容和 free 命令是一样的。

cat /proc/meminfo
less /proc/meminfo
more/proc/meminfo
egrep --color 'Mem|Cache|Swap' /proc/meminfo

vmstat

通过 vmstat-s 选项来显示内存使用

vmstat -s
 16293800 K total memory
  9125168 K used memory
 10143256 K active memory
  2252796 K inactive memory
  1001072 K free memory
   746520 K buffer memory
  5421040 K swap cache
 16636924 K total swap
  3174096 K used swap
 13462828 K free swap
 33736485 non-nice user cpu ticks
    57413 nice user cpu ticks
 10985936 system cpu ticks
465486969 idle cpu ticks
  6378633 IO-wait cpu ticks
        0 IRQ cpu ticks
   114100 softirq cpu ticks
        0 stolen cpu ticks
 41745039 pages paged in
125109404 pages paged out
  1098642 pages swapped in
  2128871 pages swapped out
2199460184 interrupts
3279263642 CPU context switches
1514898192 boot time
   3676272 forks

前面几行就能看到内存使用。

vmstat 命令能够显示进程,内存,paging,block IO,traps,disks,和 cpu 的活动及使用状态。

top

top 命令通常被用来检查每一个进程的内存和 CPU 使用,但其实也可以用他来查看系统整个的内存使用,最上方的数据就能看到。

同样,可视化程度更高的 htop 也能够快速的查看到使用的内存

dmidecode

通过一下命令也可以查看,这个命令只能够查看到物理内存条的大小,但是可以提供给我们一个信息就是系统的总内存,是由两个 8G 内存组成,而不是由 8 个 2G 内存条组成。

sudo dmidecode -t 17

选项 -t--type 缩写

sudo dmidecode -t 17
# dmidecode 3.0
Getting SMBIOS data from sysfs.
SMBIOS 2.8 present.

Handle 0x001A, DMI type 17, 40 bytes
Memory Device
        Array Handle: 0x0019
        Error Information Handle: Not Provided
        Total Width: 64 bits
        Data Width: 64 bits
        Size: 8192 MB
        Form Factor: DIMM
        Set: None
        Locator: DIMM1
        Bank Locator: Not Specified
        Type: DDR4
        Type Detail: Synchronous
        Speed: 2400 MHz
        Manufacturer: 802C0000802C
        Serial Number: 12303315
        Asset Tag: 0F161300
        Part Number: 8ATF1G64AZ-2G3B1
        Rank: 1
        Configured Clock Speed: 2133 MHz
        Minimum Voltage: Unknown
        Maximum Voltage: Unknown
        Configured Voltage: 1.2 V

Handle 0x001B, DMI type 17, 40 bytes
Memory Device
        Array Handle: 0x0019
        Error Information Handle: Not Provided
        Total Width: 64 bits
        Data Width: 64 bits
        Size: 8192 MB
        Form Factor: DIMM
        Set: None
        Locator: DIMM2
        Bank Locator: Not Specified
        Type: DDR4
        Type Detail: Synchronous
        Speed: 2400 MHz
        Manufacturer: 802C0000802C
        Serial Number: 1230330E
        Asset Tag: 0F161300
        Part Number: 8ATF1G64AZ-2G3B1
        Rank: 1
        Configured Clock Speed: 2133 MHz
        Minimum Voltage: Unknown
        Maximum Voltage: Unknown
        Configured Voltage: 1.2 V

这个输出信息中可以看到电脑使用的两条 8G 的内存,类型 DDR4,速度是 2400 MHz.

下面这个命令也能够查看电脑上安装的内存条

sudo lshw -short -C memory
H/W path        Device     Class          Description
=====================================================
/0/0                       memory         64KiB BIOS
/0/18/15                   memory         128KiB L1 cache
/0/18/16                   memory         1MiB L2 cache
/0/18/17                   memory         8MiB L3 cache
/0/14                      memory         128KiB L1 cache
/0/19                      memory         16GiB System Memory
/0/19/0                    memory         8GiB DIMM Synchronous 2400 MHz (0.4 ns)
/0/19/1                    memory         8GiB DIMM Synchronous 2400 MHz (0.4 ns)
/0/19/2                    memory         [empty]
/0/19/3                    memory         [empty]
/0/100/1f.2                memory         Memory controller

GUI

同样可以使用 GUI 来查看,Gnome System Monitor 提供一个简单的界面显示系统进程,内存和文件系统。可以使用以下命令在终端开启

gnome-system-monitor

reference


2018-01-10 linux , memory , ram , free , command

Gson 使用笔记

Gson 是 Google 发布的一个用于序列化和反序列化 json 的工具库,可以非常轻松的实现 json 到 java Object 的转变,也同样非常简单的可以将一个 Java 实例序列化为 json。

Gson 包中主要的类有 Gson, GsonBuilder, JsonParser 等等。

基本类介绍

JsonParser

JsonParser 是将 json 串解析成 JsonElement 的工具类。JsonParser 有三个 parse() 方法,分别接受不同类型的参数:

  • String
  • Reader
  • JsonReader

内部实现时使用 JsonReader 类进行解析。

基本使用

Gson 最基本的使用方法,无非就是 toJson()fromJson() 两个函数,对于简单类,可以使用如下方式:

String json = gson.toJson(target); // serializes target to Json
MyType target2 = gson.fromJson(json, MyType.class); // deserializes json into target2

如果类中含有数组,会需要用到 toJson(Object, Type)fromJson(String, Type) 这两个方法:

Type listType = new TypeToken<List<String>>() {}.getType();
List<String> target = new LinkedList<String>();
target.add("blah");

Gson gson = new Gson();
String json = gson.toJson(target, listType);
List<String> target2 = gson.fromJson(json, listType);

属性重命名

Gson 默认情况下会使用 POJO 一致的属性名去解析和生成 json 字串,但是如果想要解析和生成的时候重命名字段,可以使用 @SerializedName 来重命名。

比如 json 中的字段叫做 email_address,而在 Java 类中,可以改为 emailAddress。这样,gson 在生成时会自动将 emailAddress 属性,改为 email_address

@SerializedName("email_address")
public String emailAddress;

如果 emailAddress 在 json 中还有其他的方式,也可以使用:

@SerializedName(value = "emailAddress", alternate = {"email", "email_address"})
public String emailAddress;

重新格式化序列化和反序列化的内容

在将 json 转化为 Java Object 的时候,可以自定义 Deserializer ,格式化其中的某一些字段,比如下面内容,将 dateOfBirth 修改了格式。

public class ActorGsonDeserializer implements JsonDeserializer<ActorGson> {
    private SimpleDateFormat sdf = new SimpleDateFormat("MMM dd, yyyy hh:mm:ss a");

    @Override
    public ActorGson deserialize(JsonElement json, Type type, JsonDeserializationContext jsonDeserializationContext)
            throws JsonParseException {
        JsonObject jsonObject = json.getAsJsonObject();

        JsonElement jsonImdbId = jsonObject.get("imdbId");
        JsonElement jsonDateOfBirth = jsonObject.get("dateOfBirth");
        JsonArray jsonFilmography = jsonObject.getAsJsonArray("filmography");

        ArrayList<String> filmList = new ArrayList<>();
        if (jsonFilmography != null) {
            for (int i = 0; i < jsonFilmography.size(); i++) {
                filmList.add(jsonFilmography.get(i).getAsString());
            }
        }

        ActorGson actorGson = null;
        try {
            actorGson = new ActorGson(jsonImdbId.getAsString(), sdf.parse(jsonDateOfBirth.getAsString()), filmList);
        } catch (ParseException e) {
            e.printStackTrace();
        }
        return actorGson;
    }
}



Gson gson = new GsonBuilder()
        .registerTypeAdapter(ActorGson.class, new ActorGsonDeserializer())
        .create();

忽略某些字段

使用 transient 关键字

在 java 序列化是,一旦变量被 transient 修饰,变量将不再是持久化的一部分,变量内容在序列化后无法获得访问。同样如果在使用 Gson 序列化 json 的时候,添加关键字 transient 同样,Gson 也会忽略 该字段:

private transient int id;

需要注意的是,如果一个 field 是 static 静态变量,gson 也会排除。Gson 在创建的时候可以使用 excludeFieldsWithModifiers 来指定排除的 field:

Gson gson = new GsonBuilder()
    .excludeFieldsWithModifiers(Modifier.STATIC, Modifier.TRANSIENT, Modifier.VOLATILE)
    .create();

使用 @Expose 注解保留关心的字段

最早不知道 transient 关键字的时候,看文档中只写了 @Expose 注解,但其实效果是一样的。使用 @Expose 注解来保留关心的字段,其他不需要的字段可以不注解,同样能够达到效果。

private int id; // 忽略 id
@Expose private String name;    // 保留 name

如果使用 @Expose 注解,那么则需要使用 GsonBuilder()

Gson gson = new GsonBuilder().excludeFieldsWithoutExposeAnnotation().create();

自定义 ExclusionStrategy 规则

如果有更加复杂的排除规则,比如某一批 Field,或者指定的 Class 不需要 serialize ,可以使用 ExclusionStrategy 来自定规则。

import com.google.gson.ExclusionStrategy;
import com.google.gson.FieldAttributes;
import com.jutils.gson.entity.Car;
import com.jutils.gson.entity.PersonStrategy;

/**
 * ExclusionStrategy
 *
 * A strategy (or policy) definition that is used to decide whether or not a field or top-level class should be serialized or deserialized as part of the JSON output/input.
 */
public class ExcluStrategy implements ExclusionStrategy {
    @Override
    public boolean shouldSkipField(FieldAttributes f) {
        // ignore id field
        if (f.getDeclaringClass() == PersonStrategy.class && f.getName().equals("id")) {
            return true;
        }
        return false;
    }

    @Override
    public boolean shouldSkipClass(Class<?> aClass) {
        if (aClass == Car.class) {
            return true;
        }
        return false;
    }
}

比如说上面这个,就忽略 PersonStrategy 类中的 Field “id”,还有 Car 类。如果使用这种方式,那么需要在构造 gson 时:

Gson gson = new GsonBuilder().setExclusionStrategies(new ExcluStrategy()).create();

实现自定义注解

如果熟悉了这个 ExclusionStrategy 就可以书写自己的注解。

注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Exclude {
}

AnnotationExclusionStrategy 类

public class AnnotationExclusionStrategy implements ExclusionStrategy {
    @Override
    public boolean shouldSkipField(FieldAttributes fieldAttributes) {
        return fieldAttributes.getAnnotation(Exclude.class) != null;
    }

    @Override
    public boolean shouldSkipClass(Class<?> aClass) {
        return false;
    }
}

测试方法

@Test
public void annotationTest() {
    PersonAnnotation annotation = new PersonAnnotation(1L, "name", 10);
    Gson gson = new GsonBuilder().setExclusionStrategies(new AnnotationExclusionStrategy()).create();
    String s = gson.toJson(annotation);
    System.out.println(s);
}

将 JSON 中小写下划线转成驼峰

使用 GsonBuilder 来构造 Gson,然后传入 FieldNamingPolicy,这个方法接受很多参数,不仅可以做到将小写下划线转驼峰,还有其他很多功能。

Gson gson = new GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.create();

FieldNamingPolicy 还有这些

IDENTITY
Using this naming policy with Gson will ensure that the field name is unchanged.
LOWER_CASE_WITH_DASHES
Using this naming policy with Gson will modify the Java Field name from its camel cased form to a lower case field name where each word is separated by a dash (-).
LOWER_CASE_WITH_UNDERSCORES
Using this naming policy with Gson will modify the Java Field name from its camel cased form to a lower case field name where each word is separated by an underscore (_).
UPPER_CAMEL_CASE
Using this naming policy with Gson will ensure that the first "letter" of the Java field name is capitalized when serialized to its JSON form.
UPPER_CAMEL_CASE_WITH_SPACES
Using this naming policy with Gson will ensure that the first "letter" of the Java field name is capitalized when serialized to its JSON form and the words will be separated by a space.

反序列化时默认值

某一些情况下在反序列化 json 到 Object 时,在某些字段 JSON 中缺失时,想要给 Object 提供一个默认值,但是 Gson 在处理原始类型时,比如 int 字段,如果缺失会自动赋值为 0,某些情况下是不符合预期的。Gson 在设定默认值时需要,在 Object 构造函数中初始化该字段,并且实现 InstanceCreator 接口。

public class RawDataInstanceCreator implements InstanceCreator<RawData> {

  @Override
  public RawData createInstance(Type type) {
    return new RawData();
  }
}

构造时传入:

Gson gson = new GsonBuilder()
    .registerTypeAdapter(RawData.class, new RawDataInstanceCreator())
    .create();

Gson 反序列化时类型不匹配

举一个比较通俗的例子就是,当一条 JSON 数据返回时,字段值是一个 Double,但是返回的时候是放在字符串中返回的。这个时候就需要用到 TypeAdapter。

class DoubleTypeAdapter extends TypeAdapter<Number> {
	@Override
	public void write(JsonWriter jsonWriter, Number number) throws IOException {
	  jsonWriter.value(number);
	}

	@Override
	public Number read(JsonReader jsonReader) throws IOException {
	  if (jsonReader.peek() == JsonToken.NULL) {
		jsonReader.nextNull();
		return 0D;
	  }
	  String result = jsonReader.nextString();
	  if ("".equals(result)) {
		return 0D;
	  }
	  return Double.parseDouble(result);
	}
}

然后创建 Gson 时:

    Gson gson = new GsonBuilder()
        .registerTypeAdapter(Double.class, new DoubleTypeAdapter()).create();

reference


2018-01-09 java , gson , json , google

小米路由器 3G 开启 SSH 安装 MT 工具箱

下面是小米路由器折腾记录,包括开启 SSH,然后安装 MT 工具箱,主要是为了其中的两个插件,一个是去广告,一个是 SS 代理,不过附带竟然发现了 frp 插件,开心啊。下面就是具体的记录。

小米路由器刷入开发版

下载 开发版,在后台点击上传安装开发版的 bin,然后等待重启,完成开发版安装。

小米路由器开始 SSH

小米帐号绑定小米路由器,设置路由器可正常上网,并使用手机版小米 WiFi 绑定路由器,然后在绑定小米账号的前提下,进入 https://d.miwifi.com/rom/ssh 这个网站,然后找到 SSH 登录的 root 密码,之后会用到。

工具包使用方法:小米路由器需升级到开发版 0.5.28 及以上,小米路由器 mini 需升级到开发版 0.3.84 及以上,小米路由器 3 即将支持。注意:稳定版不支持。 请将下载的工具包 bin 文件复制到 U 盘(FAT/FAT32 格式)的根目录下,保证文件名为 miwifi_ssh.bin; 断开小米路由器的电源,将 U 盘插入 USB 接口; 按住 reset 按钮之后重新接入电源,指示灯变为黄色闪烁状态即可松开 reset 键; 等待 3-5 秒后安装完成之后,小米路由器会自动重启,之后您就可以尽情折腾啦 :)

如果 Chrome 浏览器出现错误提示:”This site can’t be reached. d.miwifi.com refused to connect. ERR_CONNECTION_REFUSED”,需要手动将http替换为https

在使用 ssh 登录路由器之前确认路由器的 IP 地址,比如我下面例子中会使用 192.168.31.1 来举例。

刷入 MT 工具箱

MT 工具箱是目前第三方插件里面最为方便易用的插件集合

KMS 服务器,VSFTP 服务器,VPN 服务器,远程管理,ARIA2,Koolproxy 广告过滤,阿呆喵广告过滤,Shadowsocks,webshell, frp 服务

Misstar Tools 2.0 工具箱安装,经过上面的几个步骤,开启 SSH 之后,使用 ssh root@192.168.31.1 来连接路由器,使用之前获取的 SSH root 密码登录,进去之后 passwd 修改 root 密码,以方便下一次使用,然后直接执行如下代码,就能安装 MT 工具箱。

wget http://www.misstar.com/tools/appstore/install.sh -O /tmp/install.sh && chmod a+x /tmp/install.sh && /tmp/install.sh

卸载

wget http://www.misstar.com/tools/uninstall.sh -O /tmp/uninstall.sh && chmod +x /tmp/uninstall.sh && /tmp/uninstall.sh

安装 Shadowsocks 科学上网插件的方法

在开启 SSH,并且安装 Misstar Tools 工具箱的前提下,有两种方法可以安装 Shadowsocks 插件,第一种就是使用备份的文件,传入路由器之后运行安装,第二种直接在安装页面修改页面内容,推荐使用第二种方法。

方法一:使用文件安装

  1. 将压缩包中两个文件传到路由器,放在同一目录,名字不要改。
  2. 执行:chmod +x ./install_ss & ./install_ss add
  3. 刷新路由器后台,科学上网插件已经出现了。
  4. 卸载的时候执行:chmod +x ./install_ss & ./install_ss del

from: http://bbs.xiaomi.cn/t-13765387

方法二:修改页面 ID

使用 Chrome 打开 MT 插件管理页面,使用开发者工具,定位页面中任意一个 安装 按钮,然后找到代码中的 id="ftp" 字样,修改为 id="ss" ,然后点安装,成功后会回到 MT 工具箱首页,配置使用即可。

刷其他固件

按照上面官方的步骤操作完路由器就已经获取 root 权限了,再使用 ssh 工具连接路由即可,建议在进行下一步操作之前备份原版分区文件

root@XiaoQiang:~# cat /proc/mtd
dev:    size   erasesize  name
mtd0: 07f80000 00020000 "ALL"
mtd1: 00080000 00020000 "Bootloader"
mtd2: 00040000 00020000 "Config"
mtd3: 00040000 00020000 "Bdata"
mtd4: 00040000 00020000 "Factory"
mtd5: 00040000 00020000 "crash"
mtd6: 00040000 00020000 "crash_syslog"
mtd7: 00040000 00020000 "reserved0"
mtd8: 00400000 00020000 "kernel0"
mtd9: 00400000 00020000 "kernel1"
mtd10: 02000000 00020000 "rootfs0"
mtd11: 02000000 00020000 "rootfs1"
mtd12: 03580000 00020000 "overlay"
mtd13: 012a6000 0001f000 "ubi_rootfs"
mtd14: 030ec000 0001f000 "data"

查看 U 盘挂载的位置命令:

df -h

U 盘一般是 /extdisks/ 开头,后面的可能不一样,我的是: /extdisks/sda4/

备份小米路由器 3G 原版分区到文件,每行是一条命令,分别执行,最后一条可能会报错,可不用理会,最有用的是 mtd0-mtd4

dd if=/dev/mtd0 of=/extdisks/sda4/rom/ALL.bin
dd if=/dev/mtd1 of=/extdisks/sda4/rom/Bootloader.bin
dd if=/dev/mtd2 of=/extdisks/sda4/rom/Config.bin
dd if=/dev/mtd3 of=/extdisks/sda4/rom/Bdata.bin
dd if=/dev/mtd4 of=/extdisks/sda4/rom/Factory.bin
dd if=/dev/mtd5 of=/extdisks/sda4/rom/crash.bin
dd if=/dev/mtd6 of=/extdisks/sda4/rom/crash_syslog.bin
dd if=/dev/mtd7 of=/extdisks/sda4/rom/reserved0.bin
dd if=/dev/mtd8 of=/extdisks/sda4/rom/kernel0.bin
dd if=/dev/mtd9 of=/extdisks/sda4/rom/kernel1.bin
dd if=/dev/mtd10 of=/extdisks/sda4/rom/rootfs0.bin
dd if=/dev/mtd11 of=/extdisks/sda4/rom/rootfs1.bin
dd if=/dev/mtd12 of=/extdisks/sda4/rom/overlay.bin
dd if=/dev/mtd13 of=/extdisks/sda4/rom/ubi_rootfs.bin
dd if=/dev/mtd14 of=/extdisks/sda4/rom/data.bin

首先,下载 Breed 刷入不死 breed,

下载地址:https://breed.hackpascal.net/ (搜索 breed-mt7621-xiaomi-r3g.bin)

用命令下载,在电脑终端中运行

wget https://breed.hackpascal.net/breed-mt7621-xiaomi-r3g.bin

将文件传入路由器

scp breed-mt7621-xiaomi-r3g.bin root@192.168.31.1:/tmp/

进入路由器

ssh root@192.168.31.1
cd /tmp
mtd -r write breed-mt7621-xiaomi-r3g.bin Bootloader

机器会重新启动,指示灯变蓝,确保电脑设置为自动获取 IP 地址,使用网线连接。刷入成功后,断掉电源,用东西顶住复位键不松开,然后再接上电源等待 10 秒左右放开复位键,浏览器输入 http://192.168.1.1 即可进行 Breed Web 恢复界面:

如果要刷入其他固件,打开 Breed Web 恢复控制台,点击左侧“固件更新”,钩选“固件”,选择固件,再点“上传”

更多内容可查看: http://www.right.com.cn/forum/thread-161906-1-1.html

小米路由器的目录结构

小米路由器基本上沿用了 Linux 的目录结构,但是也有一些区别,在用了一段时间之后发现某些目录被写满了导致一些第三方服务无法开启,也是很恼人了。这里及列一下这些目录的作用,以便于清理。

/       根目录
bin    二进制可执行命令
boot   bootloader 启动相关
data   用户数据文件
dev    设备文件,驱动等等
etc    配置文件
extdisks        外置硬盘挂载点
lib     共享库
mnt     临时挂载点
opt     可选程序安装点
proc    系统内存映射虚拟目录,可用来获取系统信息
root    系统管理员主目录
sbin    系统管理命令
sys
userdisk    路由硬盘(一般为内置)
usr         存放应用程序和文件
tmp        临时存放点
www         浏览器网页存放区

reference


2018-01-07 router , xiaomi , ssh , frp , shadowsocks

电子书

本站提供服务

最近文章

  • AI Shell 让 AI 在命令行下提供 Shell 命令 AI Shell 是一款在命令行下的 AI 自动补全工具,当你想要实现一个功能,敲一大段命令又记不住的时候,使用自然语言让 AI 给你生成一个可执行的命令,然后确认之后执行。
  • 最棒的 Navidrome 音乐客户端 Sonixd(Feishin) Sonixd 是一款跨平台的音乐播放器,可以使用 [[Subsonic API]],兼容 Jellyfin,[[Navidrome]],Airsonic,Airsonic-Advanced,Gonic,Astiga 等等服务端。 Sonixd 是一款跨平台的音乐播放器,可以使用 [[Subsonic API]],兼容 Jellyfin,[[Navidrome]],Airsonic,Airsonic-Advanced,Gonic,Astiga 等等服务端。
  • 中心化加密货币交易所 Gate 注册以及认证 Gate.io 是一个中心化的加密货币交易所。Gate 中文通常被称为「芝麻开门」,Gate 创立于 2013 年,前身是比特儿,是一家致力于安全、稳定的数字货币交易所,支持超过 1600 种数字货币的交易,提供超过 2700 个交易对。
  • 不重启的情况下重新加载 rTorrent 配置文件 因为我在 Screen 下使用 rTorrent,最近经常调试修改 rtorrent.rc 配置文件,所以想要找一个方法可以在不重启 rTorrent 的情况重新加载配置文件,网上调查了一下之后发现原来挺简单的。
  • Go 语言编写的网络穿透工具 chisel chisel 是一个在 HTTP 协议上的 TCP/UDP 隧道,使用 Go 语言编写,10.9 K 星星。