网络上的代码一般使用substr(md5("x"),0,16)、substr(md5("x"),8,16)、substr(md5("x"),8,-8)这种有损的的方式截取长度（最好用array_unique()剔除重复，即便如此如果多次生成也可能存在重复），md5("x",true)说是长度为16又都没写清楚，这里稍作研究。 md5()产生128位内容。 默认使用16进制（16进制即二进制1111[4位]，十进制为15加1进1）分成【32】组4位，长度为32的字符串，每个字符为[0-9,a-f]总共15个字符表示。 md5("x",true)输出128位二进制，直接打印的话根据不同编码（8位一字符长度就为16）可能产生乱码。 因此如果我使用大的进制来表示md5()结果的话，将会更短。 试验代码： <?php //md5()产生128位内容。 //默认使用16进制（16进制即二进制1111[4位]，十进制为15 加1进1）分成【32】组4位，长度为32的字符串，每个字符为[0-9,a-f]总共15个字符表示。 //因此如果我使用大的进制来表示md5()结果的话，将会更短。 //md5("x",true)输出128位二进制，直接打印的话根据不同编码可能产生乱码。 $data=''; $i=0; //这里转换成二进制内容的字符串。为什么不使用md5("x",true)生成128位二进制？php没现成的二进制分割连接工具。 foreach(str_split(md5("x")) as $item){ $d=str_pad(base_convert($item,16,2), 4, "0", STR_PAD_LEFT);//16进制转2进制并补0 $data.=$d; $i++; echo "{$i}-{$d}\n";//2进制结果 } echo "\n"; echo strlen($data);echo "\n";//128位二进制结果 $data.="00"; $j=0; //使用32进制（32进制即二进制11111[5位] || 十进制为15 加1进1）分成【26】组5位（=130位，前面加两个00以便整除），每个字符为[0-9,a-v]总共31个字符表示 foreach(str_split($data,5) as $item){ $j++; echo $j.'-'.base_convert($item,2,32);//32进制结果 echo "\n"; } echo "\n"; //结果成功缩短成长度为26的字符串，值不变 //可以使用更大进制么？可以，但base_convert()最高只支持36进制，需要自己实现更高进制转换。 //那为什么不使用36进制？因为十进制35 = 二进制100011，如果结果中刚好有一个111111~100111的值 > v，转换后将产生两个字符，最终长短不统一。同理33-35进制 ?> 思考 如果使用足够大的进制就可以任意缩短； 用更好的字符表示，每种进制结果其实是由进制数-1个字符表示，自己就可以组织并剔除一些容易混淆的字符比如数字1和小写的l，数字0和字母o，相当创造自己的进制的表示方法，当然不管从哪方面讲，使用扩展来做是最好的方法； 这也是一种改进的加密方式。
本来看MySQL协议是要做一个skynet的lua c库的，今天群里有人实现了，主要是根据openresty/lua-resty-mysql改的，之前工作老停留在协议内容上，不过也好，充分学些了一些MySQL协议。 同时这个项目也值得关注OpenResty。 一次正常的过程如下 三次握手建立tcp连接 建立MySql连接 a) 服务端往客户端发送握手初始化包(Handshake Initialization Packet) b) 客户端往服务端发送验证包(Client Authentication Packet) c) 服务端往客户端发送成功包 客户端与服务端之间交互 a) 客户端往服务端发送命令包（Command Packet） b) 服务端往客户端发送回应包（OK Packet, or Error Packet, or Result Set Packet） 断开MySql连接 a) 客户端往服务端发送退出命令包 四次握手断开tcp连接 Packet 在mysql中，如果client或server要发送数据，它需要将数据按照(2 ** 24 - 1)拆分成packet，给每一个packet添加header，然后再以此发送。 对于一个packet，格式如下： 3 payload length 1 sequence id string[len] payload 前面3个字节表明的是该packet的长度，每个packet最大不超过16MB。第4个字节表明的是该packet的序列号，从0开始，对于多个packet依次递增，等到下一个新的命令发送数据的时候才重置为0。前面4个字节组成了一个packet的header，后面就是该packet实际的数据。 因为一个packet最大能发送的数据位16MB，所以如果需要发送大于16MB的数据，就需要拆分成多个packet进行发送。 通常，server会回给client三种类型的packet i) OK Packet sql/protocol.cc::net_send_ok() ii) ERROR Packet sql/protocol.cc::net_send_error_packet() iii) result set PacketS 头包+字段包+EOF包+行包+EOF包 数据类型 mysql协议只有两种基本的数据类型，integer和string。 integer integer包括fixed length integer和length encoded integer两种，对于length encoded integer，用的地方比较多，这里详细说明一下。 对于一个integer，我们按照如下的方式将其转成length encoded integer： 如果value < 251，使用1 byte 如果value >= 251 同时 value < 2 ** 16，使用fc + 2 byte 如果value >= 2 ** 16 同时 value < 2 ** 24，使用fd + 3 byte 如果value >= 2 ** 24 同时 value < 2 ** 64，使用fe + 8 byte 相应的，对于一个length encoded integer，我们可以通过判断第一个byte的值来转成相应的integer。 string string包括： fixed length string，固定长度string null terminated string，以null结尾的string variable length string，通过另一个值决定长度的string length encoded string，通过起始length encoded integer决定长度的string rest of packet string，从当前位置到包结尾的string MySQL协议分析.pdf 截包逐个分析 包功能结构，看上图（新窗口打开大图）更佳 协议分析，go实现proxy MYSQL协议分析，分析较到位 官方协议说明 MySQL通讯协议研究1（基础知识） MySQL协议分析 redis协议
为什么在linux下选择epoll? select 本质上是通过设置或者检查存放fd标志位的数据结构来进行下一步处理。这样所带来的缺点是： 1 单个进程可监视的fd数量被限制 2 需要维护一个用来存放大量fd的数据结构，这样会使得用户空间和内核空间在传递该结构时复制开销大 3 对socket进行扫描时是线性扫描 单个进程所能打开的最大连接数有FD_SETSIZE宏定义，其大小是32个整数的大小（在32位的机器上，大小就是3232，同理64位机器上FD_SETSIZE为3264），当然我们可以对进行修改，然后重新编译内核，但是性能可能会受到影响，这需要进一步的测试。因为每次调用时都会对连接进行线性遍历，所以随着FD的增加会造成遍历速度慢的“线性下降性能问题”。内核需要将消息传递到用户空间，都需要内核拷贝动作 poll 本质上和select没有区别，它将用户传入的数组拷贝到内核空间，然后查询每个fd对应的设备状态，如果设备就绪则在设备等待队列中加入一项并继续遍历，如果遍历完所有fd后没有发现就绪设备，则挂起当前进程，直到设备就绪或者主动超时，被唤醒后它又要再次遍历fd。这个过程经历了多次无谓的遍历。 它没有最大连接数的限制，原因是它是基于链表来存储的，但是同样有一个缺点：大量的fd的数组被整体复制于用户态和内核地址空间之间，而不管这样的复制是不是有意义。poll还有一个特点是“水平触发”，如果报告了fd后，没有被处理，那么下次poll时会再次报告该fd。poll本质上和select没有区别，但是它没有最大连接数的限制，原因是它是基于链表来存储的 因为每次调用时都会对连接进行线性遍历，所以随着FD的增加会造成遍历速度慢的“线性下降性能问题”。内核需要将消息传递到用户空间，都需要内核拷贝动作 epoll 在前面说到的复制问题上，epoll使用mmap减少复制开销。还有一个特点是，epoll使用“事件”的就绪通知方式，通过epoll_ctl注册fd，一旦该fd就绪，内核就会采用类似callback的回调机制来激活该fd，epoll_wait便可以收到通知 虽然连接数有上限，但是很大，1G内存的机器上可以打开10万左右的连接，2G内存的机器可以打开20万左右的连接 因为epoll内核中实现是根据每个fd上的callback函数来实现的，只有活跃的socket才会主动调用callback，所以在活跃socket较少的情况下，使用epoll没有前面两者的线性下降的性能问题，但是所有socket都很活跃的情况下，可能会有性能问题。epoll通过内核和用户空间共享一块内存来实现的。 ET还是LT? 什么是epol的LT与ET 电子通信领域的名词，文字描述有点儿困难，用两个图来表示。 在图中 x 轴表示时间。 high 表示 fd 不可读或不可写。 low 表示 fd 可读或可写。 0 表示 IO 多路复用器不返回事件。 1 表示 IO 多路复用器返回事件。 Edge Trigger 边缘触发 high 00000000 00000000 0 0 0 0 0 0 0 0 low 00000001 00000001 00000000 Level Trigger 水平触发 high 11111111 11111111 1 1 1 1 1 1 1 1 low 00000001 10000001 10000000 一图胜千言，简单来说就是当边缘触发时，只有 fd 变成可读或可写的那一瞬间才会返回事件。当水平触发时，只要 fd 可读或可写，一直都会返回事件。 我们来举个例子，当一个 fd 收到 1000 字节的数据，这时无论 边缘触发 还是 水平触发 都会返回可读事件。如果我们调用 recv 读了 500 字节并返回 IO多路复用器 。在 边缘触发 的IO多路复用器中，因为可读的状态并没有回到 low 位，我们以后永远都不会再收 fd 的读事件了，除非我们把所有 buffer 中的数据全部读完。在条件触发的IO多路复用器中，我们会立即再次收到可读事件，直到我们把剩下的 500 字节 recv 完。 通常来讲，我们倾向于使用 Level Trigger 处理读事件，以防代码有 Bug 导致接收不到可读事件 文字描述： LT: 水平触发 一直触发 ·内核中的socket接受缓冲区不为空 有数据可读 读事件一直触发 ·内核中的socket发送缓冲区不满 可以继续写入数据 写事件一直触发 ·因此在一开始的时候不能马上关注EPOLLOUT 因为这个时候内核socket发送缓冲区中没有数据一直可写 所以会一直触发EPOLLOUT 导致busy_loop 应该在写数据事件返回EAGAIN(说明内核中socekt的发送缓冲区满了)的时候 才开始关注EPOLLOUT 即关注内核socket缓冲区的可写事件 把还没写完的数据存至应用缓冲区 等内核缓冲区空出来，可写事件就触发，就从应用缓冲区取出数据拷贝到内核缓冲区，如果应用缓冲区中的数据已经完倒拷到了内核缓冲区，就取消可写事件，如果不能完全拷贝完，就不取消 ET: 边沿触发 从低到高这种变化才会通知 ·由低到高 由高到低出发 ·一开始的时候就关注EPOLLIN EPOLLOUT不会产生busy_loop ·这里读要一次行全部读完到产生EAGAIN为止 才会处于低电平模式 否则一直处于高电平没有高低变化再有数据到来的时候 不再出发EPOLLIN事件 容易落掉数据 ·将未发送的数据添加到应用层缓冲区 connfd EPOLLOUT事件到来内核缓冲区可写 发送数据 ·发送数据直到发送完或者产生EAGAIN(内核缓冲区满了) ·如果是epoll模型，可以改用edge trigger(ET)。问题是如果漏掉了一次accpet(2) 程序再也不会受到新的连接 ET还是LT? 相对于ET LT更容易处理些 理论上ET更高效些 但是实际的使用中处理不好容易漏掉事件 并且在实际的使用中ET未必比LT效率高 ngx使用了ET muduo skynet使用了LT 参考 wiki/epoll 大并发服务器开发 Linux epoll介绍和程序实例 linux下epoll ET模型accept并发问题 用 Python 理解服务器模型
#!/usr/bin/python #-*- coding:utf8 -*- ''' Created on 2014-06-02 # QQ:263967133 # pings运行一般运行一次就够了，自己选一个ip段 # google ip地址段 # 64.233.160.0 - 64.233.191.255 # 66.102.0.0 - 66.102.15.255 # 66.249.64.0 - 66.249.95.255 # 72.14.192.0 - 72.14.255.255 # 74.125.0.0 - 74.125.255.255 # 209.85.128.0 - 209.85.255.255 # 216.239.32.0 - 216.239.63.255 window下应该只需要把ping参数-c 改成 -n，没测 ''' import time,os start_Time=int(time.time()) #记录开始时间 # 生成ips def pings(): ips=open('host.txt','w') for ip3 in range(2, 254): ip='66.249.64.'+str(ip3)+"\n" # print ip ips.write(ip) ips.close() #判断文件中的ip是否能ping通，并且将通与不通的ip分别写到两个文件中 #文件中的ip一行一个 def ping_Test(): ips=open('host.txt','r') ip_True = open('ip_True.txt','w') ip_False = open('ip_False.txt','w') count_True,count_False=0,0 for ip in ips.readlines(): ip = ip.replace('\n','') #替换掉换行符 return1=os.system('ping -c 2 -w 1 %s'%ip) #每个ip ping2次，等待时间为1s if return1: print 'ping %s is fail'%ip ip_False.write(ip+"\n") #把ping不通的写到ip_False.txt中 count_False += 1 else: print 'ping %s is ok'%ip ip_True.write(ip+"\n") #把ping通的ip写到ip_True.txt中 count_True += 1 ip_True.close() ip_False.close() ips.close() end_Time = int(time.time()) #记录结束时间 print "time(秒)：",end_Time - start_Time,"s" #打印并计算用的时间 print "ping通数：",count_True," ping不通的ip数：",count_False # pings() ping_Test() gist