经常会有这样一种需求,为了方便部署软件或者工具,降低部署安装配置难度,需要把安装过程封装起来,只需要执行一条命令即可。

实现方法:
首先新建一个文件,命名为shell_pack.sh,写入以下内容:

#!/bin/bash - 
#===============================================================================
#
#          FILE: shell_pack.sh
# 
#         USAGE: ./shell_pack.sh 
# 
#   DESCRIPTION: 
# 
#       OPTIONS: ---
#  REQUIREMENTS: ---
#          BUGS: ---
#         NOTES: ---
#        AUTHOR: lwq (28120), scue@vip.qq.com
#  ORGANIZATION: 
#       CREATED: 04/22/2015 02:38:01 PM CST
#      REVISION:  ---
#===============================================================================

#===  FUNCTION  ================================================================
#         NAME:  usage
#  DESCRIPTION:  Display usage information.
#===============================================================================
function usage ()
{
        cat <<- EOT

  Usage :  $0 -p package -s script file1 file2 file3 ..

  Options:
  -h|help       Display this message
  -p|package    The output package name
  -s|script     The script will run when unpack package
  Other         The all files what you want to pack

EOT
}    # ----------  end of function usage  ----------

#-----------------------------------------------------------------------
#  Handle command line arguments
#-----------------------------------------------------------------------

while getopts ":hp:s:" opt
do
  case $opt in

    h|help    ) usage; exit 0   ;;
    p|package ) package_name=$OPTARG ;;
    s|script  ) install_script=$OPTARG ;;
    \?        ) echo -e "\n  Option does not exist : $OPTARG\n"
          usage; exit 1   ;;

  esac    # --- end of case ---
done
shift $(($OPTIND-1))

if [[ -z $package_name ]]; then
    echo "package_name can't not be empty"
    usage
    exit
fi

if [[ -z $package_name ]]; then
    echo "install_script can't not be empty"
    usage
    exit
fi

files=$@

generate_wrapper_script(){
    local install_script=$1
    local wrapper_script=$2
    cat <<-'EOT' >$wrapper_script
#!/bin/sh
echo "begin ..."
unpackdir=/tmp/$(basename $0)_unpack
rm -rf $unpackdir 2>/dev/null
mkdir -p $unpackdir
echo "unpacking ..."
sed '1, /^#__SCRIPTEND__/d' $0 | tar zxf - -C $unpackdir
if [ $? -ne 0 ]; then
    echo "unpack package failed."
    exit 1
fi
echo ""
echo "installing ..."
cd $unpackdir
EOT
    cat <<-EOR >>$wrapper_script
chmod +x $install_script
./$install_script
EOR
    cat <<-'EOE' >>$wrapper_script
if [ $? -ne 0 ]; then
    echo "install failed."
    exit 2
elif [[ -d $unpackdir ]]; then
    rm -rf $unpackdir
fi
echo "install ok, enjoy!"
exit 0
#__SCRIPTEND__
EOE
}

tarfile=package_content_$$.tgz
wrapfile=wrap_$$.sh

echo -e "start packing ..\n"
tar zcvf $tarfile $files $install_script
generate_wrapper_script $install_script $wrapfile
cat $wrapfile $tarfile > $package_name
chmod +x $package_name

echo -e "\noutput: $package_name\n"

rm -f $tarfile
rm -f $wrapfile

用法:shell_pack.sh -p package -s script file1 file2 file3 ..

Linux下敲docker命令抱一下错误:
FATA[0000] Get http:///var/run/docker.sock/v1.18/images/json: dial unix /var/run/docker.sock: permission denied. Are you trying to connect to a TLS-enabled daemon without TLS?

解决办法:
sudo chmod -R 777 /var/run/docker.sock

POST通过Request Body传递参数将请求整体提交给服务器,POST请求中content-type的三种数据类型:

  1. content-type:application/x-www-form-urlencoded
  2. content-type:application/json
  3. content-type:multipart/form-data

第一种:content-type:application/x-www-form-urlencoded

参数格式:key1=value1&key2=value2
示例图如下:
2020-12-28T12:14:13.png

2020-12-28T12:17:06.png

2020-12-28T12:23:06.png
以上请求消息体数据也可设置为如下图格式(HTTP Header可不设置content-type):
2020-12-28T12:25:22.png
⚠️注意

  • 若不指定content-type,默认使用此格式
  • 若使用Parameters参数格式时,可以不设置content-type类型
  • 若使用Parameters参数格式时,设置content-type:application/json类型去请求,会出现报错
  • 可以使用Body Data格式传Content-Type为application/x-www-from-urlencoded类型的参数,需注意格式

第二种:content-type:application/json

参数为json格式 :{"key1":"value1","key2":"value2"}
示例图如下:
2020-12-28T12:33:54.png

2020-12-28T12:36:49.png
⚠️注意

  • Content-Type为application/json仅可使用BodyData传参,数据格式必须使用application/json去传递参数,否则报错

第三种:content-type:multipart/form-data

⚠️注意
上传文件必须使用content-type:multipart/form-data作为请求头
post结合multipart/form-data才能真正将文件内容传入请求体。

1、删除低版本驱动

sudo apt purge nvidia*

2、把显卡驱动加入PPA

sudo add-apt-repository ppa:graphics-drivers
sudo apt update

3、查找适合自己的闭源驱动

ubuntu-drivers devices

回显如下:

test@lamsfun:~/download$ ubuntu-drivers devices
== /sys/devices/pci0000:00/0000:00:01.0/0000:01:00.0 ==
modalias :         pci:v000010DEd00001C82sv000010DEsd000011BFbc03sc00i00
vendor   : NVIDIA Corporation
model    : GP107 [GeForce GTX 1050 Ti]
driver   : nvidia-driver-390 - third-party free
driver   : nvidia-driver-435 - distro non-free
driver   : nvidia-driver-415 - third-party free
driver   : nvidia-driver-410 - third-party free
driver   : nvidia-driver-440 - third-party free recommended
driver   : xserver-xorg-video-nouveau - distro free builtin

4、选择最新推荐的版本安装

sudo apt install nvidia-driver-440

5、重启系统

sudo reboot

最近要测试图片的显示质量,咨询了一些业内的大佬,提到了这样2个字眼:

  • PSNR:峰值信噪比
  • SSIM:结构相似性

最近要测试图片的显示质量,咨询了一些业内的大佬,提到了这样2个字眼:

  • PSNR:峰值信噪比
  • SSIM:结构相似性

背景:

这几天居家隔离,想着之前的远程控制软件不太好用,要么收费,要么难用,之前也实现了安卓的远程控制,想着自己写一款适用于PC之间的通用远程桌面控制工具。

语言:

思来想去加上调研和自己熟悉的情况,决定采用Java来实现,具体原因有如下几点:

  • 个人相对于C、C++,本身对Java熟悉一点。
  • 为了之后实现安卓端,可以复用部分代码。
  • Java自带的Robot可以很方便实现远程控制,已经包含屏幕截屏,图片编码,用户事件处理。
  • 可能可以跨平台,关于资源占用,现代的21世纪(0202年)的电脑,应该都用上了SSD和4G+的内存了。
  • 其他...

知识储备

远程控制系统主要分为两个端:被控端、控制端。

  1. 首先,我们看看被控端需要做什么?

    • 通过socket/websocket接收远程控制请求
    • 获取屏幕截屏
    • 对截屏进行编码压缩(JPEG|自定义协议)
    • 通过socket/websocket发送截屏
    • 通过socket/websocket接收控制端的用户事件
    • 执行用户事件
    • 以上步骤循环
  2. 然后,我们看看控制端需要做什么?

    • 通过socket/websocket发送远程控制请求
    • 通过socket/websocket接收截屏
    • 把截屏在本地渲染出来(Java Canvas|Direct3D|OpenGL)
    • 通过socket/websocket发送用户事件
    • 以上步骤循环

1、下载Python最新版本3.8文件
2、编译脚本:

#!/bin/bash
COMPILE_ROOT=`pwd`
ANDROID_NDK_ROOT=~/ndk/android-ndk-r20
ANDROID_GCC_ROOT=${ANDROID_NDK_ROOT}/toolchains/llvm/prebuilt/linux-x86_64
ANDROID_GCC_PATH=${ANDROID_GCC_ROOT}/bin

BUILD_PATH=${COMPILE_ROOT}/build
OUT_PATH=${COMPILE_ROOT}/out

OPENSSL_PATH=${COMPILE_ROOT}/thirdparty/libressl-2.7.4/out
OPENSSL_LIB_PATH=${COMPILE_ROOT}/thirdparty/libressl-2.7.4/out/lib

CROSS_COMPILER=aarch64-linux-android-
CROSS_COMPILER_CLANG=aarch64-linux-android28-

#prepare
mkdir -p ${BUILD_PATH}
mkdir -p ${OUT_PATH}
export PATH=${ANDROID_NDK_ROOT}:${ANDROID_GCC_PATH}:$PATH

export ARCH="aarch64"
export CC="${CROSS_COMPILER_CLANG}clang -pie -fPIE" 
export CPP="${CROSS_COMPILER_CLANG}clang -E  -pie -fPIE"
export CXX="${CROSS_COMPILER_CLANG}clang++  -pie -fPIE"

export AS="${CROSS_COMPILER}as"
export LD="${CROSS_COMPILER}ld  -pie -fPIE"
export GDB="${CROSS_COMPILER}gdb"
export STRIP="${CROSS_COMPILER}strip"
export RANLIB="${CROSS_COMPILER}ranlib"
export OBJCOPY="${CROSS_COMPILER}objcopy"
export OBJDUMP="${CROSS_COMPILER}objdump"
export AR="${CROSS_COMPILER}ar"
export NM="${CROSS_COMPILER}nm"
export READELF="${CROSS_COMPILER}readelf"
export M4=m4
export TARGET_PREFIX=$CROSS_COMPILER
export CONFIG_SITE="config.site"
export CXXFLAGS="-D__ANDROID_API__=28 "

cd ${BUILD_PATH}

echo -e "ac_cv_file__dev_ptmx=yes\nac_cv_file__dev_ptc=no" > config.site

../configure --host=aarch64-linux-android  \
--host=aarch64-linux \
--build=x86_64-pc-linux-gnu \
--target=aarch64-linux-android \
LDFLAGS="-Wl,--allow-shlib-undefined -D__ANDROID_API__=28 -fPIC -L${OPENSSL_LIB_PATH}" \
CFLAGS="-D__ANDROID_API__=28  " \
CPPFLAGS="-D__ANDROID_API__=28" \
--enable-shared \
--enable-ipv6 \
--with-openssl=${OPENSSL_PATH} \
--prefix=${OUT_PATH}

make -j8 2>&1 |tee build.log
echo "-----------build success!-------------"

注意: 大家在使用时候注意自己的ndk所在路径,按自己的实际情况进行修改,我的ndk存放在自己的home目录下。

非root用户登录MySQL报错如下:

mysql -uroot -p
#ERROR 1698 (28000): Access denied for user 'root'@'localhost'

解决办法:
1、账号切换为root账号

2、使用以下命令登录

mysql -uroot -p 

3、指定数据库mysql

use mysql;

4、更新密码

update user set plugin="mysql_native_password",authentication_string=password('你的新密码') where user="root";

5、更新权限

FLUSH PRIVILEGES;

6、mysql -u root -p并以新密码登入mysql,enjoy!!!

原理简介:
1、使用反射截图,压缩为webp格式图片,把图片编码为base64格式,通过websocket服务发送给前端,前端绘制出图片。
2、websocket服务器使用websocket库:Java-WebSocket-1.4.0-with-dependencies.jar。
3、事件转发也是走websocket,发送事件类型和相关参数,websocket服务做出对应动作。

ScreenCaptor反射截图关键方法:

public static Bitmap getScreencap(int screenWidth, int screenHeight) {
    Bitmap bitmap = null;
    String surfaceClassName;
    ServiceManager serviceManager = new ServiceManager();

    if (screenHeight == 0 || screenWidth ==0){
        screenWidth = serviceManager.getDisplayManager().getDisplayInfo().getSize().getWidth();
        screenHeight = serviceManager.getDisplayManager().getDisplayInfo().getSize().getHeight();
    }

    if (Build.VERSION.SDK_INT <= 17) {
        surfaceClassName = "android.view.Surface";
    } else {
        surfaceClassName = "android.view.SurfaceControl";
    }

    try {
        // api_level >= 27,截图方法:public static Bitmap screenshot(int width, int height) {}
        if (Build.VERSION.SDK_INT <= 27) {

            bitmap = (Bitmap) Class.forName(surfaceClassName).getDeclaredMethod("screenshot", new Class[]{Integer.TYPE, Integer.TYPE})
                    .invoke(null, new Object[]{screenWidth, screenHeight});

        } else {
            // 参考这里https://medium.com/@punpun/android-surfacecontrol-screenshot-changed-in-android-pie-9-0-8baf2c91a068
            // api_level大于27,截图方法变为:public static Bitmap screenshot(Rect sourceCrop, int width, int height, int rotation) {}
            Rect rect = new Rect(0, 0, 0, 0);
            int rotation = serviceManager.getDisplayManager().getDisplayInfo().getRotation();

            bitmap = (Bitmap) Class.forName(surfaceClassName).getDeclaredMethod("screenshot", new Class[]{Rect.class, Integer.TYPE, Integer.TYPE, Integer.TYPE})
                    .invoke(null, new Object[]{rect, screenWidth, screenHeight, rotation});
        }
    } catch (Throwable e) {
        e.printStackTrace();
    }
    return bitmap;
}

websocket服务器,关键方法onMessage:

@Override
public void onMessage(WebSocket webSocket, String s) {

        if (s.equals( "help")) {
            String help = "screencap, keyevent, mouseevent";
            webSocket.send(help);
        }
       else if(s.equals("screencap")){
            String screencap = ScreenCaptor.getBase64Screencap();

            webSocket.send(screencap);
        }
        else if(s.equals("keyevent")){
            webSocket.send("keyevent cmd.");
        }
        else if(s.contains("mouseevent")) {

            String[] cmds = s.split("#");

            Input input = new Input();
            int inputSource = InputDevice.SOURCE_TOUCHSCREEN;
            input.sendTap(inputSource, Float.parseFloat(cmds[2]),
                    Float.parseFloat(cmds[3]));
            webSocket.send("mouseevent success.");
        } else{
            webSocket.send("Unknown cmd: " + s);
        }
}

前端js关键代码:

var screen = document.getElementById('screen');
var websocket = '';

var x = 0;
var y = 0;

var downTimestamp = 0;
var upTimestamp = 0;

if (window.WebSocket) {
    websocket = new WebSocket(encodeURI('ws://localhost:8888'));
    websocket.onopen = function() {
        console.log('已连接');
    };
    websocket.onerror = function() {
        console.log('连接发生错误');
        alert("websocket连接失败")
    };
    websocket.onclose = function() {
        console.log('已经断开连接');
    };
    // 消息接收
    websocket.onmessage = function(message) {
        websocket.send("screencap");
        screen.src =  message.data; 
    };
} else {
    alert("该浏览器不支持websocket。<br/>建议使用高版本的浏览器,<br/>如 IE10、火狐 、谷歌  、搜狗等");
}