MySQL协议分析

本来看MySQL协议是要做一个skynet的lua c库的,今天群里有人实现了,主要是根据openresty/lua-resty-mysql改的,之前工作老停留在协议内容上,不过也好,充分学些了一些MySQL协议。

同时这个项目也值得关注OpenResty

一次正常的过程如下

  1.  三次握手建立tcp连接
  2. 建立MySql连接 
    a) 服务端往客户端发送握手初始化包(Handshake Initialization Packet)
    b) 客户端往服务端发送验证包(Client Authentication Packet)
    c) 服务端往客户端发送成功包
  3. 客户端与服务端之间交互
    a) 客户端往服务端发送命令包(Command Packet) 
    b) 服务端往客户端发送回应包(OK Packet, or Error Packet, or Result Set Packet) 
  4. 断开MySql连接 
    a) 客户端往服务端发送退出命令包 
  5. 四次握手断开tcp连接 

- 阅读剩余部分 -

为什么是EPOLL ? LT还是ET?

为什么在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 理解服务器模型

g-o-o-g-l-e i-p-s

#!/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

php函数crc32存在的问题

中午突然获知AS客户端与服务端c协议通讯时,为了验证完整性和防外挂,服务端要求协议带上crc的验证信息,我做的php后台有Socket形式的GM命令也要求相同的协议。

但php和服务端计算出来的验证值是不同的,然后我试了下python写crc,python和服务端计算后的验证值是相同的。

经过一番搜索,通过文章1文章2得知,在python2.x中 binascii.crc32(v) 求出了v的crc32值,是一个long型,经过 binascii.crc32(v) & 0xffffffff 计算后的验证值跟php一样的,可以说:

php的crc32 = python的 binascii.crc32(v) & 0xffffffff

此文验证结果没错,但原理性内容存在问题,待我学习crc算法,慎读。

- 阅读剩余部分 -

游戏通讯协议

你们用哪一种协议?
1、包长度->版本号->协议号->内容

2、包头标志(比如“|”)->版本号->协议号->内容长度->内容

还有消息包格式的定义,这个曾在云风的blog上展开过激烈的争论。消息包格式定义包括三段,包长、消息码和包体,争论的焦点在于应该是消息码在前还是包长在前,我们也把这个当作是信仰问题吧,有兴趣的去云风的blog上看看,论论。

另外早期有些游戏的包格式定义是以特殊字符作分隔的,这样一个好处是其中某个包出现错误后我们的游戏还能继续。但实际上,我觉得这是完全没有必要的,真要出现这样的错误,直接断开这个客户端的连接可能更安全。而且,以特殊字符做分隔的消息包定义还加大了一点点网络数据量。
引用百万用户级游戏服务器架构设计

lua调用so动态库

今天,我给我的测试软件框架添加了一个 C 模块,里面封装了一个 sleep 的系统调用。我的目的是在用 Lua 时,要延时的话,不用执行 os.execute("sleep 5") 之类的蹩脚语句,而替之以 mt.sleep(5) 之类的就行了。

- 阅读剩余部分 -

skynet目录和文件分析

  • 根目录
    README.md 简单介绍了怎么编译和测试Skynet
    LICENSE 许可证信息,采用MIT,很宽松的协议。
    Makefile 编译规则文件,用于编译Skynet
    platform.mk 编译与平台相关的设置
    3rd 第三方的代码,有lua和jemalloc等
    examples 附带的例子
    lualib 使用lua写的库
    lualib-src 使用C写并封装给lua使用的库
    service 使用lua写的Skynet的服务模块
    service-src 使用C写的Skynet的服务模块
    skynet-src Skynet的核心代码
    test 使用lua写的一些测试代码