ProtoThread 协程复习笔记

近一年没用 ProtoThread 了,忘得差不多了。这次搞硬件密码器重新学习了下,同时记录下来笔记,方便下次使用。

ProtoThread 是一个资源占用极少的多线程协程库,可以在51、avr等资源极其有限的硬件上面运行。

ProtoThread 有几种实现方式,这里只考虑存c switch 实现。这个实现其实就是用1byte存放当前执行到的位置,下次恢复这个线程的时候通过 switch 跳转到上次执行的位置继续执行。每个线程就只占用2byte存放当前位置(当前代码行号)即可。伪代码:

这种实现的几个特点:
• 纯c实现,无硬件依赖,方便移植
• 不能在函数内再次使用switch
• 资源占用极少,每线程仅占用2字节
• 支持阻塞操作,没有栈的切换
• 运行在单一c函数中,无法跨函数是用。弥补措施是被调用函数也写成单独的Protothread
• 线程切换时不保存局部变量,弥补措施是使用静态局部变量,造成单个函数不能多线程同时执行。另一个弥补措施是实现自己的 pt 结构,将需要保存的局部变量保存到 pt 结构里面。

附几个函数:
PT_INIT(pt) 初始化任务变量,只在初始化函数中执行一次就行
PT_BEGIN(pt) 启动任务处理,放在函数开始处
PT_END(pt) 结束任务,放在函数的最后
PT_WAIT_UNTIL(pt, condition) 等待某个条件(条件可以为时钟或其它变量,IO等)成立,否则直接退出本函数,下一次进入本 函数就直接跳到这个地方判断
PT_WAIT_WHILE(pt, cond) 和上面一个一样,只是条件取反了
PT_WAIT_THREAD(pt, thread) 等待一个子任务执行完成
PT_SPAWN(pt, child, thread) 新建一个子任务,并等待其执行完退出
PT_RESTART(pt) 重新启动某个任务执行
PT_EXIT(pt) 任务后面的部分不执行,直接退出重新执行
PT_YIELD(pt) 锁死任务
PT_YIELD_UNTIL(pt, cond) 锁死任务并在等待条件成立,恢复执行
在pt中一共定义四种线程状态,在任务函数退出到上一级函数时返回其状态
PT_WAITING 等待
PT_EXITED 退出
PT_ENDED 结束
PT_YIELDED 锁死

(下面的是定时器,该宏是 http://www.cnblogs.com/xiaowuyi/p/4319720.html 写的,用之前请在#include “pt.h” 的前面,前面啊!加上一句#define PT_USE_TIMER)

先说明一下,下面的定时不一定完全准确的,可能会有点点的误差,可能偏后。如果遇上了很烦的任务,有可能会使延时延后。但是正常情况下,直接用就好了。

如果要很精确的延时,请用delay语句或者计时器,但是,绝大多数情况下,绝大多数情况!绝大多数情况!请用下面的语句代替delay延时!这样才能把CPU让给别的任务使用。

PT_TIMER_DELAY(pt,延时毫秒数);
字面上的意思,不用多说了吧?最大值约为49.7天,估计没人会延时辣么久吧……

PT_TIMER_MICRODELAY(pt,延时微秒数);
字面上的意思,不用多说了吧?注意,最小精度与arduino的版本有关,与micros()有精度一致。

PT_TIMER_WAIT_TIMEOUT(pt,条件,毫秒数);
如果条件成立了,或者超时了,就继续运行,否则切换任务。

要用的话,请在#include “pt.h”前面加上一句 #define PT_USE_SEM

首先要创建一个信号量,这个一定是全局变量:
static struct pt_sem 信号量名;

接着请在setup()函数里面给它初始化:
PT_SEM_INIT(&信号量名,数量);
信号量名前面有个&,别忘了。数量就相当于停车场的总车位数。

然后要用啦。任务要停一辆车进去:
PT_SEM_WAIT(pt,&信号量名);
信号量名前面有个&,别忘了。一个语句只能停一辆车,土豪好多车就用多次。

任务要开一辆车出来:
PT_SEM_SIGNAL(pt,&信号量名);
信号量名前面有个&,别忘了。用一次出一辆。

参考:
• http://dunkels.com/adam/pt/
• Protothread机制文档 http://blog.csdn.net/tietao/article/details/8507455
• 一个“蝇量级” C 语言协程库 http://coolshell.cn/articles/10975.html
• ProtoThreads http://blog.csdn.net/utopiaprince/article/details/6041385

修正中文 gitbook pdf 书籍字体大小不一的问题

第二次用了,记录下来,下次就不用在查资料了。

gitbook 网站生成的 pdf 中文字体非常难看,字体大小不一。

解决办法是手工指定中文字体,指定 pdf 文字大小。

book.json 例子:

例子使用的 微软雅黑 字体,默认linux下没有这个字体,需要手工将字体拷贝到 /usr/share/fonts/truetype 目录。

可以通过 travis 自动生成 pdf 文件,.travis.yml 例子是:

这个例子会在每次 git tag 时自动生成 pdf 文件,并发布到 github 。需要将 github token 设在到 CI_USER_TOKEN 环境变量。

Docker 小抄

之前学习docker时整理的,现在有的命令忘记了。打印出来,方便随时查找。

web 版本格式没了,[PDF]Docker 小抄 有正确的格式。

镜像部分

docker pull ubuntu:14.04
获得 ubuntu 标签为 14.04的镜像,如果省略14.04则下载 latest 标签的镜像,ubuntu默认为最新的长期支持版本。相当于:docker pull registry.hub.docker.com/ubuntu:14.04 。

docker images
显示本地已有镜像。镜像id表示了唯一的镜像,有些的镜像可能存在多个 TAG ,但是id一致表示实际是一个镜像。

docker commit -m “提交说明” -a “提交用户” 容器id 用户名/仓库名:TAG
提交镜像,只是提交到本地,并没有上传到 docker hub 。

sudo cat ubuntu-14.04-x86_64-minimal.tar.gz |docker import – ubuntu:14.04
从本地导入镜像。源可以是 openvz 模板,地址:http://openvz.org/Download/templates/precreated

docker push ouruser/sinatra
上传镜像到 docker hub

docker save -o ubuntu14.04.tar ubuntu:14.04
导出镜像到文件

docker load –input ubuntu14.04.tar 或 docker load < ubuntu14.04.tar 导入镜像,这将导入镜像及相关元数据(包括标签等信息)。docker rmi aaaaa/bbbb 删除镜像,注意 docker rm 是删除容器。无法删除使用中的镜像。docker rmi $(docker images -q -f "dangling=true") 移除未打标签的中间镜像。容器docker run ubuntu:14.04 /bin/echo 'hello word' 使用 ubuntu:14.04 镜像启动一个新的容器并执行 /bin/echo 'hello word' 命令。 默认容器停止后还会继续保留,可以通过 start 命令重新启动,可以通过 --rm 参数实现容器停止自动删除的功能。--restart=always选项用来保证Docker守护进程在容器出错或者重启后自动启动容器。docker run -t -i ubuntu:14.04 /bin/bash 启动容器并执行 /bin/bash 命令。 -t 是分配伪终端并绑定到容器的标准输入上面。-i 则是保持标准输入打开状态。docker start 容器id 启动已停止的容器,将继续执行创建时指定的命令。通过增加 -i 参数可以进入交互模式。docker stop 容器id 停止容器。并不会丢失对容器所做的修改!!通过 start 重新启动容器后可以发现之前的修改都保存着!docker restart 容器id 重启容器。同样不会丢失所做的修改!docker run -d 镜像名称 命令 后台启动容器,就是不要将标准输出在当前终端下。注意:容器是否会长期运行是和指定的命令有关,而和 -d 参数没有关系。docker log 容器id 查询容器日志,就是标准输出记录。docker attach 容器id 附加到指定容器,个人理解就是将容器标准输出连接到当前终端,将当前中断的标准输入连接到容器。注意,多个附加操作是显示一样的内容。实际操作容器时建议通过新命令 docker exec 大体。退出,一定不要用ctrl+c,那样就是让docker容器停止了。要用如下快捷键:先按,ctrl+p再按,ctrl+q 。使用CTRL-c或CTRL-d退出容器,将向容器发送SIGKILL,导致容器停止。使用 CTRL-\退出容器时,将会输出docker客户端的堆栈信息。 docker run -t -i → can be detached with ^P^Q and reattached with docker attach docker run -i → cannot be detached with ^P^Q; will disrupt stdin docker run → cannot be detached with ^P^Q; can SIGKILL client; can reattach with docker attachdocker exec 容器id 命令 在指定的容器内执行命令。执行/bin/bash命令需要通过 -ti 选项打开伪终端及标准输入。已经关闭的容器无法执行命令。nsenter 老的在指定容器执行命令的方案。docker export 容器id > ubuntu.tar
导出容器快照到文件。

cat ubuntu.tar | sudo docker import -test/ubuntu:v1.0
导入容器快照为镜像。容器快照不保存历史纪录及元数据,体积小;load 导入的镜像储存包含历史纪录及元数据(标签等),体积比较大。

docker import http://www.aaa.com/bbb.tgz
通过网络导入容器快照。

docker rm 容器id
删除容器。-v 参数表示同时删除数据卷,否则即使容器被删除数据卷也会永久保留。

docker rm -v $(docker ps -a -q)
删除所有未运行的容器,-v 参数使得会同时删除数据卷。

docker inspect 容器id
审计容器,显示容器的详细信息。

数据管理

sudo docker run -ti -v /user_data ubuntu /bin/bash
启动一个容器,并且挂载了一个 /user_data 的数据卷。数据卷绕过了 UFS ,对数据卷的更新不会影响镜像,默认会一直存在,不会随着容器的删除而删除。实际实现是见一个目录挂载到了容器指定的目录,这里是 /user_data 。未指定数据卷源时源是在主机的 /var/lib/docker/volumes 目录下,docker inspect 容器id 输出的容器详细信息里面有数据卷的详细信息。
“Volumes”: {
“/udata”: “/var/lib/docker/volumes/1328a35ef12eb9cfa97210f567a13d70ccf157856c2d79f922a858ef4da1fad8/_data”
},
“VolumesRW”: {
“/udata”: true
},
可以看到 /user_data 数据卷实际是主机的/var/lib/docker/volumes/1328a35ef12eb9cfa97210f567a13d70ccf157856c2d79f922a858ef4da1fad8/_data目录,对这个目录的修改容器的/user_data 会实时的反应出来。
不指定数据卷源时会自动创建一个新的目录作为数据卷的源,默认每个容器的数据卷不相关。

docker run -ti -v /user_data:~/user_data:ro ubuntu /bin/bash
使用主机的 ~/user_data 目录作为源为容器创建一个路径是 /user_data 的只读数据卷,ro 表示只读。
也可以挂载单个文件作为数据卷,但是 vi 等文本编辑工具会造成文件 inode 改变,会使得docker报告错误。

docker rm -v 容器id
删除容器并删除数据卷。资料显示目前 docker 默认删除容器时不会删除容器卷,同时不存在删除无主地数据卷的功能,是个大坑…

docker run -d -v /dbdata –name dbdata training/postgres echo Data-only container for postgres
创建一个名叫 dbdata 的容器,它有一个数据卷 /dbdata ,它的启动命令是 echo … 表示启动就输出一句话就结束容器(数据卷容器不需要启动即可使用)。

docker run -d –volumes-from dbdata -ti –name db1 ubuntu bash
启动一个新容器,它挂载数据卷容器 dbdata 。也就是它将完全以和 dbdata 容器 相同的容器路径挂载相同的主机目录。也就是本容器 /dbdata 和 容器 dbdata 下的 /dbdata 都是挂载的相同的主机目录。可以通过多个 –volumes-from 参数来从多个数据卷容器挂载数据,一个数据卷容器可以同时被多个容器挂载。
同样删除容器及数据卷容器默认不会实际删除数据,必须在删除最后一个容器时通过 docker rm -v 增加 -v 参数来实际删除数据。

docker run –volumes-from dbdata -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar /dbdata
备份数据卷容器的数据。命令实际执行的操作:容器启动后,使用了 tar 命令来将 dbdata 卷备份为容器中 /backup/backup.tar 文件,也就是主机当前目录下的名为 backup.tar 的文件。

docker run -v /dbdata –name dbdata2 ubuntu /bin/bash
创建一个空数据卷容器
docker run –volumes-from dbdata2 -v $(pwd):/backup busybox tar xvf
/backup/backup.tar
将当前目录的backup.tar 内的数据恢复到 数据卷容器 dbdata2 。命令实际执行的操作:创建一个容器,挂载 dbdata2 容器卷中的数据卷,并使用 untar 解压备份文件到挂载的容器卷中。
docker run –volumes-from dbdata2 busybox /bin/ls /dbdata
检查恢复是否成功。

网络功能

docker run -d -P training/webapp python app.py
启动一个容器, 使用 -P 参数时,docker 将会随机的映射一个 49000-49900 的端口到容器开放的网络端口。

$ sudo docker ps -l
]CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
532192b4328c training/webapp “python app.py” About a minute ago Up About a minute 0.0.0.0:32768->5000/tcp suspicious_lovelace
可以参看映射的端口号。

$ sudo docker port 5321
5000/tcp -> 0.0.0.0:32768
可以查看指定容器的端口映射。

docker run -d -p 5000:5000 training/webapp python app.py
小写的 p 映射制定的端口。可选的格式是:ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort 。

docker run -d -p 5000:5000/udp training/webapp python app.py
增加 /udp 标记表示映射 udp 端口。

$ sudo iptables -L
Chain INPUT (policy ACCEPT)
target prot opt source destination

Chain FORWARD (policy ACCEPT)
target prot opt source destination
DOCKER all — anywhere anywhere
ACCEPT all — anywhere anywhere ctstate RELATED,ESTABLISHED
ACCEPT all — anywhere anywhere
ACCEPT all — anywhere anywhere

Chain OUTPUT (policy ACCEPT)
target prot opt source destination

Chain DOCKER (1 references)
target prot opt source destination
ACCEPT tcp — anywhere 172.17.0.24 tcp dpt:5000
可以看到端口映射是通过 iptables 实现的。

docker run -d –name db -e AAA=A1 -e bbb=b1 training/postgres
docker run -d -P –name web –link db:db training/webapp python app.py
建立一个名为 db 的容器,然后创建一个名为 web 的容器,并连接到 db 容器。注意:即使不执行连接操作默认所有容器也都在同一个虚拟局域网里面,是可以互相通信的,只不过不知道docker为容器分配的ip地址而已。可以通过 docker 服务的 –icc=false 参数禁止默认容器之间的互联,同时 –iptables=true 允许 docker 修改 iptables 时,link 操作会自动添加 iptables 规则允许相互访问开放的端口。

$docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
3113cef6d6b0 training/webapp “python app.py” 2 minutes ago Up About a minute 0.0.0.0:32770->5000/tcp web
b2fbf68028c3 training/postgres “su postgres -c ‘/us 4 minutes ago Up 4 minutes 5432/tcp db
文档上面说 ps 命令在NAMES字段会显示连接关系,但是实测没有…

root@vps11:~# docker exec -ti web bash
root@5a48dc56a38e:/opt/webapp# env
HOSTNAME=5a48dc56a38e
DB_NAME=/web/db
DB_PORT_5432_TCP_ADDR=172.17.0.5
DB_PORT=tcp://172.17.0.5:5432
DB_PORT_5432_TCP=tcp://172.17.0.5:5432
LS_COLORS=
DB_ENV_bbb=b1
DB_ENV_AAA=A1
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PWD=/opt/webapp
DB_PORT_5432_TCP_PORT=5432
SHLVL=1
HOME=/root
DB_PORT_5432_TCP_PROTO=tcp
LESSOPEN=| /usr/bin/lesspipe %s
DB_ENV_PG_VERSION=9.3
LESSCLOSE=/usr/bin/lesspipe %s %s
_=/usr/bin/env
root@5a48dc56a38e:/opt/webapp# cat /etc/hosts
172.17.0.6 5a48dc56a38e
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.5 db 2f115673d7cd
可以看到 docker 通过环境变量及hosts文件为web容器公开了连接信息(并没有为 db 容器公开web 的信息),DB_的前缀是连接别名。同时 link 还会为web容器公开了db容器 -e 设置的环境变量(环境变量前面增加大写的被连接容器名字,小写的环境变量也会同样小写转发过去。) inspect web 能看到 link 了 db,但是看不到 db 的环境变量,只能看到 db -e 设置的自身的环境变量。

Dockerfile

# This dockerfile uses the ubuntu image
# VERSION 2 – EDITION 1
# Author: docker_user
# Command format: Instruction [arguments / command] ..

# 基础镜像
FROM ubuntu

# Maintainer: 维护者信息
MAINTAINER docker_user docker_user@email.com

# Commands to update the image
RUN echo “deb http://archive.ubuntu.com/ubuntu/ raring main universe” >> /etc/apt/sources.list
RUN apt-get update && apt-get install -y nginx
RUN echo “\ndaemon off;” >> /etc/nginx/nginx.conf

# 启动容器时运行的命令,如果指定多个,只执行最后一个。如果 run 时指定了命令会覆盖当前设置。
CMD /usr/sbin/nginx

FROM
格式为 FROM 或FROM :
第一条指令必须为 FROM 指令。并且,如果在同一个Dockerfile中创建多个镜像时,可以使用多个 FROM 指令(每个镜像一次)。
MAINTAINER
格式为 MAINTAINER ,指定维护者信息。
RUN
格式为 RUN  或 RUN [“executable”, “param1”, “param2”]。
前者将在 shell 终端中运行命令,即 /bin/sh -c;后者则使用 exec 执行。指定使用其它终端可以通过第二种方式实现,例如 RUN [“/bin/bash”, “-c”, “echo hello”]。
每条 RUN 指令将在当前镜像基础上执行指定命令,并提交为新的镜像。当命令较长时可以使用\ 来换行。
CMD
支持三种格式
• CMD [“executable”,”param1″,”param2″] 使用 exec 执行,推荐方式;
• CMD command param1 param2 在 /bin/sh 中执行,提供给需要交互的应用;
• CMD [“param1″,”param2”] 提供给 ENTRYPOINT 的默认参数;
指定启动容器时执行的命令,每个 Dockerfile 只能有一条 CMD 命令。如果指定了多条命令,只有最后一条会被执行。
如果用户启动容器时候指定了运行的命令,则会覆盖掉 CMD 指定的命令。
EXPOSE
格式为 EXPOSE […]。
告诉 Docker 服务端容器暴露的端口号,供互联系统使用。在启动容器时需要通过 -P,Docker 主机会自动分配一个端口转发到指定的端口。
ENV
格式为 ENV 。 指定一个环境变量,会被后续 RUN 指令使用,并在容器运行时保持。
例如
ENV PG_MAJOR 9.3
ENV PG_VERSION 9.3.4
RUN curl -SL http://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgress && …
ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH

ADD
格式为 ADD
该命令将复制指定的  到容器中的 。 其中  可以是Dockerfile所在目录的一个相对路径;也可以是一个 URL;还可以是一个 tar 文件(自动解压为目录)。
COPY
格式为 COPY
复制本地主机的 (为 Dockerfile 所在目录的相对路径)到容器中的 
当使用本地目录为源目录时,推荐使用 COPY。
ENTRYPOINT
两种格式:
• ENTRYPOINT [“executable”, “param1”, “param2”]
• ENTRYPOINT command param1 param2(shell中执行)。
配置容器启动后执行的命令,并且不可被 docker run 提供的参数覆盖。
每个 Dockerfile 中只能有一个 ENTRYPOINT,当指定多个时,只有最后一个起效。
VOLUME
格式为 VOLUME [“/data”]。
创建一个可以从本地主机或其他容器挂载的挂载点,一般用来存放数据库和需要保持的数据等。相当于 docker -v 参数,为了可移植性考虑,不允许指定主机路径,只能指定容器内的路径。
USER
格式为 USER daemon。
指定运行容器时的用户名或 UID,后续的 RUN 也会使用指定用户。
当服务不需要管理员权限时,可以通过该命令指定运行用户。并且可以在之前创建所需要的用户,例如:RUN groupadd -r postgres && useradd -r -g postgres postgres。要临时获取管理员权限可以使用 gosu,而不推荐 sudo。
WORKDIR
格式为 WORKDIR /path/to/workdir。
为后续的 RUN、CMD、ENTRYPOINT 指令配置工作目录。
可以使用多个 WORKDIR 指令,后续命令如果参数是相对路径,则会基于之前命令指定的路径。例如
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
则最终路径为 /a/b/c。
ONBUILD
格式为 ONBUILD [INSTRUCTION]。
配置当所创建的镜像作为其它新创建镜像的基础镜像时,所执行的操作指令。
例如,Dockerfile 使用如下的内容创建了镜像 image-A。
[…]
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build –dir /app/src
[…]
如果基于 image-A 创建新的镜像时,新的Dockerfile中使用 FROM image-A指定基础镜像时,会自动执行 ONBUILD 指令内容,等价于在后面添加了两条指令。
FROM image-A
#Automatically run the following
ADD . /app/src
RUN /usr/local/bin/python-build –dir /app/src
使用 ONBUILD 指令的镜像,推荐在标签中注明,例如 ruby:1.9-onbuild。

来自

创建镜像

编写完成 Dockerfile 之后,可以通过 docker build 命令来创建镜像。

基本的格式为 docker build [选项] 路径,该命令将读取指定路径下(包括子目录)的 Dockerfile,并将该路径下所有内容发送给 Docker 服务端,由服务端来创建镜像。因此一般建议放置 Dockerfile 的目录为空目录。也可以通过 .dockerignore 文件(每一行添加一条匹配模式)来让 Docker 忽略路径下的目录和文件。

要指定镜像的标签信息,可以通过 -t 选项,例如

$ sudo docker build -t myrepo/myapp /tmp/test1/

golang recover() 的一个坑,记录一下。

今天碰到了一个 recover() 的一个坑。

有一个操作预期可能出现 panic 而且不需要关心它是否panic.就建立了一个匿名函数将操作包裹起来并 追加了 defer recover。 原来都是 defer func(){_=recover()}(),这次省掉了匿名函数,直接 defer recover() 。

结果如上,直接挂了。recover 无效…

查看了 recover 的文档:recover 只能放到 defer 函数里面,不能放到子函数。实测直接 defer recover() 也不行。recover 是内置函数,源码需要到go的实现里面查,懒得去查了。

go 的很多设计感觉都是黑魔法,就像这里,如果 panic 是一个协程本地变量保存的,那么在什么地方调用 recover 都行。但是看情况不是,恐怕和函数调用搅到一起了。

v:=dict[key]、 v,ok:=dict[key] 这个设计也是使用的黑魔法,感觉是一开始只设计了第一种实现,但是由于需要获得是否存在key,就另加了第二个实现,很生硬的感觉。

chan 实现也比较奇怪,连接之类的关闭都是 conn.Close() 实现关闭,chan 却是 close(chan),设计并不统一,很别扭。

完整的golang 多协程+信道 任务处理示例

有几个地方需要注意:for i + 协程时如果协程使用 i ,那么需要增加 i:=i 来防止多协程冲突;实际执行任务时需要用一个函数包起来,防止单个任务panic造成整个程序崩溃。

socket.io-python-emitter 发送消息 socket.io-redis 无法收到的问题。

之前将服务迁移到 docker ,结果发现 socket.io-python-emitter 发送的消息全部发送不出去。经检查发现是 socket.io-redis 0.2.0 做了性能优化,与老版本不兼容了,无奈查源码自己打了个补丁(现已合并至主干)。

最开始是发现网站发起的推送延迟很厉害,而且推送 ping 日志显示丢包达到了80%以上。由于所有推送操作同时使用了极光推送及自建的 socket.io 推送。一开始没想到两套推送方案全部挂了,以为是测试设备出了问题,并且测试时设备日志记录了一次极光推送被强制关闭的log。同时发现极光推送控制台日志显示设备不在线(后来发现近期极光推送非常不稳定,这种延迟是常规状态了,由于原来自建推送可用, 所以没有注意)。

但是经检查发现设备没有问题,而且设备状态上报部分同样使用的 socket.io,也很正常。所以又测试了一下python向网页端推送,发现还是收不到推送,确认python发出的推送出现故障了。

无奈进 redis 数据库查看推送是否进入了数据库,结果发现python发出的推送消息的确进入了redis数据库,但是和socket.io本身发出消息的频道名称不一致。找了一下,没有当时的记录了,大体是执行的redis-cli命令,通过 PSUBSCRIBE * 订阅所有频道。

于是觉得可能是频道名字的问题,所以仿着 socket.io 消息的格式通过 PUBLISH 频道名称 消息 发了个消息,无效,并且命令返回了1,只有手工PSUBSCRIBE * 的订阅,也就是 socket.io 过呢本没有接收消息。觉得是 socket.io 有问题,岁查看代码,发现socket.io-redis 会根据房间名订阅消息,并不会订阅 #emitter 频道的消息,就很纳闷之前的系统为什么能工作。检查了下修订历史,发现是 0.1.4 -> 0.2.0 为了性能只订阅相关房间的消息,看了一下发现 python 的代码是2年前了,和当前的实现不兼容…

无奈只能自己修改了,中间又发现新版本同时还修改了包格式,不过都问题不大。

这次修bug理清了大部分 socket.io-redis 的流程。

socket.io-redis 接收消息是通过订阅 redis 上面相关的频道来实现的,会检查 uid 看看是不是本机发出的,如果不是就转到 socket.io 处理。不过这里有个问题,uid 是随机生成的6位字符串,也就是有可能碰到相同的uid,socket.io-redis 会抛弃相同uid的消息造成相同uid的 socket.io 之间无法通行。

发送消息是通过向 redis 发送消息,然后其他 socket.io-redis 订阅并接收实现的。广播消息会直接发送到 prefix + ‘#’ + packet.nsp + ‘#’ ;发送到指定房间的消息会对涉及到的房间都发送一条 prefix + ‘#’ + packet.nsp + ‘#’ + room + ‘#’ 频道的消息。

删除 adb emulator-5554 device 的办法

adb devices 多了一个 emulator-5554 设备,所有adb 的操作都需要手动指定设备名称,实在太麻烦了。

多次检查确定所有模拟器都关闭了,检查 5554 端口,确认并没有程序使用这个端口,最后终于找到了原因,adb 会搜索 5555 – 5683 端口,发现有打开的端口就会认为存在模拟器。我的5555端口是打开的,所以adb增加了一个迷你其,但是名字还是5554就比较坑人了。关闭5555端口后重启adb,emulator-5554 设备就消失了。

http://stackoverflow.com/questions/13017269/adb-devices-showing-dummy-device

SourceTree 多密钥 keys

默认配置下SourceTree 使用 putty 做 git ssh 登陆,每次重启后只会保留一个密钥…次次都需要手动添加其他的密钥,很麻烦。
从网上查了一下,SourceTree 支持多密钥需要修改为 openssh ,并且修改多个配置文件。照着做了,结果还是不行。

最后发现并不需要麻烦的手写配置文件,直接修改使用 openssh ,然后直接点击 工具 => 添加ssh密钥 将多个密钥都加进来,实际使用时将自动选择密钥。不需要麻烦的修改配置文件。

crontab 执行(超时)中断错误

今天碰到了 crontab 执行命令半截中断的情况。查看日志中断的位置并没有什么特殊操作,查看内存,并没有内存不足的情况。记不清楚来源的资料说过太多的标注输出也有可能造成 crontab 命令中断,遂将标准输出完全重定向到log文件,经测试没有再次出现中断现象。问题解决。


*/5 * * * * root /usr/bin/命令 > /log.log 2>&1