nick 发布的文章

Python网络编程五:WSGI接口与网关协议

以前写PHP的时候,对于网关协议的理解只有CGI:外部应用程序(CGI程序)与Web服务器之间的接口标准,而对于python,wsgi是python的专属服务器网关接口。
引用校友“松鼠奥利奥”的一段看法:

"我对 CGI 的理解是利用程序的标准输入输出流,完成 HTTP 通信。HTTP 是文本协议,每次请求的文本以标准输入流的形式进入服务器端 CGI 程序,创建进程;然后进程的标准输出流作为响应 。
WSGI 和 CGI 原理极其相似,但是是完全不同的实现。WSGI 是 Python 专用的协议,也是输入&输出的方式传输文本流,但不是创建进程,而是对一个 WSGI 程序(callable 的对象,可以是函数也可以是实现了 __call__ 的对象),将 request 作为参数传入(不再是纯文本,而是经过包装),同样将经过包装的 response 作为响应返回。request/response 的包装由 Python 标准库提供。
二者都是标准,都有诸多实现。CGI 最为广泛,无论是二进制程序、perl 脚本、python 脚本还是 PHP 都可以以这样的原理提供 HTTP 服务;WSGI 在 Python 界也是公共标准,主流 Web 框架基本都有实现,例如 Bottle 的 bottle.Bottle (Application 对象)就实现了 WSGI 协议(见 __call__ 方法)。"

WSGI区分为两个部份:一为“服务器”或“网关”,另一为“应用程序”或“应用框架”。在处理一个WSGI请求时,服务器会为应用程序提供环境资讯及一个回呼函数(Callback Function)。当应用程序完成处理请求后,透过前述的回呼函数,将结果回传给服务器。
所谓的 WSGI 中间件同时实现了API的两方,因此可以在WSGI服务和WSGI应用之间起调解作用:从WSGI服务器的角度来说,中间件扮演应用程序,而从应用程序的角度来说,中间件扮演服务器。

我们通常使用的web框架如:flask django 就是一个web 应用程序,借助wsgi接口,完成与web服务器(nginx)的协调联系。
理解了这一点,自己开发web框架有有路可寻,一个链接:web框架开发
当然,在python里,还有一些服务器网关接口,如FastCGI之类,可以google一下。

理解了Wsgi,开发web将会有一个大致的理解。
上代码:

#coding:utf-8

from wsgiref.simple_server import make_server

# web app
def app(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/plain')])
yield "Hello world!\n"

# web server
httpd = make_server('', 8000, app)
print "Serving HTTP on port 8000..."
# 开始监听HTTP请求:
httpd.serve_forever()

Python网络编程四:HTTP服务器

前面我介绍了一些服务器的基本知识,这一节我们要来了解学习web开发要了解的Web服务器。
比较好的web服务器像NGINX,能处理静态文件,多并发,反向代理,是一个高效的软件,用c语言开发的。
然而,我们在这里介绍的只是一个简单的Http服务器。
科普http:

超文本传输协议(英文:HyperText Transfer Protocol,缩写:HTTP)是互联网上应用最为广泛的一种网络协议。设计HTTP最初的目的是为了提供一种发布和接收HTML页面的方法。通过HTTP或者HTTPS协议请求的资源由统一资源标识符(Uniform Resource Identifiers,URI)来标识。

我们做服务器这边,所以要注意返回信息的输出格式:

HTTP/1.1 200 OK
Content-Length: 3059
Server: GWS/2.0
Date: Sat, 11 Jan 2003 02:44:04 GMT
Content-Type: text/html
Cache-control: private
Set-Cookie: PREF=ID=73d4aef52e57bae9:TM=1042253044:LM=1042253044:S=SMCc_HRPCQiqy
X9j; expires=Sun, 17-Jan-2038 19:14:07 GMT; path=/; domain=.google.com
Connection: keep-alive

以下,呈上两段学习时候阅读到的代码,一个来自Vamei,一个来自the5fire,两位的博客都是很好的学习资源。
我们先来看看简单基于socket的代码:

# Written by Vamei

import socket

# Address
HOST = ''
PORT = 8000

# Prepare HTTP response
text_content = '''HTTP/1.x 200 OK
Content-Type: text/html

WOW

Wow, Python Server


'''

# Read picture, put into HTTP format
f = open('test.png','rb')
pic_content = '''
HTTP/1.x 200 OK
Content-Type: image/png

'''
pic_content = pic_content + f.read()
f.close()

# Configure socket
s = socket.ocket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((HOST, PORT))

# infinite loop, server forever
while True:
# 3: maximum number of requests waiting
s.listen(3)
conn, addr = s.accept()
request = conn.recv(1024)
method = request.split(' ')[0]
src = request.split(' ')[1]

# deal with GET method
if method == 'GET':
# ULR
if src == '/test.png':
content = pic_content
else: content = text_content

print 'Connected by', addr
print 'Request is:', request
conn.sendall(content)
# close connection
conn.close()

服务器端的输出:

Connected by ('127.0.0.1', 2727)
Request is: GET /test.png HTTP/1.1
Host: 127.0.0.1:8000
Connection: keep-alive
Cache-Control: max-age=0
Accept: image/webp,*/*;q=0.8
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.111 Safari/537.36
Referer: http://127.0.0.1:8000/
Accept-Encoding: gzip,deflate,sdch
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.6,fr;q=0.4,ja;q=0.2


Connected by ('127.0.0.1', 2728)
Request is: GET /favicon.ico HTTP/1.1
Host: 127.0.0.1:8000
Connection: keep-alive
Accept: */*
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.111 Safari/537.36
Accept-Encoding: gzip,deflate,sdch
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.6,fr;q=0.4,ja;q=0.2

以上代码应该不难理解,关键在于,Request的不同请求会得到不同输出,一个是文本,一个是图片。

好,接下来,我们用python的库,BaseHTTPServer,看名字就知道干嘛的了。
上代码:

#coding:utf-8
__author__ = 'the5fire'
from os import path

from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler

class Handler(BaseHTTPRequestHandler):

def do_GET(self):
self.send_response(200)
self.end_headers()
self.wfile.write(self.render('index'))
self.wfile.write('\n')
return

def render(self, name='index'):
file_name = '%s.html' % name
if path.isfile(file_name):
html = open(file_name, 'r').read()
return html
return None


if __name__ == '__main__':
server = HTTPServer(('localhost', 8181), Handler)
print 'Development server is running at http://127.0.0.1:8181/'
print 'Starting server, use to stop'
server.serve_forever()

我们只需要放上index.html就会在用户请求时,把index.html返回给客户端。

Python网络编程三:协程gevent

python的协程中要应用yield,常用于生成器:yield语句代替return返回一个值,则定义了一个生成器函数。 生成器函数使用yield语句返回一个值,然后保存当前函数整个执行状态,等待下一次调用。 在协程中使用到yield,我觉得就是利用这个特性吧。

什么是协程?

子例程的起始处是惟一的入口点,一旦退出即完成了子例程的执行,子例程的一个实例只会返回一次。 协程可以通过yield来调用其它协程。通过yield方式转移执行权的协程之间不是调用者与被调用者的关系,而是彼此对称、平等的。 协程的起始处是第一个入口点,在协程里,返回点之后是接下来的入口点。子例程的生命期遵循后进先出(最后一个被调用的子例程最先返回);相反,协程的生命期完全由他们的使用的需要决定。

含糊不清有点,看下线程和协程吧:

协程和线程一样共享堆,不共享栈,协程由程序员在协程的代码里显示调度。协程和线程的区别是:协程避免了无意义的调度,由此可以提高性能,但也因此,程序员必须自己承担调度的责任,同时,协程也失去了标准线程使用多CPU的能力。

另外直知乎上面有个答案讲了“进程、线程、协程”,参考一下:参考

好了,可以进入Gevent了。 我一开始使用Gevent只是使用“异步”,因为快! 如下代码所示:

import gevent.monkey
gevent.monkey.patch_socket()

import gevent
import requests


urls=[]
urls.append('http://myreading.sinaapp.com/yige/?offset=0')
urls.append('http://myreading.sinaapp.com/zhihu/?offset=0')
urls.append('http://myreading.sinaapp.com/meiwen/?offset=0')
urls.append('http://myreading.sinaapp.com/36kr/?offset=0')
urls.append('http://myreading.sinaapp.com/xinli/?offset=0')
urls.append('http://myreading.sinaapp.com/guokr/?offset=0')
urls.append('http://myreading.sinaapp.com/yp136/?offset=0')

def fetch(url,pid):
response = requests.get(url)
json_result = response.json()
title = json_result[0]['title']
print 'Process %s: %s' %(pid,title)

def synchronous():
for i in range(len(urls)):
fetch(urls[i],i)

def asynchronous():
threads = []
for i in range(len(urls)):
threads.append(gevent.spawn(fetch,urls[i],i))
gevent.joinall(threads)

print('Asynchronous:')
asynchronous()

print('Synchronous:')
synchronous()

关于Gevent的更多知识,以后在慢慢研究,贴个链接:Gevent指南

Python网络编程二:多线程循环Server

上一节,实现的只是简单的Socket API,从这一节开始陆续介绍python网络方面的其他东西。 今天来完善上一次的server服务器,这次我们加入多线程。 科普 thread:线程 python 里面的 thread 接口:thread

服务器通过开创建多线程,来服务多个客户端。 threadserver:

#coding=utf-8
import socket
import thread

def main():
listen_sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM,)
listen_sock.bind(('127.0.0.1', 6000))
listen_sock.listen(5)#accept()应答之前进入队列等待
#循环等待 创建子线程
while True:
conn_sock, client_addr = listen_sock.accept()
thread.start_new(serve, (conn_sock, client_addr))

def serve(conn_sock, client_addr):
print('connected from %s:%s' % client_addr)
input = conn_sock.recv(1024)
#循环等待 客户端通信
while 'done' != input.strip():
conn_sock.sendall(input)
input = conn_sock.recv(1024)
conn_sock.sendall('bye!\n')
conn_sock.close()

main()

Python网络编程一:简单socket API

python的socket模块和C语言的Berkeley套接字(socket)API一脉相称,这就不介绍了。其实其他语言如Java之类的socket操作基本类似。 先简单介绍API:(谷歌检索拿来一张图) 既然这是系列的第一篇,先上简单的socket server和client 的代码: server:

import socket
serversocket=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
serversocket.bind(('127.0.0.1',6000))
serversocket.listen(1)
clientsocket,clientaddress=serversocket.accept()
print 'Connection from',clientaddress
while 1:
data=clientsocket.recv(1024)
if not data:break
print 'Received from client:',repr(data)
print 'Echo:',repr(data)
clientsocket.send(data)
clientsocket.close()
serversocket.close()

client:

import socket
clientsocket=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
try:
clientsocket.connect(('127.0.0.1',6000))
while 1:
data=raw_input('>')
clientsocket.send(data.encode())
if not data:break
newdata=clientsocket.recv(1024)
print 'Received from server:',repr(newdata)
finally:
clientsocket.close()

说它简单,是因为只是循环服务器,也没有多线程。以后再补充!