1.服务器部署。
部署
- 为每个服务器程序都编写服务所提供的所有功能:通过两次fork()创建一个Unix守护进程或是将自己注册为一个Windows服务,安排进行系统级的日志操作,支持配置文件以及提供启动、关闭、和重启的相关机制。 ps:fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。
- 十二要素应用:该方法提倡只实现服务器程序必要功能的最小集合
2.简单的TCP协议。
下面的代码是模仿服务器和客户端的交互,客户端向服务端提出三个问题,服务器进行回答并将答案传给客户端。
#!/usr/bin/python
#coding:utf-8
import argparse, socket, time
#客户端希望服务器理解的三个问题和回答,以字典对应键值对的形式给出
aphorisms = {b'Beautiful is better than?': b'Ugly.',
b'Explicit is better than?': b'Implicit.',
b'Simple is better than?': b'Complex.'}
def get_answer(aphorism):
time.sleep(0.0)
return aphorisms.get(aphorism, b'Error: unknown aphorism.')
def parse_command_line(description):
parser = argparse.ArgumentParser(description=description)
parser.add_argument('host', help='IP or hostname')
parser.add_argument('-p', metavar='port', type=int, default=1060,
help='TCP port (default 1060)')
args = parser.parse_args()
address = (args.host, args.p)
return address
#构造TCP监听套接字
def create_srv_socket(address):
listener = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
listener.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
listener.bind(address)
listener.listen(64)
print('Listening at {}'.format(address))
return listener
def accept_connections_forever(listener):
while True:
sock, address = listener.accept()
print('Accepted connection from {}'.format(address))
handle_conversation(sock, address)
def handle_conversation(sock, address):
try:
while True:
handle_request(sock)
# 捕捉异常除了EOFError其他错误都会被下一个except捕获并打印
except EOFError:
print('Client socket to {} has closed'.format(address))
except Exception as e:
print('Client {} error: {}'.format(address, e))
# 确保无论该函数通过那一条代码路径退出都会执行关闭套接字选项
finally:
sock.close()
def handle_request(sock):
aphorism = recv_until(sock, b'?')
answer = get_answer(aphorism)
sock.sendall(answer)
def recv_until(sock, suffix):
message = sock.recv(4096)
if not message:
raise EOFError('socket closed')
while not message.endswith(suffix):
data = sock.recv(4096)
if not data:
raise IOError('received {!r} then socket closed'.format(message))
message += data
return message
if __name__ == '__main__':
a = parse_command_line('TCP')
b = create_srv_socket(a)
accept_connections_forever(b)
这段代码的重点在最后四个函数。
- accept_connections_forever():函数中包含一个简单的循环,循环中不断通过监听套接字接受连接请求,并且使用print()把每个连接的客户端打印出来,然后将连接套接字作为参数传给下面的函数。
- handle_conversation():此函数包含了一个无限的循环,来不断地处理请求。该程序会捕捉可能发生的错误,这使得客户端套接字的任何问题都不会引起程序崩溃。
- handle_request(sock):函数能够简单的读取客户端的问题,然后做出应答。
- recv_until():进行封帧操作。只要不断累加的字节字符串没有形成一个完整的问题,就会不断重复调用套接字的recv()方法。
为了测试这个代码我们还要编写一个客户端:
#!/usr/bin/python
#coding:utf-8
import argparse,random,socket,zen_utils
def client(address,cause_error=False):
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sock.connect(address)
aphorisms = list(zen_utils.aphorisms)
if cause_error:
sock.sendall(aphorisms[0][:-1])
return
for aphorism in random.sample(aphorisms,3):
sock.sendall(aphorism)
print(aphorism,zen_utils.recv_until(sock,b'.'))
sock.close()
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Example client')
parser.add_argument('host',help='IP or hostname')
parser.add_argument('-e',action='store_true',help='cause an error')
parser.add_argument('-p',metavar='port',type=int,default=1060,
help='TCP port (default 1060)')
args = parser.parse_args()
address = (args.host,args.p)
client(address,args.e)
该客户端提供了一个-e参数用来模拟客户端发送不完整的问题,然后使服务器突然挂起。下面是测试结果:
3.单线程服务器。
使用2中的示例代码可以快速编写一个单线程服务器。
#!/usr/bin/python
#coding:utf-8
import zen_utils
if __name__ == '__main__':
address = zen_utils.parse_command_line('Simple single-threaded server')
listener = zen_utils.create_srv_socket(address)
zen_utils.accept_connections_forever(listener)
如果与一个客户进行会话期间,另一个客户端也尝试连接这个服务器,那么就有可能造成这个服务器的死锁。由此出现了多线程,多进程服务器。
4.多线程与多进程服务器。
为了解决服务器与多个客户端进行会话,于是诞生了利用操作系统的内置支持,使用多个控制线程单独运行同一段代码。可以创建多个共享内存空间的线程,也可以创建完全独立的进程。0下面是一个多线程服务器:
#!/usr/bin/python
#coding:utf-8
import zen_utils
from threading import Thread
def start_threads(listener,workers=4):
t = (listener,)
for i in range(workers):
Thread(target=zen_utils.accept_connection_forever,args=t).start()
if __name__ == '__main__':
address = zen_utils.parse_command_line('multi-threaded server')
listener = zen_utils.create_srv_socket(address)
start_threads(listener)
这段代码中主线程启动n个服务器线程,然后退出。主线程认为这n个工作线程将永远运行,因此运行这些线程的进程也将会保持运行状态。
SocketServer框架
它使用了大量面向对象以及多继承的设计思想。socketserver模块将上述多线程模式分为了两个模式:
- 第一个用于打开监听套接字并接受客户端的server模式 。
- 第二个是用于通过某个打开的套接字与特定客户端进行会话的handler模式 。
下面是结合这两种模式的代码:
#!/usr/bin/python
#coding:utf-8
from socketserver import BaseRequestHandler,TCPServer,ThreadingMixIn
import zen_utils
class ZenHandler(BaseRequestHandler):
def handle(self):
zen_utils.handle_conversation(self.request,self.client_address)
class ZenServer(ThreadingMixIn,TCPServer):
allow_reuse_address = 1
if __name__ == '__main__':
address = zen_utils.parse_command_line('legacy "SocketServer" server')
server = ZenServer(address, ZenHandler)
server.serve_forever()
我们需要实例化一个server对象,然后将一个handler对象作为参数传给server对象。可以将ThreadingMixIN改为ForkingMixIn,这样就可以使用完全隔离线程来处理连接的客户端,而不使用进程。
5.异步服务器。
它可以在所有连接的客户端之间自由切换,并提供相应的服务 。下面是一个简单的异步服务器:
#!/usr/bin/python
#coding:utf-8
import select, zen_utils
def all_events_forever(poll_object):
while True:
for fd, event in poll_object.poll():
yield fd, event
def serve(listener):
sockets = {listener.fileno(): listener}
addresses = {}
bytes_received = {}
bytes_to_send = {}
poll_object = select.poll()
poll_object.register(listener, select.POLLIN)
for fd, event in all_events_forever(poll_object):
sock = sockets[fd]
if event & (select.POLLHUP | select.POLLERR | select.POLLNVAL):
address = addresses.pop(sock)
rb = bytes_received.pop(sock, b'')
sb = bytes_to_send.pop(sock, b'')
if rb:
print('Client {} sent {} but then closed'.format(address, rb))
elif sb:
print('Client {} closed before we sent {}'.format(address, sb))
else:
print('Client {} closed socket normally'.format(address))
poll_object.unregister(fd)
del sockets[fd]
elif sock is listener:
sock, address = sock.accept()
print('Accepted connection from {}'.format(address))
sock.setblocking(False)
sockets[sock.fileno()] = sock
addresses[sock] = address
poll_object.register(sock, select.POLLIN)
elif event & select.POLLIN:
more_data = sock.recv(4096)
if not more_data:
sock.close()
continue
data = bytes_received.pop(sock, b'') + more_data
if data.endswith(b'?'):
bytes_to_send[sock] = zen_utils.get_answer(data)
poll_object.modify(sock, select.POLLOUT)
else:
bytes_received[sock] = data
elif event & select.POLLOUT:
data = bytes_to_send.pop(sock)
n = sock.send(data)
if n < len(data):
bytes_to_send[sock] = data[n:]
else:
poll_object.modify(sock, select.POLLIN)
if __name__ == '__main__':
address = zen_utils.parse_command_line('low-level async server')
listener = zen_utils.create_srv_socket(address)
serve(listener)
Windows操作系统好像不支持异步操作,所以为了测试这个代码你可能需要在Linux操作系统上进行。这个服务器实际上有两层循环。首先是一个不断调用poll()的while循环。一次poll()调用可能返回多个事件,因此这个while()循环内部还有一个循环,用于处理poll()返回的每一个事件,下面是poll()函数一些参数的意义。
回调风格的asyncio
下面的代码使用读取问题,然后做出响应的流程。asyncio维护了一个select风格的核心循环,将所有进行 I/O 操作的套接字保存在了一个表中,有需要时会在select循环里向表中添加或删除套接字。一旦套接字关闭,asyncio就会将其清除或丢弃。最后,当接收到实际数据时,将由用户代码来决定要返回的正确响应。下面的代码使用了asyncio框架:
#!/usr/bin/python
#coding:utf-8
import asyncio,zen_utils
class ZenServer(asyncio.Protocol):
def connection_made(self, transport):
self.transport = transport
self.address = transport.get_extra_info('peername')
self.data = b''
print('Accepted connection from {}'.format(self.address))
def data_received(self, data):
self.data += data
if self.data.endswith(b'?'):
answer = zen_utils.get_answer(self.data)
self.transport.write(answer)
self.data = b''
def connection_lost(self, exc):
if exc:
print('Client {} error: {}'.format(self.address,exc) )
elif self.data:
print('Client {} sent {} but then closed'.format(self.address,self.data))
else:
print('Client {} closed socket'.format(self.address))
if __name__ == '__main__':
address = zen_utils.parse_command_line('asyncio server using callbacks')
loop = asyncio.get_event_loop()
coro = loop.create_server(ZenServer,*address)
server = loop.run_until_complete(coro)
print('Listening at {}'.format(address))
try:
loop.run_forever()
finally:
server.close()
loop.close()
代码将真正的套接字隐藏了起来,通过该框架来获取远程地址,而不是通过套接字。数据是通过一个方法调用来传输的。该方法只需要将接收到的字符串作为参数即可。回答数据被传给了框架的transport.write()方法,并没有在主事件循环中。
协程风格的asyncio
协程是一个函数,它在进行I/O操作时不会阻塞,而是会暂停,并将控制权转移回调用方。Python语言支持协程的一种标准形式就是生成器——在内部包含一个或多个yiled语句的函数。这类函数不会在运行一条返回语句后就退出,而是会返回一个序列。下面是一个协程风格的服务器:
#!/usr/bin/python
#coding:utf-8
import asyncio,zen_utils
@asyncio.coroutine
def handle_conversation(reader,writer):
address = writer.get_extra_info('peername')
print('Accepted connection from {}'.format(address))
while True:
data = b''
while not data.endswith(b'?'):
more_data = yield from reader.read(4096)
if not more_data:
if data:
print('Client {} sent {!r} but then closed'.format(address,data))
else:
print('Client {} closed socket normally'.format(address))
return
data += more_data
answer = zen_utils.get_answer(data)
writer.write(answer)
if __name__ == '__main__':
address = zen_utils.parse_command_line('asyncio server using coroutine')
loop = asyncio.get_event_loop()
coro = asyncio.start_server(handle_conversation,*address)
server = loop.run_until_complete(coro)
print('Linstening at {}'.format(address))
try:
loop.run_forever()
finally:
server.close()
loop.close()
此代码的while循环采用了之前的封帧方式,不断调用recv()然后将响应写入并发送给等待的客户端。所有操作都封装在while()循环之中。yield from代替了之前所有进行阻塞操作并等待操作系统响应的地方,这样使得系统不会造成阻塞,也不会同时处理多个客户端连接。
PS:学习这些内容学习一定的操作系统知识,实例代码都需要结合第一个代码使用,测试时请将他们放在同一个文件夹下。