python 网络请求框架_网络编程(九):Python裸写异步非阻塞网络框架

# 从之前写好的守护进程类中导入Daemon

from daemon import Daemon

import socket

import select

import time

# 导入pdb,即Python debug模块,方便调试

import pdb

# 设定这个变量,可以控制本文件在被import *

# 的时候导入的变量、函数和类的范围

__all__ = ["nbNet"]

#DEBUG = True

# 这个文件的内容会在后面详述

from nbNetUtils import *

# 我们住程序的conn_state的类原型

class STATE:

def __init__(self):

self.state = "accept" # 初始状态

self.have_read = 0

# 我们的程序在开始的时候总是要读取10个字节的头

self.need_read = 5

self.have_write = 0

self.need_write = 0

# 读写缓冲区

self.buff_read = ""

self.buff_write = ""

# 用来后续存放sock对象

self.sock_obj = ""

def printState(self):

"""debug输出函数"""

if DEBUG:

dbgPrint('\n- current state of fd:%d' % self.sock_obj.fileno())

dbgPrint(" - - state:%s" % self.state)

dbgPrint(" - - have_read:%s" % self.have_read)

dbgPrint(" - - need_read:%s" % self.need_read)

dbgPrint(" - - have_write:%s" % self.have_write)

dbgPrint(" - - need_write:%s" % self.need_write)

dbgPrint(" - - buff_write:%s" % self.buff_write)

dbgPrint(" - - buff_read:%s" % self.buff_read)

dbgPrint(" - - sock_obj:%s" % self.sock_obj)

class nbNetBase:

'''non-blocking Net类,首先我们设定了一套通信的协议,以10个byte的ASCII码数字的头来表示,后续数据的长度,例如:0000000005HELLO0000000001a0000000012hello world\n这样做的好处在于,我们可以很容易的解析消息的结束位置。并且能够在这套框架下进行任何数据的传输,包括各种二进制数据,而不需要转义。'''

def setFd(self, sock):

"""把sock放进全局的conn_state字典里"""

dbgPrint("\n-- setFd start!")

tmp_state = STATE()

tmp_state.sock_obj = sock

self.conn_state[sock.fileno()] = tmp_state

self.conn_state[sock.fileno()].printState()

dbgPrint("\n-- setFd end!")

def accept(self, fd):

"""在fd上进行accept,并且把socket设置成非阻塞模式"""

dbgPrint("\n-- accept start!")

sock_state = self.conn_state[fd]

sock = sock_state.sock_obj

conn, addr = sock.accept()

# 把socket设置成非阻塞模式

conn.setblocking(0)

return conn

def close(self, fd):

"""关闭fd,从epoll中取消关注,清理conn_state里相关的数据"""

try:

# cancel of listen to event

sock = self.conn_state[fd].sock_obj

sock.close()

except:

dbgPrint("Close fd:%sabnormal" % fd)

finally:

self.epoll_sock.unregister(fd)

self.conn_state.pop(fd)

def read(self, fd):

"""读取fd中的数据(非阻塞模式)并且设置各个计数器的数值,以供后续处理返回值是个字符串,表示下一步需要进行的处理,如:“readcontent”、“process”、“readmore”"""

# pdb.set_trace()

try:

# 从conn_state字典中取出连接

sock_state = self.conn_state[fd]

conn = sock_state.sock_obj

if sock_state.need_read <= 0:

raise socket.error

# 进行一次非阻塞的读取

one_read = conn.recv(sock_state.need_read)

dbgPrint("\tread func fd:%d, one_read:%s, need_read:%d" %

(fd, one_read, sock_state.need_read))

# 如果什么都没有读到,那应该是socket出错了

if len(one_read) == 0:

raise socket.error

# 将读到的数据放入buff_read,

# 设定have_read(已经从socket中读取的数量)

# 设定need_read(还需从socket中要读取的数量)

sock_state.buff_read += one_read

sock_state.have_read += len(one_read)

sock_state.need_read -= len(one_read)

sock_state.printState()

# 如果已经读取的数据是10个byte,那么说明数据的10字节头已经读取完毕,

# 我们可以解析判断后续的数据的长度了

if sock_state.have_read == 5:

# 由于是ASCII的数据头,我们需要用int()将它转化成数字

header_said_need_read = int(sock_state.buff_read)

if header_said_need_read <= 0:

raise socket.error

sock_state.need_read += header_said_need_read

sock_state.buff_read = ''

# 为了方便大家理解,这里打印一些debug信息

sock_state.printState()

return "readcontent"

elif sock_state.need_read == 0:

# 所有数据已经读取完毕,转入业务逻辑处理“process”

return "process"

else:

# 出去上述的所有情况,剩下的情况就是还需要读取更多的数据

return "readmore"

except (socket.error, ValueError), msg:

# 进行一些异常处理

try:

# errno等于11,即“EAGAIN”。是表示,还可以尝试进行一次读取

if msg.errno == 11:

dbgPrint("11 " + msg)

return "retry"

except:

pass

# 除去上述的特殊情况,发生了任何错误,不要挣扎,直接把socket关闭

return 'closing'

def write(self, fd):

"""非阻塞的写数据到socket中,返回值的涵义和上述的read一致"""

# 还是取出fd对应的sock_state结构体

sock_state = self.conn_state[fd]

conn = sock_state.sock_obj

last_have_send = sock_state.have_write

try:

# 非阻塞的发送数据,这里send的返回值是已经成功发送的数据量

have_send = conn.send(sock_state.buff_write[last_have_send:])

sock_state.have_write += have_send

sock_state.need_write -= have_send

if sock_state.need_write == 0 and sock_state.have_write != 0:

# 如果已经全部发送成功,返回“writecomplete”

sock_state.printState()

dbgPrint('\nwrite data completed!')

return "writecomplete"

else:

return "writemore"

except socket.error, msg:

# 发生错误,直接关闭socket

return "closing"

def run(self):

"""这个函数是装个状态机的主循环所在"""

while True:

# 这部分就是我们上面多次提到的epoll

# poll()返回的epoll_list就是有事件发生的fd的list

# 需要在循环中按照event的类型分别处理,一般分为以下几种类型

# EPOLLIN :表示对应的文件描述符可以读;

# EPOLLOUT:表示对应的文件描述符可以写;

# EPOLLPRI:表示对应的文件描述符有紧急的数据可读;一般不需要特殊处理

# EPOLLERR:表示对应的文件描述符发生错误;后面这两种需要关闭socket

# EPOLLHUP:表示对应的文件描述符被挂断;

epoll_list = self.epoll_sock.poll()

for fd, events in epoll_list:

sock_state = self.conn_state[fd]

if select.EPOLLHUP & events:

dbgPrint("EPOLLHUP")

sock_state.state = "closing"

elif select.EPOLLERR & events:

dbgPrint("EPOLLERR")

sock_state.state = "closing"

# 调用状态机

self.state_machine(fd)

def state_machine(self, fd):

"""这里的逻辑十分的简单:“按照不同fd的state,调用不同的函数即可”具体的对应表见nbNet的__init__()"""

sock_state = self.conn_state[fd]

self.sm[sock_state.state](fd)

class nbNet(nbNetBase):

def __init__(self, addr, port, logic):

dbgPrint('\n__init__: start!')

# 初始化conn_state字典,这个字典将会保存每个连接的状态

# 以及连接的读写内容。

self.conn_state = {}

# 初始化监听socket

# socket.AF_INET指的是以太网

# socket.SOCK_STREAM指的是TCP

self.listen_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)

# 开启SO_REUSEADDR,这样当监听端口处于各种xxx_WAIT的状态的时候

# 也能正常的listen、bind

self.listen_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

# 绑定在制定的IP和端口上

self.listen_sock.bind((addr, port))

# 指定backlog数,这里初学者可能会存在一个误区,需要解释一下:

# 有些地方把listen的参数成为“积压值”或者backlog,

# 最大连接数是最大能处理的连接数(accept了丢一边晾着是耍流氓)

# 提高并发处理能力是门学问,不单是提高“最大连接数”,两点结论:

# 1. backlog不能提高“最大连接数”

# 2. backlog不宜设置过大

# 举个栗子,假设我们的服务器是一个非常受欢迎的饭店:

# 最大连接数就是这个饭店最大能容纳的顾客人数,backlog就是门外允许排队的最大长度。

# 如果饭店点菜慢上菜也慢(服务器的处理不能满足要求),饭店将很快被被顾客坐满(最大连接数上限)

# ,门口排位如果无限排下去(backlog设置非常大),可能还不如告诉顾客现在人太多了,

# 我们处理不过来,不过等会儿再来试试。

# 想要提高服务质量只能通过提高翻桌率(服务器处理速度)来实现。

self.listen_sock.listen(5) # backlog

# 将listen socket同样放入conn_state

self.setFd(self.listen_sock)

# 初始化epoll的fd

self.epoll_sock = select.epoll()

# 这里指定我们的listen socket只关注EPOLLIN,即connect过来的连接

# LT 是这里的默认, 想要ET 需要改成'select.EPOLLIN | select.EPOLLET'

self.epoll_sock.register(self.listen_sock.fileno(), select.EPOLLIN)

# 业务逻辑处理函数

self.logic = logic

# 状态机的各个状态的处理函数,这里的self.sm是一个key是字符串,value是函数的字典

self.sm = {

"accept": self.accept2read,

"read": self.read2process,

"write": self.write2read,

"process": self.process,

"closing": self.close,

}

dbgPrint('\n__init__: end, register no:%s' %

self.listen_sock.fileno())

def process(self, fd):

"""调用业务逻辑处理函数self.logic,然后将它返回的字符串当成是Server对Client的回应通过约定好调用的函数原型,就可以实现比较干净的业务逻辑和网络框架的分离"""

sock_state = self.conn_state[fd]

response = self.logic(sock_state.buff_read)

# 组装给Client回应的10字节协议头

sock_state.buff_write = "%05d%s" % (len(response), response)

sock_state.need_write = len(sock_state.buff_write)

sock_state.state = "write"

self.epoll_sock.modify(fd, select.EPOLLOUT)

sock_state.printState()

def accept2read(self, fd):

"""这个函数主要完成accept到等待数据读取的状态转换"""

# accept一个连接之后,需要注册,初始化state为read

conn = self.accept(fd)

self.epoll_sock.register(conn.fileno(), select.EPOLLIN)

self.setFd(conn)

self.conn_state[conn.fileno()].state = "read"

# 现在accept 到 read的转换完成了

# 需要明确的是,我们的listen socket还是处于等待连接到来

# 的accept状态

dbgPrint("\n-- accept end!")

def read2process(self, fd):

"""这个函数主要完成read完所有请求到处理业务逻辑的状态转换"""

# pdb.set_trace()

read_ret = ""

try:

read_ret = self.read(fd)

except (Exception), msg:

dbgPrint(msg)

read_ret = "closing"

if read_ret == "process":

# 数据接收完成,转换到process阶段

self.process(fd)

# readcontent、readmore、retry 都不用改变socket的state

elif read_ret == "readcontent":

pass

elif read_ret == "readmore":

pass

elif read_ret == "retry":

pass

elif read_ret == "closing":

self.conn_state[fd].state = 'closing'

# 发生错误直接关闭,做到快速失败

self.state_machine(fd)

else:

raise Exception("impossible state returned by self.read")

def write2read(self, fd):

"""这个函数主要完成write给client回应到等待数据读取的状态转换。这个情况就是我们经常听到的“长连接”"""

try:

write_ret = self.write(fd)

except socket.error, msg:

write_ret = "closing"

if write_ret == "writemore":

pass

# 写数据完成,重置各种计数器,开始等待新请求过来

elif write_ret == "writecomplete":

sock_state = self.conn_state[fd]

conn = sock_state.sock_obj

self.setFd(conn)

self.conn_state[fd].state = "read"

self.epoll_sock.modify(fd, select.EPOLLIN)

elif write_ret == "closing":

# 发生错误直接关闭,做到快速失败

dbgPrint(msg)

self.conn_state[fd].state = 'closing'

# closing directly when error.

self.state_machine(fd)

if __name__ == '__main__':

# 这个是我们演示用的“业务逻辑”,做的事情就是将请求的数据反转

# 例如:

# 收到:0000000005HELLO

# 回应:0000000005OLLEH

def logic(d_in):

return(d_in[::-1])

# 监听在0.0.0.0:9076

reverseD = nbNet('0.0.0.0', 9090, logic)

# 状态机开始运行,除非被kill,否则永不退出

reverseD.run()

# >>study:/home/kang>telnet 127.0.0.1 50009

# Trying 127.0.0.1...

# Connected to 127.0.0.1.

# Escape character is '^]'.

# 00005abcde

# 00005edcba

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值