作为 Python 的初学者,学习别人的代码也是必要过程,本文主要是针对 《
Python中使用epoll开发服务端程序
》中的代码,进行源码注释说明。从代码中至少可以学习到如下三点:
- logging 模块的使用;
- epoll 的使用;
- 如何利用 try...catch 来处理“读穿”。
============ 我是分割线 ==============
server端代码:
#!/usr/bin/python
#-*- coding:utf-8 -*-
import socket, logging
import select, errno
logger = logging.getLogger("network-server")
def InitLog():
logger.setLevel(logging.DEBUG)
fh = logging.FileHandler("network-server.log")
fh.setLevel(logging.DEBUG)
ch = logging.StreamHandler()
ch.setLevel(logging.ERROR)
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
ch.setFormatter(formatter)
fh.setFormatter(formatter)
logger.addHandler(fh)
logger.addHandler(ch)
if __name__ == "__main__":
InitLog()
try:
# 创建 TCP socket 作为监听 socket
listen_fd = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
except socket.error, msg:
logger.error("create socket failed")
try:
# 设置 SO_REUSEADDR 选项
listen_fd.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
except socket.error, msg:
logger.error("setsocketopt SO_REUSEADDR failed")
try:
# 进行 bind -- 此处未指定 ip 地址,即 bind 了全部网卡 ip 上
listen_fd.bind(('', 2003))
except socket.error, msg:
logger.error("bind failed")
try:
# 设置 listen 的 backlog 数
listen_fd.listen(10)
except socket.error, msg:
logger.error(msg)
try:
# 创建 epoll 句柄
epoll_fd = select.epoll()
# 向 epoll 句柄中注册 监听 socket 的 可读 事件
epoll_fd.register(listen_fd.fileno(), select.EPOLLIN)
except select.error, msg:
logger.error(msg)
connections = {}
addresses = {}
datalist = {}
while True:
# epoll 进行 fd 扫描的地方 -- 未指定超时时间则为阻塞等待
epoll_list = epoll_fd.poll()
for fd, events in epoll_list:
# 若为监听 fd 被激活
if fd == listen_fd.fileno():
# 进行 accept -- 获得连接上来 client 的 ip 和 port,以及 socket 句柄
conn, addr = listen_fd.accept()
logger.debug("accept connection from %s, %d, fd = %d" % (addr[0], addr[1], conn.fileno()))
# 将连接 socket 设置为 非阻塞
conn.setblocking(0)
# 向 epoll 句柄中注册 连接 socket 的 可读 事件
epoll_fd.register(conn.fileno(), select.EPOLLIN | select.EPOLLET)
# 将 conn 和 addr 信息分别保存起来
connections[conn.fileno()] = conn
addresses[conn.fileno()] = addr
elif select.EPOLLIN & events:
# 有 可读 事件激活
datas = ''
while True:
try:
# 从激活 fd 上 recv 10 字节数据
data = connections[fd].recv(10)
# 若当前没有接收到数据,并且之前的累计数据也没有
if not data and not datas:
# 从 epoll 句柄中移除该 连接 fd
epoll_fd.unregister(fd)
# server 侧主动关闭该 连接 fd
connections[fd].close()
logger.debug("%s, %d closed" % (addresses[fd][0], addresses[fd][1]))
break
else:
# 将接收到的数据拼接保存在 datas 中
datas += data
except socket.error, msg:
# 在 非阻塞 socket 上进行 recv 需要处理 读穿 的情况
# 这里实际上是利用 读穿 出 异常 的方式跳到这里进行后续处理
if msg.errno == errno.EAGAIN:
logger.debug("%s receive %s" % (fd, datas))
# 将已接收数据保存起来
datalist[fd] = datas
# 更新 epoll 句柄中连接d 注册事件为 可写
epoll_fd.modify(fd, select.EPOLLET | select.EPOLLOUT)
break
else:
# 出错处理
epoll_fd.unregister(fd)
connections[fd].close()
logger.error(msg)
break
elif select.EPOLLHUP & events:
# 有 HUP 事件激活
epoll_fd.unregister(fd)
connections[fd].close()
logger.debug("%s, %d closed" % (addresses[fd][0], addresses[fd][1]))
elif select.EPOLLOUT & events:
# 有 可写 事件激活
sendLen = 0
# 通过 while 循环确保将 buf 中的数据全部发送出去
while True:
# 将之前收到的数据发回 client -- 通过 sendLen 来控制发送位置
sendLen += connections[fd].send(datalist[fd][sendLen:])
# 在全部发送完毕后退出 while 循环
if sendLen == len(datalist[fd]):
break
# 更新 epoll 句柄中连接 fd 注册事件为 可读
epoll_fd.modify(fd, select.EPOLLIN | select.EPOLLET)
else:
# 其他 epoll 事件不进行处理
continue
client 端代码(比较简单,未加注释):
import socket
import time
import logging
logger = logging.getLogger("network-client")
logger.setLevel(logging.DEBUG)
fh = logging.FileHandler("network-client.log")
fh.setLevel(logging.DEBUG)
ch = logging.StreamHandler()
ch.setLevel(logging.ERROR)
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
ch.setFormatter(formatter)
fh.setFormatter(formatter)
logger.addHandler(fh)
logger.addHandler(ch)
if __name__ == "__main__":
try:
connFd = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
except socket.error, msg:
logger.error(msg)
try:
connFd.connect(("127.0.0.1", 2003))
logger.debug("connect to network server success")
except socket.error,msg:
logger.error(msg)
for i in range(1, 11):
data = "The Number is %d" % i
if connFd.send(data) != len(data):
logger.error("send data to network server failed")
break
readData = connFd.recv(1024)
print readData
time.sleep(1)
connFd.close()
运行结果:
client 命令行输出。
[root@Betty Python]# python epoll_client.py
The Number is 1
The Number is 2
The Number is 3
The Number is 4
The Number is 5
The Number is 6
The Number is 7
The Number is 8
The Number is 9
The Number is 10
[root@Betty Python]#
client 日志文件。
[root@Betty Python]# vi network-client.log
2013-07-26 15:36:43,995 - network-client - DEBUG - connect to network server success
server 日志文件。
[root@Betty Python]# vi network-server.log
2013-07-26 15:36:43,995 - network-server - DEBUG - accept connection from 127.0.0.1, 34861, fd = 6
2013-07-26 15:36:43,995 - network-server - DEBUG - 6 receive The Number is 1
2013-07-26 15:36:44,996 - network-server - DEBUG - 6 receive The Number is 2
2013-07-26 15:36:45,996 - network-server - DEBUG - 6 receive The Number is 3
2013-07-26 15:36:46,996 - network-server - DEBUG - 6 receive The Number is 4
2013-07-26 15:36:47,996 - network-server - DEBUG - 6 receive The Number is 5
2013-07-26 15:36:48,997 - network-server - DEBUG - 6 receive The Number is 6
2013-07-26 15:36:49,998 - network-server - DEBUG - 6 receive The Number is 7
2013-07-26 15:36:50,999 - network-server - DEBUG - 6 receive The Number is 8
2013-07-26 15:36:52,000 - network-server - DEBUG - 6 receive The Number is 9
2013-07-26 15:36:53,001 - network-server - DEBUG - 6 receive The Number is 10
2013-07-26 15:36:54,002 - network-server - DEBUG - 127.0.0.1, 34861 closed
============ 我是分割线 ==============
感谢原作者!