如何用python和web.py搭建一个网站
1 环境搭建。
环境搭建比较繁琐,记得当时也是满世界找资料,所以我直接打包好了所有的文件(apache(已经放进去python-wscgi) + webpy+apache所需要的vs2010运行库+python+网站文件),直接解压放上去就能用。有需要同学让我传一个或者网盘发一把就行。
如果在windows,最好在win7以上版本,我用win8。linux则ubuntu较好,建议1204以上版本,不过linux我仅仅是开发用过,最终运行的服务器是采用windows+apache:
安装python2710,可以去官方网站下载,注意要把python路径加入系统环境变量。版本不能低于279,但不能用30以上,webpy支持不好。
安装webpy, 官方网下载来装就行,记得是解压后进去:python setuppy install
安装wingIDE,这个是最好用的python编辑器,可惜只能有几天试用,我就是不停的试用进行开发,用它的好处就是代码可以跳转并且支持调试,需要调试直接将程序文件run起来,单步下段点都可以。
装apache并配置python-wscgi,这个比较惨,花了好多时间去搜索才搞定,主要是windows上的python-wscgi不好难找。
为了节省以后的开发时间,我把配置好的apache给打包了,反正也是绿色的,新建网站只需要在服务器上配置开机启动apache,并在apache的config里面修改下自己网站路径就行。
2 开发。
开发阶段倒是没什么好说的,webpy官方有教程,不过这里我需要提几点建议:
如果可以务必全站用utf-8编码。
建立数据库建议写个生成脚本,比如createDataBasepy,有改动重新运行一遍,不要试用ide去建。
有人说表示只学Python没有用,必须学会一个框架(比如Django和webpy)才能找到工作。
其实掌握一个类似于框架的高级工具是有用的,但是基础的东西可以让你永远不被淘汰,不要被工具限制了自己的发展。
今天不使用框架,也不使用Python标准库中的高级包,只使用标准库中的socket接口写一个Python服务器。
框架与底层
在当今Python服务器框架 (framework, 比如Django, Twisted, webpy等等) 横行的时代,从底层的socket开始写服务器似乎是一个出力不讨好的笨方法。
框架的意义在于掩盖底层的细节,提供一套对于开发人员更加友好的API,并处理诸如MVC的布局问题。
框架允许我们快速的构建一个成型而且成熟的Python服务器。然而,框架本身也是依赖于底层(比如socket)。对于底层socket的了解,不仅可以帮助我们更好的使用框架,更可以让我们明白框架是如何设计的。
更进一步,如果拥有良好的底层socket编程知识和其他系统编程知识,你完全可以设计并开发一款自己的框架。
如果你可以从底层socket开始,实现一个完整的Python服务器,支持用户层的协议,并处理好诸如MVC(Model-View-Control)、多线程(threading)等问题,并整理出一套清晰的函数或者类,作为接口(API)呈现给用户,你就相当于设计了一个框架。
socket接口是实际上是操作系统提供的系统调用。
socket的使用并不局限于Python语言,你可以用C或者Java来写出同样的socket服务器,而所有语言使用socket的方式都类似(Apache就是使用C实现的服务器)。
但是你不能跨语言的使用框架。
框架的好处在于帮你处理了一些细节,从而实现快速开发,但同时受到Python本身性能的限制。
我们已经看到,许多成功的网站都是利用动态语言(比如Python, Ruby或者PHP,比如twitter和facebook)快速开发,在网站成功之后,将代码转换成诸如C和JAVA这样一些效率比较高的语言,从而让服务器能更有效率的面对每天亿万次的请求。
在这种情况下,底层的重要性,就远远超过了框架。
TCP/IP和socket简介
回到我们的任务。
我们需要对网络传输,特别是TCP/IP协议和socket有一定的了解。
socket是进程间通信的一种方法,它是基于网络传输协议的上层接口。
socket有许多种类型,比如基于TCP协议或者UDP协议(两种网络传输协议),其中又以TCP socket最为常用。
TCP socket与双向管道(duplex PIPE)有些类似,一个进程向socket的一端写入或读取文本流,而另一个进程可以从socket的另一端读取或写入,比较特别是,这两个建立socket通信的进程可以分别属于两台不同的计算机。
TCP协议,就是规定了一些通信的守则,以便在网络环境下能够有效实现上述进程间通信过程。
双向管道(duplex PIPE)存活于同一台电脑中,所以不必区分两个进程的所在计算机的地址,而socket必须包含有地址信息,以便实现网络通信。
一个socket包含四个地址信息: 两台计算机的IP地址和两个进程所使用的端口(port)。IP地址用于定位计算机,而port用于定位进程 (一台计算机上可以有多个进程分别使用不同的端口)。
TCP socket
在互联网上,让某台计算机作为服务器。
服务器开放自己的端口,被动等待其他计算机连接。
当其他计算机作为客户,主动使用socket连接到服务器的时候,服务器就开始为客户提供服务。
在Python中,我们使用标准库中的socket包来进行底层的socket编程。
首先是服务器端,我们使用bind()方法来赋予socket以固定的地址和端口,并使用listen()方法来被动的监听该端口。
当有客户尝试用connect()方法连接的时候,服务器使用accept()接受连接,从而建立一个连接的socket:
socketsocket()创建一个socket对象,并说明socket使用的是IPv4(AF_INET,IP version 4)和TCP协议(SOCK_STREAM)。
然后用另一台电脑作为客户,我们主动使用connect()方法来搜索服务器端的IP地址(在Linux中,你可以用$ifconfig来查询自己的IP地址)和端口,以便客户可以找到服务器,并建立连接:
在上面的例子中,我们对socket的两端都可以调用recv()方法来接收信息,调用sendall()方法来发送信息。
这样,我们就可以在分处于两台计算机的两个进程间进行通信了。
当通信结束的时候,我们使用close()方法来关闭socket连接。
(如果没有两台计算机做实验,也可以将客户端IP想要connect的IP改为"127001",这是个特殊的IP地址,用来连接当地主机。)
基于TCP socket的HTTP服务器
上面的例子中,我们已经可以使用TCP socket来为两台远程计算机建立连接。
然而,socket传输自由度太高,从而带来很多安全和兼容的问题。
我们往往利用一些应用层的协议(比如HTTP协议)来规定socket使用规则,以及所传输信息的格式。
HTTP协议利用请求-回应(request-response)的方式来使用TCP socket。
客户端向服务器发一段文本作为request,服务器端在接收到request之后,向客户端发送一段文本作为response。
在完成了这样一次request-response交易之后,TCP socket被废弃。
下次的request将建立新的socket。
request和response本质上说是两个文本,只是HTTP协议对这两个文本都有一定的格式要求。
Request <——> Response
现在,我们写出一个HTTP服务器端:
HTTP服务器程序的解释
如我们上面所看到的,服务器会根据request向客户传输的两条信息text_content和pic_content中的一条,作为response文本。
整个response分为起始行(start line), 头信息(head)和主体(body)三部分。起始行就是第一行:
它实际上又由空格分为三个片段,HTTP/1x表示所使用的HTTP版本,200表示状态(status code),200是HTTP协议规定的,表示服务器正常接收并处理请求,OK是供人来阅读的status code。
头信息跟随起始行,它和主体之间有一个空行。
这里的text_content或者pic_content都只有一行的头信息,text_content用来表示主体信息的类型为html文本:
而pic_content的头信息(Content-Type: image/jpg)说明主体的类型为jpg(image/jpg)。
主体信息为html或者jpg文件的内容。
(注意,对于jpg文件,我们使用"rb"模式打开,是为了与windows兼容。因为在windows下,jpg被认为是二进制(binary)文件,在UNIX系统下,则不需要区分文本文件和二进制文件。)
我们并没有写客户端程序,后面我们会用浏览器作为客户端。
request由客户端程序发给服务器。
尽管request也可以像response那样分为三部分,request的格式与response的格式并不相同。
request由客户发送给服务器,比如下面是一个request:
起始行可以分为三部分,第一部分为请求方法(request method),第二部分是URL,第三部分为HTTP版本。
request method可以有GET, PUT, POST, DELETE, HEAD。最常用的为GET和POST。
GET是请求服务器发送资源给客户,POST是请求服务器接收客户送来的数据。
当我们打开一个网页时,我们通常是使用GET方法;当我们填写表格并提交时,我们通常使用POST方法。
第二部分为URL,它通常指向一个资源(服务器上的资源或者其它地方的资源)。像现在这样,就是指向当前服务器的当前目录的testjpg。
按照HTTP协议的规定,服务器需要根据请求执行一定的操作。
正如我们在服务器程序中看到的,我们的Python程序先检查了request的方法,随后根据URL的不同,来生成不同的response(text_content或者pic_content)。
随后,这个response被发送回给客户端。
使用浏览器实验
为了配合上面的服务器程序,我已经在放置Python程序的文件夹里,保存了一个testjpg文件。
我们在终端运行上面的Python程序,作为服务器端,再打开一个浏览器作为客户端。
(如果有时间,你也完全可以用Python写一个客户端。原理与上面的TCP socket的客户端程序相类似。)
在浏览器的地址栏输入:
(当然,你也可以用令一台电脑,并输入服务器的IP地址)
OK,我已经有了一个用Python实现的,并从socket写起的服务器了。
从终端,我们可以看到,浏览器实际上发出了两个请求。
第一个请求为 (关键信息在起始行,这一个请求的主体为空):
我们的Python程序根据这个请求,发送给服务器text_content的内容。
浏览器接收到text_content之后,发现正文的html文本中有<IMG src="textjpg" />,知道需要获得textjpg文件来补充为,立即发出了第二个请求:
我们的Python程序分析过起始行之后,发现/testjpg符合if条件,所以将pic_content发送给客户。
最后,浏览器根据html语言的语法,将html文本和图画以适当的方式显示出来。
探索的方向
1) 在我们上面的服务器程序中,我们用while循环来让服务器一直工作下去。
实际上,我们还可以根据多线程的知识,将while循环中的内容改为多进程或者多线程工作。
2) 我们的服务器程序还不完善,我们还可以让我们的Python程序调用Python的其他功能,以实现更复杂的功能。比如说制作一个时间服务器,让服务器向客户返回日期和时间。你还可以使用Python自带的数据库,来实现一个完整的LAMP服务器。
3) socket包是比较底层的包。Python标准库中还有高层的包,比如SocketServer,SimpleHTTPServer,CGIHTTPServer,cgi。这些都包都是在帮助我们更容易的使用socket。如果你已经了解了socket,那么这些包就很容易明白了。利用这些高层的包,你可以写一个相当成熟的服务器。
4) 在经历了所有的辛苦和麻烦之后,你可能发现,框架是那么的方便,所以决定去使用框架。或者,你已经有了参与到框架开发的热情。
1、说明:windows下设置python环境变量,就是把python的安装目录添加到系统path中。2、步骤:1)确定python安装目录,根据版本不同安装目录也不同,可以在开始菜单中的快捷方式中查看。在python快捷方式上点右键,属性菜单
2)在目录中可以看到安装位置,C:\Program Files\Python35\,如下图:
3)在桌面计算机点右键属性,也可以在控制面板中选系统
4)点高级系统设置:
5)高级标签,点环境变量按钮:
6)在系统变量中找到Path然后点编辑:
7)在变量值末尾添加;C:\Program Files\Python35\,就是你python安装的目录,注意如果原来末尾没有分号要添加一个分号。然后点确定,再把之前的对话框也确定。
8)这样环境变量就设置完成了,win+r打开运行对话框输入cmd打开命令行,在命令行中输入python,出现如下就说明设置成功了。
3、注意事项:如果未出现python结果,则需要检查路径是否设置正确,并重新启动一下计算机即可。
使用pip或easy_install可以管理和安装python的package包,实际上它们都是从pypi服务器中搜索和下载package的。目前在pypi服务器上,有超过三万多个package,同时还允许我们将自己的代码也上传发布到服务器上。这样,世界上的所有人都能使用pip或easy_install来下载使用我们的代码了。
具体步骤如下:
首先创建项目文件和setup文件。
目录文件结构如下:
project/
simpletest/
__init__py
testpy
setuppy
假设项目文件只有一个simpletest包,里面有一个testpy文件。
创建的setuppy文件格式大致如下,其中,install_requires字段可以列出依赖的包信息,用户使用pip或easy_install安装时会自动下载依赖的包。详细的格式参考文档。
from setuptools import setup, find_packages
setup(
name = 'simpletest',
version = '001',
keywords = ('simple', 'test'),
description = 'just a simple test',
license = 'MIT License',
install_requires = ['simplejson>=11'],
author = 'yjx',
author_email = 'not@allcom',
packages = find_packages(),
platforms = 'any',
)
然后将代码打包。
打包只需要执行python
setuppy xxx命令即可,其中xxx是打包格式的选项,如下:
# 以下所有生成文件将在当前路径下 dist 目录中
python setuppy bdist_egg # 生成easy_install支持的格式
python setuppy sdist # 生成pip支持的格式,下文以此为例
发布到pypi。
发布到pypi首先需要注册一个账号,然后进行如下两步:
注册package。输入python setuppy register。
上传文件。输入python setuppy sdist upload。
安装测试
上传成功后,就可以使用pip来下载安装了。
另外,pypi还有一个测试服务器,可以在这个测试服务器上做测试,测试的时候需要给命令指定额外的"-r"或"-i"选项,如python
setuppy register -r "",python
setuppy sdist upload -r "",pip
install -i "" simpletest。
发布到测试服务器的时候,建议在linux或cygwin中发布,如果是在windows中,参考文档,需要生成pypirc文件
socket服务器再细分可分为多种了,tcp,udp,websocket,都是调用socket模块,但是具体实现起来有一点细微的差别
先给出一个tcp和udp通过socket协议实现的聊天室的例子
python聊天室(python27版本):
都是分别运行serverpy和clientpy,就可以进行通讯了。
TCP版本:
socket-tcp-serverpy(服务端):
#-- encoding:utf-8 --
#socketgetaddrinfo(host, port, family=0, socktype=0, proto=0, flags=0)
#根据给定的参数host/port,相应的转换成一个包含用于创建socket对象的五元组,
#参数host为域名,以字符串形式给出代表一个IPV4/IPV6地址或者None
#参数port如果字符串形式就代表一个服务名,比如“http”"ftp""email"等,或者为数字,或者为None
#参数family为地主族,可以为AF_INET ,AF_INET6 ,AF_UNIX
#参数socktype可以为SOCK_STREAM(TCP)或者SOCK_DGRAM(UDP)
#参数proto通常为0可以直接忽略
#参数flags为AI_的组合,比如AI_NUMERICHOST,它会影响函数的返回值
#附注:给参数host,port传递None时建立在C基础,通过传递NULL。
#该函数返回一个五元组(family, socktype, proto, canonname, sockaddr),同时第五个参数sockaddr也是一个二元组(address, port)
#更多的方法及链接请访问
# Echo server program
from socket import
import sys
import threading
from time import ctime
from time import localtime
import traceback
import time
import subprocess
reload(sys)
syssetdefaultencoding("utf8")
HOST='127001'
PORT=8555 #设置侦听端口
BUFSIZ=1024
class TcpServer():
def __init__(self):
selfADDR=(HOST, PORT)
try:
selfsock=socket(AF_INET, SOCK_STREAM)
print '%d is open' % PORT
selfsockbind(selfADDR)
selfsocklisten(5)
#设置退出条件
selfSTOP_CHAT=False
# 所有监听的客户端
selfclients = {}
selfthrs = {}
selfstops = []
except Exception,e:
print "%d is down" % PORT
return False
def IsOpen(ip, port):
s = socket(AF_INET, SOCK_STREAM)
try:
sconnect((ip, int(port)))
# sshutdown(2)
# 利用shutdown()函数使socket双向数据传输变为单向数据传输。shutdown()需要一个单独的参数,
# 该参数表示s了如何关闭socket。具体为:0表示禁止将来读;1表示禁止将来写;2表示禁止将来读和写。
print '%d is open' % port
return True
except:
print '%d is down' % port
return False
def listen_client(self):
while not selfSTOP_CHAT:
print(u'等待接入,侦听端口:%d' % (PORT))
selftcpClientSock, selfaddr=selfsockaccept()
print(u'接受连接,客户端地址:',selfaddr)
address = selfaddr
#将建立的client socket链接放到列表selfclients中
selfclients[address] = selftcpClientSock
#分别将每个建立的链接放入进程中,接收且分发消息
selfthrs[address] = threadingThread(target=selfreadmsg, args=[address])
selfthrs[address]start()
timesleep(05)
def readmsg(self,address):
#如果地址不存在,则返回False
if address not in selfclients:
return False
#得到发送消息的client socket
client = selfclients[address]
while True:
try:
#获取到消息内容data
data=clientrecv(BUFSIZ)
except:
print(e)
selfclose_client(address)
break
if not data:
break
#python3使用bytes,所以要进行编码
#s='%s发送给我的信息是:[%s] %s' %(addr[0],ctime(), datadecode('utf8'))
#对日期进行一下格式化
ISOTIMEFORMAT='%Y-%m-%d %X'
stime=timestrftime(ISOTIMEFORMAT, localtime())
s=u'%s发送给我的信息是:%s' %(str(address),datadecode('utf8'))
#将获得的消息分发给链接中的client socket
for k in selfclients:
selfclients[k]send(sencode('utf8'))
selfclients[k]sendall('sendall:'+sencode('utf8'))
print str(k)
print([stime], ':', datadecode('utf8'))
#如果输入quit(忽略大小写),则程序退出
STOP_CHAT=(datadecode('utf8')upper()=="QUIT")
if STOP_CHAT:
print "quit"
selfclose_client(address)
print "already quit"
break
def close_client(self,address):
try:
client = selfclientspop(address)
selfstopsappend(address)
clientclose()
for k in selfclients:
selfclients[k]send(str(address) + u"已经离开了")
except:
pass
print(str(address)+u'已经退出')
if __name__ == '__main__':
tserver = TcpServer()
tserverlisten_client()
——————————华丽的分割线——————————
socket-tcp-clientpy (客户端):
#-- encoding:utf-8 --
from socket import
import sys
import threading
import time
reload(sys)
syssetdefaultencoding("utf8")
#测试,连接本机
HOST='127001'
#设置侦听端口
PORT=8555
BUFSIZ=1024
class TcpClient:
ADDR=(HOST, PORT)
def __init__(self):
selfHOST = HOST
selfPORT = PORT
selfBUFSIZ = BUFSIZ
#创建socket连接
selfclient = socket(AF_INET, SOCK_STREAM)
selfclientconnect(selfADDR)
#起一个线程,监听接收的信息
selftrecv = threadingThread(target=selfrecvmsg)
selftrecvstart()
def sendmsg(self):
#循环发送聊天消息,如果socket连接存在则一直循环,发送quit时关闭链接
while selfclientconnect_ex(selfADDR):
data=raw_input('>:')
if not data:
break
selfclientsend(dataencode('utf8'))
print(u'发送信息到%s:%s' %(selfHOST,data))
if dataupper()=="QUIT":
selfclientclose()
print u"已关闭"
break
def recvmsg(self):
#接收消息,如果链接一直存在,则持续监听接收消息
try:
while selfclientconnect_ex(selfADDR):
data=selfclientrecv(selfBUFSIZ)
print(u'从%s收到信息:%s' %(selfHOST,datadecode('utf8')))
except Exception,e:
print str(e)
if __name__ == '__main__':
client=TcpClient()
clientsendmsg()
UDP版本:
socket-udp-serverpy
# -- coding:utf8 --
import sys
import time
import traceback
import threading
reload(sys)
syssetdefaultencoding('utf-8')
import socket
import traceback
HOST = "127001"
PORT = 9555
CHECK_PERIOD = 20
CHECK_TIMEOUT = 15
class UdpServer(object):
def __init__(self):
selfclients = []
selfbeats = {}
selfADDR = (HOST,PORT)
try:
selfsock = socketsocket(socketAF_INET, socketSOCK_DGRAM)
selfsockbind(selfADDR) # 绑定同一个域名下的所有机器
selfbeattrs = threadingThread(target=selfcheckheartbeat)
selfbeattrsstart()
except Exception,e:
tracebackprint_exc()
return False
def listen_client(self):
while True:
timesleep(05)
print "hohohohohoo"
try:
recvData,address = selfsockrecvfrom(2048)
if not recvData:
selfclose_client(address)
break
if address in selfclients:
senddata = u"%s发送给我的信息是:%s" %(str(address),recvDatadecode('utf8'))
if recvDataupper() == "QUIT":
selfclose_client(address)
if recvData == "HEARTBEAT":
selfheartbeat(address)
continue
else:
selfclientsappend(address)
senddata = u"%s发送给我的信息是:%s" %(str(address),u'进入了聊天室')
for c in selfclients:
try:
selfsocksendto(senddata,c)
except Exception,e:
print str(e)
selfclose_client(c)
except Exception,e:
# tracebackprint_exc()
print str(e)
pass
def heartbeat(self,address):
selfbeats[address] = timetime()
def checkheartbeat(self):
while True:
print "checkheartbeat"
print selfbeats
try:
for c in selfclients:
print timetime()
print selfbeats[c]
if selfbeats[c] + CHECK_TIMEOUT <timetime():
print u"%s心跳超时,连接已经断开" %str(c)
selfclose_client(c)
else:
print u"checkp%s,没有断开" %str(c)
except Exception,e:
tracebackprint_exc()
print str(e)
pass
timesleep(CHECK_PERIOD)
def close_client(self,address):
try:
if address in selfclients:
selfclientsremove(address)
if selfbeatshas_key(address):
del selfbeats[address]
print selfclients
for c in selfclients:
selfsocksendto(u'%s已经离开了' % str(address),c)
print(str(address)+u'已经退出')
except Exception,e:
print str(e)
raise
if __name__ == "__main__":
udpServer = UdpServer()
udpServerlisten_client()
——————————华丽的分割线——————————
socket-udp-clientpy:
# -- coding:utf8 --
import sys
import threading
import time
reload(sys)
syssetdefaultencoding('utf-8')
import socket
HOST = "127001"
PORT = 9555
#BEAT_PORT = 43278
BEAT_PERIOD = 5
class UdpClient(object):
def __init__(self):
selfclientsock = socketsocket(socketAF_INET,socketSOCK_DGRAM)
selfHOST = HOST
selfADDR = (HOST,PORT)
selfclientsocksendto(u'请求建立链接',selfADDR)
selfrecvtrs = threadingThread(target=selfrecvmsg)
selfrecvtrsstart()
selfhearttrs = threadingThread(target=selfheartbeat)
selfhearttrsstart()
def sendmsg(self):
while True:
data = raw_input(">:")
if not data:
break
selfclientsocksendto(dataencode('utf-8'),selfADDR)
if dataupper() == 'QUIT':
selfclientsockclose()
break
def heartbeat(self):
while True:
selfclientsocksendto('HEARTBEAT',selfADDR)
timesleep(BEAT_PERIOD)
def recvmsg(self):
while True:
recvData,addr = selfclientsockrecvfrom(1024)
if not recvData:
break
print(u'从%s收到信息:%s' %(selfHOST,recvDatadecode('utf8')))
if __name__ == "__main__":
udpClient = UdpClient()
udpClientsendmsg()
0条评论