这段时间做了一个有趣的东西,就是利用长短信检测用户手机状态,原理是发送半条短信,一般来说:移动比较新点的手机都收不到,很老的手机会收到;联通手机几乎都收不到;电信手机都能收到。但这些短信都有一个共同的特点,手机不会提示收到短信,且能返回短信状态报告。后来测试中发现一些问题,这里总结一下。
移动联通长短信测试乱码,解决办法:在MsgContent前加入7个字节的TP_udhi头
    7位的协议头格式:06 08 04 XX XX MM NN
    byte 1 : 06, 表示剩余协议头的长度
    byte 2 : 08, 这个值在GSM 03.40规范9.2.3.24.1中规定,表示随后的这批超长短信的标识位长度为2(格式中的XX值)。
    byte 3 : 04, 这个值表示剩下短信标识的长度
    byte 4-5 : XX XX,这批短信的唯一标志,事实上,SME(手机或者SP)把消息合并完之后,就重新记录,所以这个标志是否唯一并不是很重要。
    byte 6 : MM, 这批短信的数量。如果一个超长短信总共5条,这里的值就是5。
    byte 7 : NN, 这批短信的数量。如果当前短信是这批短信中的第一条的值是1,第二条的值是2。
例如:06 08 04 00 39 02 01
电信长短信测试乱码,解决办法:在MsgContent前加入6个字节的TP_udhi头
    6位协议头格式:05 00 03 XX MM NN
    byte 1 : 05, 表示剩余协议头的长度
    byte 2 : 00, 这个值在GSM 03.40规范9.2.3.24.1中规定,表示随后的这批超长短信的标识位长度为1(格式中的XX值)。
    byte 3 : 03, 这个值表示剩下短信标识的长度
    byte 4 : XX,这批短信的唯一标志(被拆分的多条短信,此值必需一致),事实上,SME(手机或者SP)把消息合并完之后,就重新记录,所以这个标志是否唯一并不是很重要。
    byte 5 : MM, 这批短信的数量。如果一个超长短信总共5条,这里的值就是5。
    byte 6 : NN, 这批短信的数量。如果当前短信是这批短信中的第一条的值是1,第二条的值是2。
例如:05 00 03 39 02 01

      有时候可能遇到一个任务是需要延时处理,当前段发出一个请求,告诉后端延迟多长时间处理。这两天刚好遇到这个问题,解决方案:
            1、接收到请求后放入内存,到点执行,但是这要求任务量不大,延迟较短,否则可能导致消耗大量内存
            2、每秒刷数据库,这简直是胡扯。
            3、建立内存队列,当收到任务时从数据库取任务并更新队列。
      说实话没有一点技术含量,写这篇博客也只是想写一下实现过程。
            1、当接到有任务信号时,从数据库中取出当前不在内存中的N条数据。N为内存窗口大小。
            2、如果当前队列未满,直接放入。
            3、如果当前队列已满,检查当前取出的任务是否比队列中的任务更紧急,是则替换。
            4、返回一个等待下一次检查的时间。即下一任务的最短执行时间。
      懒得说了,来程序,这网速慢的,也该睡觉了。

# 任务信号处理
_DATA_EVENT_ = threading.Event()
_DATA_EVENT_.set()
def dataSignalHandler(signum, frame):
    _DATA_EVENT_.set()
signal.signal(signal.SIGUSR1, dataSignalHandler)
# 程序终止信号处理
_END_EVENT_ = threading.Event()
_END_EVENT_.clear()
def endSignalHandler(signum, frame):
    _END_EVENT_.set()
    _DATA_EVENT_.set()
signal.signal(signal.SIGTERM, endSignalHandler)
############################################################
_SLOCK_ = threading.Lock() #更新任务队列锁
class Server:
    def __init__(self, num, cktime):
        self.max = (datetime.datetime(2000,1,1), '') #队列中最大时间、对应流水号
        self.num = num
        self.sendQueue = {} #待发送短信队列
    def run(self):
        '''主控线程'''
        svc = [
            threading.Thread(target = self.doReceive),
            threading.Thread(target = self.doTask),
        ]
        for i in svc:
            i.start()
        while not _END_EVENT_.isSet():
            _END_EVENT_.wait(60)
        for i in svc:
            i.join()
    def doReceive(self):
        '''可变时更新内存队列'''
        minsec = 12
        while not _END_EVENT_.isSet():
            _DATA_EVENT_.wait(minsec) #多线程?
            minsec = self.updateCkQueue()
            if minsec > 0: #如果没有需要及时处理信息
                _DATA_EVENT_.clear()
    def updateCkQueue(self):
        '''更新队列,返回下一个记录最短时间'''
        ids = self.sendQueue.keys()
        #从数据库获取任务
        #多取一条是为了获得下次查询数据库的最短时间
        rows = cs.getRecords(ids, self.num+1)
        if not rows:
            return 12
        minsec = '' #最短多少秒取
        for row in rows:
            if len(self.sendQueue) < self.num: #队列未满,直接入队
                self.sqin(row['m_id'], row)
                if row['m_dotime'] > self.max[0]:
                    self.modifyMax((row['m_dotime'], row['m_id']))
                continue
            if row['m_dotime'] < self.max[0]:#当前记录更急
                d = self.sqout(self.max[1])
                self.sqin(row['m_id'], row) #入库
                self.modifyMax((row['m_dotime'], row['m_id']))
                self.modifyMax('')
                row = d #出狱后的记录
            mt = row['m_dotime'] - time.time()
            minsec = minsec == '' and mt #初始化minsec
            minsec = min(mt, minsec)
        return minsec !='' and minsec or 0
    def doTask(self):
        '''检查内存队列'''
        while not _END_EVENT_.isSet():
            for i in self.sendQueue.keys():
                d = self.sendQueue[i]
                if datetime.datetime.now() > d['m_dotime']: #该处理了
                    #dosomething
            _END_EVENT_.wait(1) #等待1秒继续检查
    def modifyMax(self, data = ''):
        '''修改当前最大时间和序列号'''
        if data:
            self.max = data
            return data
        for i in self.sendQueue.keys(): #重新获取最大时间
            d = self.sendQueue[i]
            self.max = d['m_dotime'] > self.max[0] and (d['m_dotime'], i) or self.max
    @ThreadUtil.lockingCall(_SLOCK_)
    def sqin(self, id, data):
        self.sendQueue[id] = data
    @ThreadUtil.lockingCall(_SLOCK_)
    def sqout(self, id):
        return self.sendQueue.pop(id)

      在分享一个放松进程信号的小function:

def sendSignal(app, sig):
    try:
        #存放进程的文件夹
        f = open('%s/var/run/%s.pid' % (_BASIC_PATH_, app), 'r')
        pid = f.read()
        f.close()
        os.kill(int(pid), getattr(signal, sig))
        return 1
    except:
        return 0

      肯定还有更好性能更优的方案,只是我还没有想到,有建议的麻烦留个。thx
【后记】写日志很有必要,刚刚整理过程的时候又发现一个bug,之前取数据库中数据,是根据当前最大时间为根本,结果发现,bug啊 啊,看来只能用not in了

13 / 04 / 2011 buling

        没想到整天和他们开玩笑要换工作,我真的做了。天天说的最多的话题就是工资太低了,都说什么时候换,我怕赶不上他们的步伐,就找了个晚上更新了简历,周末给迅雷投了一下,跟梦天堂投了一下,结果今天真收到电话了。星哥,对不起,我不该撒谎说去给同学拿身份证。
        这公司真好,我几乎从来没想过要离开,但最近变了。hejh(高中同学)突然病了,每周要打一针,这针值1600,过了两天,她突然说她妈妈也病了,天哪,为什么这么晦气,老天怎么这样。再想想自己家里的两老,为我耗尽了他们的一生,头发白了,腰也弯了,厚厚的衣服把自己包裹的够老人。看着真的让人心痛了,以前每年都回去,但去年没有,因为我真的没有钱。我要改变,一定要保证每个月能给他们打点钱,每年能给他们做体检,我不想当大事发生的时候我无能为力的堕落。
        今天面试很顺利,没有做笔试,虽然我也准备了心态,没有准备知识。一面、两面、人力面。二面gg竟然又是个四川人,为什么用又呢,这里我想说两句,发现自己见着四川人很亲,之前托管党员关系时那大姐是四川的,结果她用家乡话给我交流,很方便就办完了,后来就是ht,真的很谢谢,还有那次叫个快递,也是四川人,真好。哈哈,愿天下四川一家亲。
       这是第二次正规面试,我不是很有信心,所以想多尝试两次,从中感觉一下自己还欠缺什么,都问了一些比较常见的,倒是我讲了很多自己做的。我理解到的是公司也想尝试转php,目前主要做uchome等开发。最开始从php转到后台开发,是因为php太无聊了,应用开发果然有挑战信,得时时注意服务状态,能扛得住恶劣的环境。
        不写了,两天写了三篇,这样有点唠叨。祈祷hejh早日恢复健康,希望你妈妈也快快好起来。

无标签信息 1 条
12 / 04 / 2011 buling

        虽然现在有点晚了,但还是想写完,今天完成了一个伟大的事情,那就是完完整整的装了一次lamp,N久之前我就开始期望要自己学一次,但原来在业开实在是没时间,这两天稍微有点空闲,刚好来学习一下,星哥给了很多技术支持,特别感谢。
        以前从没装过软件,第一次接触,使用yum,原来这个东西如此之方便。我是安装一个虚拟主机,装的是scientific linux6.0,但因为源不好,安装的时候老卡住,星哥提示要删除缓存文件,再重试,果然:

rm -rf /var/cache/yum/x86_64/6.0/sl/*

       接下来就开始安装软件:很傻瓜的安装:

yum install httpd #安装apache
/sbin/chkconfig httpd on #设置apache服务器httpd服务开机启动
/etc/init.d/httpd start #开启apache
/etc/init.d/httpd status #查看apache状态
yum install mysql #客服端,即连mysql端
yum install mysql-server #服务端
/sbin/chkconfig --add mysqld #把mysql添加到服务清单中
/sbin/chkconfig mysqld on #设置mysql开机启动
/etc/init.d/mysqld start #开启mysql服务 /sbin/service mysqld status
yum install php #
yum install php-mysql #安装php连mysql组件
mysqladmin -u root password '123456' 设置mysql数据库账号
mysql -u root -p
mysql>show databases;
mysql>use test;
mysql>show tables;
mysql> GRANT ALL PRIVILEGES ON my_db.* TO 'user'@'localhost' IDENTIFIED BY 'password';

有的情况可能出现,安装好php后发现运行.php文件显示源代码,修改/etc/httpd/conf/http.conf文件,检查是否有如下配置,没有,添加重启apache即可:

AddType application/x-httpd-php .php

       好了,这就是伟大的amp,安装完了。下面来添加一个www用户,他主要就是放置站点文件的,

useradd www
passwd www

        打开/etc/httpd/conf/httpd.conf文件,在末尾添加如下代码,这里我就不解释,很简单,意会。


    ServerAdmin webmaster@dummy-host.example.com
    DocumentRoot /home/www/html/www.fenchu.com/public_html/
    ServerName fenchu.com
    ServerAlias fenchu.com www.fenchu.com
    ErrorLog logs/dummy-www.fenchu.com-error_log
    CustomLog logs/dummy-www.fenchu.com-access_log common
    Include conf/public/comon.conf
    SetEnv Root_Path /home/www/html/www.fenchu.com/
    SetEnv Base_Url http://www.fenchu.com/


    ServerAdmin webmaster@dummy-host.example.com
    DocumentRoot /home/www/html/bbs.fenchu.com/public_html/
    ServerName bbs.fenchu.com
    ServerAlias bbs.fenchu.com
    ErrorLog logs/dummy-bbs.fenchu.com-error_log
    CustomLog logs/dummy-bbs.fenchu.com-access_log common
    SetEnv Root_Path /home/www/html/bbs.fenchu.com/
    SetEnv Base_Url http://bbs.fenchu.com/

         结果发现一个问题,请求失败,结果星哥出马,指出selinux问题,虽然我现在还没搞懂这是个啥东西,不过知道怎么改,上代码,下来再研究:

#网页不能访问
setenforce 0
vi /etc/selinux/config
SELINUX=disabled
vi /etc/sysconfig/iptables #防火墙
/etc/init.d/iptables stop #关闭防火墙

        大部分问题都解决了,现在说几个小细节,想让用户访问自己目录可以:修改/etc/httpd/conf/httpd.conf 文件中的IfModule mod_userdir.c开启用户目录访问。开启php简写格式需要设置php.ini,short_open_tag = On,记得重启apache。
        最后一个关键问题,因为是虚拟主机,随意只能在我的局域网能访问,外面不可访问,这次又是星哥站了出来:做端口转发,很好,之前写短信网关的时候就是为了好测试,这就驾轻就熟了,把本地的80端口映射到虚拟机的80端口,但是我本地也装了一套amp,因为配置host的时候不能带端口,果断把本地的80端口换成8888端口(修改httpd.conf文件的Listen),然后再putty connection的命令行:-L 80 192.168.203.183:80,192.168.203.183这个ip是虚拟机的ip地址。很好,绑定一下hosts文件:

192.168.0.64 www.500pai.cn #64是我本地ip地址

        效果很好,等这几天把单点登录和短信网关的一些小问题处理一下,就接着来memcache和nginx安装配置。不早了,又该睡觉了。

        晚上回来上不了网,因为之前发现有人蹭网,所以用了给最简单的办法,设置ip个数限制,刚开始还好,随着大家生活水平的提高,陆续添加了两个只能phone,一个kindle,结果上网被挤下来了,更改配置后就顺便改了密码,不知道怎么的,结果不能连了,估计是更改的时候不小心把密码输错了?但没道理有线也不行,貌似因为我重启系统(路由的)连接配置丢失了。
        他们仨没办法放弃了,我就用线直接连接猫,新建一个宽带连接:【网上邻居】->【属性】->【创建一个新的连接】->【连接到Internet】->【手动设置我的连接】->【用要求用户名和密码的宽带连接来连接】->【isp名称】(可空)->【电话号码】(可空)->【输入相关账号密码】->OK,顺利连上网络。
        现在开始配置路由了,把猫连接电脑的线还回给路由器,访问192.168.1.1(tplink是这么的),默认账号密码:admin、admin,进入,开始【设置向导】->【PPPoE(ADSL虚拟拨号)】->【上网账号密码】->OK
        一切就绪,给无线设个密码,大功告成。

        一看距离上次发博客快10天了,是时候更新更新了。
       最近主要忙于sso单点登录实现。目标:如500WAN旗下有500PAI网站,那么用户在500WAN网站登录后,打开500PAI时也应该处于登录状态。说说实现原理吧。
       1、当用户登录时,其实是登录到passport.500wan.com站点,并非登录www.500wan.com和www.500pai.com网站,并跳转到需登录目标网站,如500PAI。
       2、500PAI站点会去passport.500wan.com站点询问,现在是否登录了,如果想用户体验好些,这里应该是个异步动作,但涉及到跨域问题,所以建议用jsonp实现。passport检查session发现用户登录了,就会给500PAI分配一张票sid,用于php服务端获取是谁登陆了。
       3、500PAI取得票号sid后,可以通过passport站点提供的api验证票号sid得到登录的用户信息,500PAI网站对该用户自动登录。
       一个简单的sso单点登录模型就有了,如果有第三个域500pai.cn,需要登录,只需要到passport去取一张票号sid就可以了,不需要再次输入用户名密码登录。
       单点登录实现起来很简单,但有个性能问题,如打开任意500WAN或500PAI的每个页面都需要到passport去获取票号,因为大部分人可能是未登录状态,每次都需要去检查passport是否登录成功,这将对passport造成很大鸭梨。如果网站登录要求不高,可以再关键链接处添加跳转页,只有从跳转页跳转的网址才帮助检查passport是否登录,但这样,如果一个大型网站有N多第三方链接,这就很痛苦。
        我首先想到的方法是本地存储来实现,但是下来花了很多时间,始终涉及到一个跨域问题。某晚突然觉得通过iframe可以实现,iframe的跳转页将被缓存。下面是整个过程:【说明】窗口中的小窗口均为iframe隐藏域窗口