记一次和流氓软件战斗的过程
一次偶然的机会,看到了一个软件的输出日志上有一个很奇怪的代理请求。本地的随机端口访问到我的代理软件端口,请求转发数据到localhost:[50051-500053]。而且是不停循环。用netstat查看,发现本地并没有监听50051-50053的端口,于是盲猜开始了。
起先以为是我的代理配置错了,导致请求被错误的导向了代理软件。所以把所有软件检查一遍,关闭代理配置。发现连接依旧,不停的创建tcp。
在搜索引擎的帮助下,检查了所有搜出来可能出问题的软件。并且将其卸载了,问题依旧。
难道我电脑因为前段时间装了360中病毒了?于是下载终极杀器-360系统急救箱,花了3个多小时全盘扫描。换来的结果确实系统很干净,只有几个无关痛痒的处理。
好了,这下有点意思了。于是打开proxifier劫持所有localhost上的流量。创建一个不存在的proxy server。然后就坐看监控后台,等待目标出现。
有意思的一幕出现了。explorer.exe 这个软件创建tcp的目标居然是我原本的proxy server。这就神奇了,我没设置系统代理,所有代理都是软件层面配置。explorer.exe 是一个windows的外壳程序,他怎么知道我的代理软件是什么。这下真的是丈二和尚摸不着头脑了。加上夜深了,于是睡觉放弃。等脑子清醒了在搞。
时间来到了第二天,按照习惯更新每天github仓库,在同步github的上突然想到git bash上是不是也配置了代理,于是echo $http_proxy。发现还真他娘的有。然而查看profile文件时,它居然没有 .bash_rc 文件,又是一头雾水。
$ cat ~/.bash_history
Display all 53 possibilities? (y or n)
于是打开搜索工具,发现gitbash可以读环境变量。打开系统环境变量设置一看,果然在个人环境变量里设置了http_proxy=localhost:xxxxx(我真的不记得我啥时候配置的)。删除之。
再次打开proxifier软件,重复之前的步骤,这些explorer.exe 的目标到了localhost:[50051-50053],那么代理工具的这个疑点解决了。但是为什么频繁的访问50051-500053端口呢?
在微软社区上看到一条同样信息的描述
explorer一直在访问localhost的50051,50052,50053端口,请问有人知道这是在做什么吗?
最近偶尔,几个小时内,都会不断开启explorer进程,然后都在用tcp访问localhost的端口,先50051,不通就关闭,再开一个进程访问50052,再不通访问50053,然后又从50051开始,删除explorer进程、重启都没效果,只有几个小时后它才会停下来。 我再本机开了tcplistener,监听端口,读取访问的信息,我挑一些明文贴出来 PRI * HTTP/2.0 SM :authoritylocalhost:50052@:path2/fileExplorerPlugin.FileService/ConfigNotification user-agent4grpc-c++/1.19.1 grpc-c/7.0.0 (windows; chttp2; gold)@grpc-accept-encodingidentity,deflate,gzip@accept-encoding 请问有人知道这是在做什么吗?
底下的回答是让用户执行干净启动,于是我按照要求,禁用了各种非必要服务,关闭自启,重启后问题依旧。
没办法硬着头皮继续按照前面的排查,搜索引擎出现的最多结果是企业微信,但是我已经卸载了问题还在,到底是谁在作妖?那是不是没有卸载干净,抱着试试看的想法,打算删除企业微信的日志文件夹。
神奇的事情发生了,真的删不掉。这我的暴脾气了就来了。上资源管理器一看,果然这个文件夹被explorer.exe 占用了。直觉告诉我,真相就要出来了。
于是按图索骥,根据文章内容,到企业微信社区下载官方提供的反注册插件工具。
1.反注册wedriveplugin插件(双击uninstall_plugins\uninstall_wxdrive.bat) https://share.weiyun.com/0ZTrbRlD
当我满怀欣喜的点开执行时,我内心的冲动再也无法压制,企业微信–垃圾国产软件。
但这并不妨碍我删除他,于是打开火绒的强制清理工具,没有什么问题是不能通过暴力解决的。
删掉,重启explorer.exe。再看监控后台,很清爽了,再也没有异常连接了。世界安静,仿佛电脑运行速度都变快了。
最后总结下:算了,不骂同行了,也许我写的代码也被别人骂过,积点德。给腾讯比个心。真棒,浪费了我将近2天的时间。也算在压抑的疫情氛围下找点乐子。
【油猴脚本】关闭CSDN中占用资源过高的雪点动画
以前用老笔记本访问csdn某些页面时,机器就很卡,一看任务管理器gpu占用很高。mac因为垃圾集显所以特别明显。
排查下来发现是网页里css加载某些动画,那么我们通过油猴脚本来删除。
// ==FeHelperMonkey==
// @reminder 请不要删除这部分代码注释,这是FeHelper油猴脚本能正常工作的基本条件!当然,你可以按需修改这里的内容!
// @id mf_1645510946779
// @name csdn移除雪点动画
// @url-pattern https://blog.csdn.net/*
// @enable true
// @require-js
// @auto-refresh 0
// @updated 2022-02-22 14:59:35
// ==/FeHelperMonkey==
// 在这里,可以随便写你的代码,并且,你的代码中
// 1. 可以进行页面上的所有DOM操作
// 2. 可以访问页面上原本已挂载的所有Js变量,比如页面上已经有了jQuery,你可以直接使用
// 3. 可以依赖注入一个第三方js脚本,然后在你的代码中直接使用,如:依赖jQuery后直接使用
// 4. 好了,你的代码可以这样写:
(() => {
let sources = document.getElementsByTagName('link')
let i=0;
for (let bb of sources) {
if(bb.href.search("whitemove") > 0){
console.log("删除浪费机器性能的动画!"+bb.href);
document.getElementsByTagName('head')[i].removeChild(bb)
i++
}
}
})();
删除动画前资源占用:

删除动画后资源占用:

冬至快乐,“菜鸟公园”博客迁移后第一篇
今天是冬至,祝大家冬至快乐,吃饺子,圆圆满满。

博客已经顺利迁移完成了,再也不用忍受阿里云的各种限制了,没有crontab,网速又慢。以后就用 https://sophie520.top 这个网址了。 sophie520.top 这个网址因为需要备案的原因,所以不指向新博客地址了,避免不必要的麻烦。
关于迁移
其实很早就想迁移博客,只不过因为想用微信公众号的开发者,就一直放在了阿里云的虚拟主机上。但是最近接二连三的宕机,让我对阿里云的服务很不满意,提了技术工单,都是把责任推卸给我,说我博客的 wp-cron 导致了cpu 100%宕机了。我承认这个是耗性能,但是阿里的那个cpu监控,只有0%和100%的跳跃让我笑了。咱也不是不会整是不是,这不就有了迁移这件事了。虽然迁移到了境外,但是从用户角度来看响应速度似乎比阿里云杭州机房还快。这就不得不怀疑阿里云在高价卖垃圾。
记录下迁移的步骤
- 从阿里提取网站代码备份,数据库备份
- 开设新服务器,推荐Ubuntu,安装mariadb,nginx,php,fpm,并调试好。环境搭建可以参考这篇文章
我一般创建下面的文件调试看信息。
<?php
echo phpinfo();
- 创建网站的工作目录,并设置拥有者为
www-data,并且记住设置该目录的上级目录是否能被www-data用户读取。这个问题一般是导致 403 报错的根源。 -
修改
wp-config.php文件里的DB配置,打开同文件内的WP_DEBUG调试开关。 -
导入备份的数据库,更新表
wp_options,如下所示,网址改为新网址,不然网站的跳转会有问题

-
测试下邮件能否正常发送
-
批量修改发表的文章里面的图片地址,主题的图片地址,或者手动修改也行。
关于网站SSL证书
在阿里云申请免费的DV证书,每个账号能申请20张,具体请 Google,签发只需要几分钟。
nginx 里的配置可以参考阿里云给的帮助。
server {
···
listen 443 ssl default_server;
listen [::]:443 ssl default_server;
ssl_certificate xxxx.pem; #需要将xxxx.pem替换成已上传的证书文件的名称。
ssl_certificate_key xxxx.key; #需要将xxxx.key替换成已上传的证书密钥文件的名称。
ssl_session_timeout 5m;
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
# 表示使用的加密套件的类型。
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; #表示使用的TLS协议的类型。
ssl_prefer_server_ciphers on;
···
}
本站点也增加了80端口的跳转,所以大家输入sophie520.top 可以自动跳转到安全的 https 站点。
关于公众号
以后公众号就不提供快递,翻译等查询了,因为国内备案的机器我已经清退了。境外服务器无法接入微信开发。反正公众号也没有多少人看,逃)
让浏览器自动移除知乎的登录弹窗(已更新)
由于某些原因,我注销了知乎的账号。但是在网页的搜索中,会出现一些知乎的内容。当我点进去会发现,知乎会强制登录才能查看内容。以前会有一个关闭的按钮,最近关闭的按钮被去除了。
作为程序员,肯定不希望阅读体验这么差,所以用 油猴工具 制作了如下脚本,自动移除弹窗,并恢复页面的滚动条。
// ==FeHelperMonkey==
// @reminder 请不要删除这部分代码注释,这是FeHelper油猴脚本能正常工作的基本条件!当然,你可以按需修改这里的内容!
// @id mf_1606719966367
// @name 关闭知乎登录
// @url-pattern https://*.zhihu.com/question/*
// @enable true
// @require-js
// @auto-refresh 0
// @updated 2020-12-01 10:03:30
// ==/FeHelperMonkey==
// 在这里,可以随便写你的代码,并且,你的代码中
// 1. 可以进行页面上的所有DOM操作
// 2. 可以访问页面上原本已挂载的所有Js变量,比如页面上已经有了jQuery,你可以直接使用
// 3. 可以依赖注入一个第三方js脚本,然后在你的代码中直接使用,如:依赖jQuery后直接使用
// 4. 好了,你的代码可以这样写:
var toast = (content,time) => {
return new Promise((resolve,reject) => {
let elAlertMsg = document.querySelector("#fehelper_alertmsg");
if (!elAlertMsg) {
let elWrapper = document.createElement('div');
elWrapper.innerHTML = '<div id="fehelper_alertmsg" width="100%" style="position:absolute;top:10px;left:50%;-webkit-transform:translateX(-50%);z-index:100">' +
'<tb style="background:red;display:inline-block;color:white;text-align:center;' +
'padding:10px 10px;margin:0 auto;font-size:20px;border-radius:8px;">' + content + '</tb></div>';
elAlertMsg = elWrapper.childNodes[0];
document.body.appendChild(elAlertMsg);
// elAlertMsg = document.getElementById("fehelper_alertmsg");
// elAlertMsg.style.left=(document.body.clientWidth-elAlertMsg.offsetWidth)/2+"px";
} else {
elAlertMsg.querySelector('p').innerHTML = content;
elAlertMsg.style.display = 'block';
}
window.setTimeout(function () {
elAlertMsg.style.display = 'none';
resolve && resolve();
}, time || 1000);
});
};
(()=>{
toast("【菜鸟公园】关闭知乎登录弹窗",2000).then(()=>{
let a = document.getElementsByClassName('Modal-wrapper');
for (i=0;i<a.length;i++){
a[i].parentNode.removeChild(a[i]);
}
document.documentElement.style.overflow='visible';
});
})();
如何在Windows中体验极致的Linux开发环境
1. 背景
相当长的一段时间,我的工作平台都是MAC,清爽干净的界面,没有广告烦扰,方便的快捷键,好用的触摸板等等。但是换了工作之后,公司配备的是一台Windows台式机,虽然可以带自己的笔记本工作,但是总有些不便。于是一直在折腾Windows下的Linux开发环境。
之前一直是用 vm 搭建,也没有觉得不方便的地方。但是最近一直被安利 wsl2 。于是就安装试用了一番。发现在我的开发环境下完全可以抛弃 vm 了。那么对比 vm 有哪些优势劣势呢。
优势
- 不用每次点击启动vm,然后点开机或者恢复。直接在终端中选择打开。
- 不用配置网络,实际上wsl2 你也没有机会配置。wsl2深度融合Windows环境,在wsl启动的服务,在Windows 可以直接
localhost:port访问 - 挂载了本地磁盘,虽然vm也可以共享,但是没有这个方便。
劣势
- 只有Ubuntu可以选择
- 没有systemd,需要自己些reload脚本
- 启用hyper v之后,影响vm和模拟器运行
2. 安装WSL2
- 从
启用或关闭 Windows 功能里勾选适用于 Linux 的 Windows 子系统和虚拟机平台,重启电脑 - 以管理员身份启动
powershell,输入下面指令wsl --set-default-version 2 - 如果提示要升级内核,按照提示走
- 从 Microsoft Store 下载最新 Linux 分发版, 下载完后点击启动,安装
- [ 可选 ] 换源,使用阿里Ubuntu镜像。 换完之后执行
sudo apt update - [ 可选 ] 从 Microsoft Store 安装 Windows Terminal
3. 搭建开发环境
nginx,php,fpm,mariadb,redis,golang,openssh 等软件均可以通过 apt install 来安装。
通过 service xxx start|restart|stop 来控制状态
- 特别说明
mariadb安装完后需要配置root账号密码,参考文章
# 第一次设置密码
sudo mysql_secure_installation
# 控制台登录, 如果有报错,记得使用sudo
sudo mysql -u root -p
# 修改mysqld 绑定IP
sudo vim /etc/mysql/my.cnf
# 注释下面的语句
bind-address = 127.0.0.1
# php连mysql需要安装扩展
sudo apt install php-mysql
# fastcgi listen字段,需要跟nginx.conf 对应
sudo vim /etc/php/7.4/fpm/pool.d/www.conf
- Ubuntu20.04
apt install的服务配置文件目录整理
| 服务 | 默认路径 | 默认配置文件名 |
|---|---|---|
nginx |
/etc/nginx/nginx.conf |
nginx.conf |
maraiadb |
/etc/mysql/mariadb.conf.d/50-server.cnf |
my.conf |
redis |
/etc/redis/redis.conf |
redis.conf |
php-fpm |
/etc/php/7.4/fpm/php-fpm.conf/etc/php/7.4/fpm/pool.d/www.conf |
php-fpm.conf |
php |
/etc/php/7.4/cli/php.ini |
php.ini |
sshd |
/etc/ssh/ssh_config |
ssh_config |
4. 总结唠嗑
关于wsl2的使用说明,建议阅读官方文档
在 windows 宿主机使用 localhost 就能登录wsl2,不要被国内博客误导!!! 前提是重新安装 openssh 服务。
最后两个安装步骤虽然是可选,但是强烈建议做完,能很大程度的提升使用体验。
换源的好处自然是加快包安装速度
使用 windows Terminal 时让我有点在 MAC 下工作的感觉。总结下有几点:
– 支持多标签页
– 能快捷进入wsl,下拉菜单中点选进入
– 界面重新设计,很清爽,有 Linux 终端的味道,离 MAC 还差点
– 支持 Ctrl C 复制,Ctrl V 粘贴,我很喜欢这个设定。并且 Ctrl C原本的中断功能不受影响
wsl 不支持 systemd ,所以不用折腾开机启动了。需要的服务建议通过 sudo service 服务启动,或者写一个 shell 脚本。
wsl把 windows 磁盘挂载在 /mnt/ 目录下,可以对该目录下文件进行读写操作。
配合 VS CODE 敲代码,只需要安装一个Remote - WSL插件。在 远程资源管理器里选择 WSL Targets 一键连接。 本地开发,即在服务器上开发。NICE
测试过 php 项目 和 golang 的 web 项目,目前没有发现任何功能上的问题。
5. 目前遇到的?坑?
由于我装 ohmyzsh 了,它在git的目录会有额外的操作,所以进入到挂载在 /mnt/ 下面的目录时,会有一段时间读取过程,耗时较长。不用 ohmyzsh 不会有问题。
一个方便的数据库操作shell脚本
用法说明
这个脚本可以方便的备份和恢复数据库,而且通过show参数可以用来查看该实例中有多少个数据库。
唯一需要手动操作的是到脚本里配置账号密码。账号密码放在脚本的目的是为了避免每次输入。
导出文件格式是tar.gz。导入的文件格式是zip。这部分跟我的习惯有关系,我备份的数据库都会在本地进行处理压缩成zip上传,就是为了防止没有经过处理的数据库直接拿去导入。
#!/bin/bash
# 这是一个自动化导出MySQL数据库的脚本
# 修改此处
user="root"
passwd="123456"
port=3306
host="127.0.0.1"
dblist=(
"test"
)
root=`pwd`"/"
function printUsage {
echo "Usage:"
echo "$0 [-t dump|import|show] [-f file]"
echo "导出/导入数据库脚本 v1.1"
echo "导出的sql以数据库命名。"
echo "导入的文件同样是提取sql文件名!"
echo "show 参数可以打印数据库名,用于替换dblist"
echo "dump文件是tar.gz的压缩包,import的文件是zip的压缩包"
}
function printParaErr {
echo "参数错误!"
}
function printFileNotExist {
echo "文件不存在!"
}
function printFileNotSupport {
echo "仅支持压缩文件!(zip)"
}
function cleanOutDir {
if [ -d $outDir ]; then
echo "清理临时目录:" $outDir
rm -rf $outDir
fi
}
function addNote {
echo "是否添加备注文件?[回车跳过]"
read note
if [ ${#note} -gt 1 ];then
cd $root$outDir
echo $note > note.txt
fi
}
if [ $# == 0 ]; then
printUsage
exit 0
fi
GETOPTOUT=`getopt t:f: "$@"`
set -- $GETOPTOUT
while [ -n "$1" ]
do
case $1 in
-t)
type=$2
shift
;;
-f)
inputfile=$2
shift
;;
--)
shift
break
;;
*)
printParaErr
printUsage;
exit 1;
;;
esac
shift
done
if [ $type == 'dump' ]; then
echo "目标数据库为 $user@$host:$port"
elif [ $type == 'import' ]; then
if [ ! -f "$inputfile" ]; then
printFileNotExist
printUsage
exit 0
fi
ext_name="${inputfile##*.}"
if [ $ext_name != 'zip' ]; then
printFileNotSupport
printUsage
exit 0
fi
echo "导入源数据为: $inputfile"
echo "目标数据库为: $user@$host:$port"
elif [ $type == 'show' ]; then
echo "当前实例存在的数据库:(已排除系统数据库)"
else
printParaErr
printUsage;
exit 1;
fi
# echo "请输入数据库密码,按回车键结束"
# stty -echo # 设置输入字符不回显
# read passwd
# stty echo # 取消不回显状态
runTime=`date '+%Y%m%d%H%M'`
outDir="./sqlHandler_$runTime/"
cleanOutDir
if [ $type == 'dump' ]; then
echo "创建导出目录:" $outDir
mkdir -p $outDir
for db_name in ${dblist[*]}; do
cmd="mysqldump -h $host -u $user -P $port -p$passwd $db_name"
echo $cmd | sed "s/-p$passwd/-p****/g";
$cmd > "$outDir$db_name.sql";
done
cd $outDir
tar cvzf "dbbak_$runTime.tar.gz" *
rm -rf *.sql
addNote
echo "导出文件为:"
echo $root$outDir"/"`ls|grep tar`|sed -e "s/\/\//\//g" -e "s/\/\.\//\//g"
elif [ $type == 'import' ]; then
echo "创建临时目录:" $outDir
mkdir -p $outDir
unzip -d $outDir $inputfile
files=$(ls $outDir)
cd $outDir
echo "mysql command:"
for file in ${files[*]}; do
db_name="${file%.*}"
# ext_name="${file##*.}"
cmd="mysql -h $host -u $user -P $port -p$passwd --default-character-set=utf8 $db_name"
echo -e "\t"$cmd " < $file" | sed "s/-p$passwd/-p****/g";
$cmd < $file
done
cd $root
cleanOutDir
# $cmd < $inputfile
elif [ $type == 'show' ]; then
rm -rf $outDir
cmd="mysql -h $host -u $user -P $port -p$passwd "
$cmd -e "show databases;" |grep -v -E 'Database|mysql|information_schema|performance_schema'|sed -e 's/^/"/g' -e 's/$/"/g'
# echo $result
fi
if [[ $? == 0 ]]; then
echo "操作完成!";
else
echo "┗━━ 操作失败!";
fi
PHP中静态变量作用域浅谈
最近看代码,发现有个代码写的有意思,在函数内用static修饰局部变量。当时心想,难道是跟global一样,扩大作用域?又看到同一个类里面,两个方法都用static修饰了同样的变量,我明白了,事情肯定没有我想象的这么简单,所以我写了下面的这个方法来测试下。
<?php
class MyTest
{
public static function func1($arg1)
{
static $result = [];
$p = serialize($arg1);
var_dump($p);
if (isset($result[$p])) {
return $result[$p];
}
$result[$p] = date('Y-m-d H:i:s') . " by func1 rand:".rand(0,100);
return $result[$p];
}
public static function func2($arg1)
{
static $result = [];
$p = serialize($arg1);
var_dump($p);
if (isset($result[$p])) {
return $result[$p];
}
$result[$p] = date('Y-m-d H:i:s') . " by func2 rand:".rand(0,100);
return $result[$p];
}
}
$arg = ['1' => '2'];
var_dump(MyTest::func1($arg));
var_dump(MyTest::func2($arg));
var_dump(MyTest::func1($arg));
var_dump(MyTest::func2($arg));
这个输出应该是如何呢?大家不妨想一想。同样的static $result 结果会相同吗?
好了我们一起来看输出。
string(18) "a:1:{i:1;s:1:"2";}"
string(36) "2020-04-28 03:30:04 by func1 rand:45"
string(18) "a:1:{i:1;s:1:"2";}"
string(36) "2020-04-28 03:30:04 by func2 rand:40"
string(18) "a:1:{i:1;s:1:"2";}"
string(36) "2020-04-28 03:30:04 by func1 rand:45"
string(18) "a:1:{i:1;s:1:"2";}"
string(36) "2020-04-28 03:30:04 by func2 rand:40"
从上面可以看出,static $result的作用域还是自己的函数内。但是在类实例化的生命周期内,当函数返回结果时,其生命周期并没有释放。
那么我们可以利用这个特性来缓存函数操作的结果,而又不需要定义类的成员变量。
手机app https抓包步骤一揽
1. 工具准备
- WireShark
下载地址 开源工具,免费使用,支持WIN与OSX平台。
Wireshark(前称Ethereal)是一个网络封包分析软件。网络封包分析软件的功能是撷取网络封包,并尽可能显示出最为详细的网络封包资料。Wireshark使用WinPCAP作为接口,直接与网卡进行数据报文交换。 -
Fiddler
下载地址 开源工具,免费试用,支持WIN平台,OSX需要自行编译。
Fiddler是一个http协议调试代理工具,它能够记录并检查所有你的电脑和互联网之间的http通讯,设置断点,查看所有的“进出”Fiddler的数据(指cookie,html,js,css等文件)。 Fiddler 要比其他的网络调试器要更加简单,因为它不仅仅暴露http通讯还提供了一个用户友好的格式。
2. 如何使用
http的抓包使用Fiddler 或者其他代理软件很直接的就可以完成。但是https的抓包因为经过了加密,所以并不能直接看到抓包的内容,只能看到密文。那么如何使用http来进行抓包呢?


设置完成后,关闭软件重新打开。条件允许的情况下最好是重启电脑。
然后在手机里输入网址 http://pc.ip:8080 看是否出现如下截图。出现表示fiddler设置正确。

iOS只能下载该证书,要安装证书需要2个步骤。 安卓机暂不清楚。
1. 点设置-> 通用 -> 描述文件。 找到刚下载的“DO_NOT_TRUST_FiddlerRoot”点击安装
2. 点设置-> 通用 -> 关于本机 -> 证书信任设置。 点击完全信任。
然后打开浏览器,输入任意网址,能正常打开即表示完成。
3. 抓包结果
现在打开手机app,便可以看到fiddler里出现http和https的访问记录了。看下面的实例。

记一下RabblitMQ的安装和RPC的工作模式

1. 什么是Rabbitmq
Rabbitmq 是一款由erlang开发的基于AMQP协议的消息队列。官网地址https://www.rabbitmq.com 有六种工作模式。详细的教程在本文就不再赘述,可以直接去官网阅读。
2. 安装RabbitMq
安装环境CentOS7,步骤如下:
- 从源码安装
#下面是从源码master分支安装示例
git clone https://github.com/erlang/otp.git
cd otp
./otp_build autoconf
./configure
make
make install
安装完成之后,记得修改/etc/profile,设置erlang的路径
vim /etc/profile
export ERLANG="/usr/local/lib/erlang/bin/"
export PATH=/usr/local/php/bin:/usr/local/nginx/sbin:$PATH:$ERLANG
source /etc/profile
- 安装socat
sudo yum install socat
- 下载Rabbitmq-server安装包
wget https://github.com/rabbitmq/rabbitmq-server/releases/download/v3.8.2/rabbitmq-server-3.8.2-1.el7.noarch.rpm
rpm -ivh --nodeps rabbitmq-server-3.8.2-1.el7.noarch.rpm
一定要注意加上--nodeps, 不然会提示erlang依赖版本问题
- 启动服务端
rabbitmq-plugins list #查看组件是否正常显示
rabbitmq-plugins enable rabbitmq_management # 加载web管理组件
rabbitmq-server #启动服务端
在浏览器里输入http://localhost:15672,看后台是否出来,默认账号密码都是guest
3. RPC下如何工作
在上面的web页可以添加用户,由于默认的guest是不允许在外网访问,所以我们新增了yhw这个用户
从官网下载教程代码。到php目录下执行composer install。修改好配置之后我们启动rpc_server.php和rpc_client.php即可。
最后附上修改的测试代码。
rpc_server.php
<?php
require_once __DIR__ . '/vendor/autoload.php';
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;
$connection = new AMQPStreamConnection('10.211.55.3', 5672, 'yhw', 'yhw');
$channel = $connection->channel();
$channel->queue_declare('rpc_queue', false, false, false, false);
function fib($n)
{
if ($n == 0) {
return 0;
}
if ($n == 1) {
return 1;
}
return fib($n - 1) + fib($n - 2);
}
// 我增加的测试函数
function sayHello($n)
{
return sprintf(
"%s Hello gentleman, I am rabbitmq, working on RPC mode. You typed %d, the result is %d",
date('Y-m-d H:i:s'),
$n,
fib($n)
);
}
echo " [x] Awaiting RPC requests\n";
$callback = function ($req) {
$n = intval($req->body);
echo ' [.] fib(', $n, ")\n";
$msg = new AMQPMessage(
(string)sayHello($n),
array('correlation_id' => $req->get('correlation_id'))
);
$req->delivery_info['channel']->basic_publish(
$msg,
'',
$req->get('reply_to')
);
$req->delivery_info['channel']->basic_ack(
$req->delivery_info['delivery_tag']
);
};
$channel->basic_qos(null, 1, null);
$channel->basic_consume('rpc_queue', '', false, false, false, false, $callback);
while ($channel->is_consuming()) {
$channel->wait();
}
$channel->close();
$connection->close();
?>
rpc_client.php
<?php
require_once __DIR__ . '/vendor/autoload.php';
use PhpAmqpLib\Connection\AMQPStreamConnection;
use PhpAmqpLib\Message\AMQPMessage;
class FibonacciRpcClient
{
private $connection;
private $channel;
private $callback_queue;
private $response;
private $corr_id;
public function __construct()
{
$this->connection = new AMQPStreamConnection(
'10.211.55.3',
5672,
'yhw',
'yhw'
);
$this->channel = $this->connection->channel();
list($this->callback_queue, ,) = $this->channel->queue_declare(
"",
false,
false,
true,
false
);
$this->channel->basic_consume(
$this->callback_queue,
'',
false,
true,
false,
false,
array(
$this,
'onResponse'
)
);
}
public function onResponse($rep)
{
if ($rep->get('correlation_id') == $this->corr_id) {
$this->response = $rep->body;
}
}
public function call($n)
{
$this->response = null;
$this->corr_id = uniqid();
// 此处为调用参数,以及回复
$msg = new AMQPMessage(
(string) $n,
array(
'correlation_id' => $this->corr_id,
'reply_to' => $this->callback_queue
)
);
$this->channel->basic_publish($msg, '', 'rpc_queue');
while (!$this->response) {
$this->channel->wait();
}
return $this->response;
}
}
$fibonacci_rpc = new FibonacciRpcClient();
$n = (int)$argv[1];
while(1){
$response = $fibonacci_rpc->call($n);
echo ' [.] Got ', $response, "\n";
usleep(100);
}
?>
稍微改动下,客户端的代码,多起几个php脚本,看执行结果是否能回复正确。看下面截图结果完全正确。


聊一聊快排算法
曾经有一个人让我写下快排算法,我给他写出来了,然后就被Diss了,怎么申请这么多临时变量。来,各位看官一起看看本菜🐔的作业。各位看官也可以动手写一写。
快速排序的逻辑 — 来自百度百科
快速排序算法通过多次比较和交换来实现排序,其排序流程如下:
(1)首先设定一个分界值,通过该分界值将数组分成左右两部分。
(2)将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。此时,左边部分中各元素都小于或等于分界值,而右边部分中各元素都大于或等于分界值。
(3)然后,左边和右边的数据可以独立排序。对于左侧的数组数据,又可以取一个分界值,将该部分数据分成左右两部分,同样在左边放置较小值,右边放置较大值。右侧的数组数据也可以做类似处理。
(4)重复上述过程,可以看出,这是一个递归定义。通过递归将左侧部分排好序后,再递归排好右侧部分的顺序。当左、右两个部分各数据排序完成后,整个数组的排序也就完成了。
<?php
$input = [100, 23, 452, 234, 56223, 6234, 134, 51, 234];
$ret = quickSort($input);
printf("%s\n", json_encode($ret));
function quickSort($input)
{
$len = count($input);
if ($len <= 1) {
return $input;
}
$temp = $input[0];
$left = $right = [];
for ($i = 1; $i < $len; $i++) {
if ($temp >= $input[$i]) {
$left[] = $input[$i];
} else {
$right[] = $input[$i];
}
}
$left = quickSort($left);
$right = quickSort($right);
return array_merge($left, [$temp], $right);
}
基本上也是符合快速排序的标准,不过确实$left和$right这两个属于数组copy,会需要大量的内存空间的。但是既然说到利用了中间变量来存放数组,那么是否有别的方式呢?
答案肯定是有的了,既然不能用中间变量存数组,那么我可以使用交换的方式,因为反正都是比大小,我们通过一个临时变量将原数组的数据挖一个出来,然后再去比较,可以看下面的示例。
[100, 23, 452, 234, 56223, 6234, 134, 51, 234]
第一趟,我先取100出来,然后从右向左扫描,比他小的(51<100)放入他的坑,那么结果如下:
[---, 23, 452, 234, 56223, 6234, 134, 51, 234]
从左扫发现51,填入左边的坑。
[51, 23, 452, 234, 56223, 6234, 134, ---, 234]
然后从51的下个值往右扫,发现452>100,那么把452填入上面挖的坑
[51, 23, ---, 234, 56223, 6234, 134, 452, 234]
然后从452往左扫描,发现没有小于100的。那么100 就只能去填坑了。自此我们做了一个循环。
[51, 23, 100 234, 56223, 6234, 134, 452, 234]
那么下一个循环我们怎么开始呢?因为100的右边肯定是大于它的,100的左边肯定是小于它的。PS.这不是废话吗?上面的快排就是这样要求的。
我们就从51,到100 这三个元素之间继续排序。
[51,23,100]
[--,23,100]
[23,--,100]
[23,50,100]
最左边的完成了,那么我们来看看最右边的
[234,56223,6234, 134, 452, 234]
.... 各位看官自己实现下看看?
好了直接上代码
<?php
function quickSort(&$input, $low, $high)
{
if ($high <= $low) return;
$i = $low;
$j = $high;
$key = $input[$low];
while ($i < $j) {
while ($i < $j && $input[$j] >= $key) {
$j--;
}
if ($i < $j) {
$input[$i] = $input[$j];
$i++;
}
while ($i < $j && $input[$i] < $key) {
$i++;
}
if ($i < $j) {
$input[$j] = $input[$i];
$j--;
}
// printf("find i(%d),j(%d)\n", $i, $j);
}
$input[$i] = $key;
quickSort($input, $low, $i - 1);
quickSort($input, $i + 1, $high);
// return $input;
}
为了节省递归参数赋值,我们传了引用进去,更加节省内存空间。那么具体效果如何呢,我们来看看跟内置函数sort的对比结果。
排序个数:100000
内置排序耗时:27.215004 ms
手写排序耗时:104.742050 ms
结果抽查 1
排序个数:200000
内置排序耗时:61.784983 ms
手写排序耗时:241.871119 ms
结果抽查 1
排序个数:300000
内置排序耗时:87.085009 ms
手写排序耗时:448.505878 ms
结果抽查 1
排序个数:400000
内置排序耗时:120.175838 ms
手写排序耗时:618.201971 ms
结果抽查 1
排序个数:500000
内置排序耗时:149.783850 ms
手写排序耗时:796.765804 ms
结果抽查 1
排序个数:600000
内置排序耗时:182.029963 ms
手写排序耗时:1063.417196 ms
结果抽查 1
排序个数:700000
内置排序耗时:209.767103 ms
手写排序耗时:1333.762884 ms
结果抽查 1
排序个数:800000
内置排序耗时:241.870165 ms
手写排序耗时:1633.098841 ms
结果抽查 1
排序个数:900000
内置排序耗时:272.398949 ms
手写排序耗时:1975.782871 ms
结果抽查 1
排序个数:1000000
内置排序耗时:299.492121 ms
手写排序耗时:2303.519964 ms
结果抽查 1
可以看出虽然我们采用了传引用的方法,在大量的数据排序的时候,自己写的还是没有内置的厉害。基本上内置的算法只需要自己写的1/5的时间。
那么是否别的语言也是这样的呢?我们来看看golang吧。
排序个数: 100000
内置排序耗时: 16.072056ms
手写排序耗时: 6.95578ms
结果抽查: true
排序个数: 200000
内置排序耗时: 31.831613ms
手写排序耗时: 14.822814ms
结果抽查: true
排序个数: 300000
内置排序耗时: 46.872946ms
手写排序耗时: 24.190585ms
结果抽查: true
排序个数: 400000
内置排序耗时: 66.008501ms
手写排序耗时: 33.844555ms
结果抽查: true
排序个数: 500000
内置排序耗时: 77.824994ms
手写排序耗时: 43.05986ms
结果抽查: true
排序个数: 600000
内置排序耗时: 106.42095ms
手写排序耗时: 53.728681ms
结果抽查: true
排序个数: 700000
内置排序耗时: 117.377987ms
手写排序耗时: 65.855924ms
结果抽查: true
排序个数: 800000
内置排序耗时: 125.352682ms
手写排序耗时: 80.507389ms
结果抽查: true
排序个数: 900000
内置排序耗时: 140.563745ms
手写排序耗时: 92.550294ms
结果抽查: true
排序个数: 1000000
内置排序耗时: 162.15016ms
手写排序耗时: 100.330999ms
结果抽查: true
可以看到golang手写的居然比内置的快。100万个int排序,golang内置需要162ms,手写的居然只要100ms,php内置需要299ms,而手写居然需要2300多ms。
看来php的执行效率确实低,不过惊讶的是php内置函数排序居然只比golang慢一倍。
电脑配置
CPU:2.8 GHz 双核Intel Core i5
MEM:8 GB 1600 MHz DDR3
PHP version:7.4.2
Golang version :go1.13.7
最后贴上golang代码
package main
import (
"fmt"
"math/rand"
"sort"
"time"
)
func quickSortCustom(nums *[]int, start int, end int) {
// 起点和终点重合的时候,退出
if start >= end {
return
}
i, j := start, end
//左节点的坑挖出来,备用
mid := (*nums)[start]
for {
// 左节点位移到跟右节点重合时退出for循环
if i >= j {
break
}
// 右节点开始向左查找,直到比基准值小
for {
if j > i && (*nums)[j] >= mid {
j--
} else {
break
}
}
// 把右节点的坑,填入左节点。现在右节点j空出来了
if i < j {
(*nums)[i] = (*nums)[j]
i++
}
// 左节点开始向右寻找,直到找到比基准值大的
for {
if j > i && (*nums)[i] < mid {
i++
} else {
break
}
}
//把左节点的值填入右节点的坑,(上面的右节点j是空的没有变过)。此时左节点i空出来了
if i < j {
(*nums)[j] = (*nums)[i]
j--
}
}
// 把最开始挖出来的坑放到新坑里面
(*nums)[i] = mid
quickSortCustom(nums, start, i-1)
quickSortCustom(nums, i+1, end)
}
type IntSlice []int
func (s IntSlice) Less(i, j int) bool { return s[i] < s[j] }
func (s IntSlice) Len() int { return len(s) }
func (s IntSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func main() {
startTime := time.Now()
for i := 100000; i <= 1000000; i += 100000 {
numsIn := make(IntSlice, 0)
numsMy := make([]int, 0)
for n := 0; n < i; n++ {
//rand.Seed(int64(n))
x := rand.Intn(10000)
numsIn = append(numsIn, x)
numsMy = append(numsMy, x)
}
l := len(numsMy)
//fmt.Println("原始:", numsMy, "排序个数:", l)
fmt.Println("排序个数:", l)
// ---------------- 内置排序 开始----------------
startTime = time.Now()
sort.Ints(numsIn)
cost := time.Since(startTime)
fmt.Println("内置排序耗时:", cost)
// ---------------- 内置排序 结束----------------
// ---------------- 手写排序 开始----------------
startTime = time.Now()
quickSortCustom(&numsMy, 0, l-1)
cost = time.Since(startTime)
fmt.Println("手写排序耗时:", cost)
// ---------------- 手写排序 结束----------------
fmt.Println("结果抽查:", numsMy[l/2] == numsIn[l/2])
//fmt.Println("内置:", numsIn)
//fmt.Println("手写:", numsMy)
fmt.Println(" ")
}
//nums := []int{8081, 7887, 1847, 4059, 2081, 1318, 4425, 2540, 456, 3300}
//fmt.Println("原始的数据:", nums)
//quickSortCustom(&nums, 0, len(nums)-1)
//fmt.Println("自己排序后:", nums)
//
//nums2 := IntSlice{8081, 7887, 1847, 4059, 2081, 1318, 4425, 2540, 456, 3300}
////fmt.Println("排序前:",nums2)
//sort.Stable(nums2)
//fmt.Println("内置排序后:", nums2)
}
