图形界面
python支持多种图形界面的第三方库:
Tk/wxWidgets/Qt/GTK
python自带的库是支持Tk的tkinter
使用tkinter进行GUI编程
Tkinter
编写python代码会调用内置的Tkinter,Tkinter等装了访问Tk的接口
tk是一个图形库,支持多个操作系统,使用tcl语言开发
tk会调用操作系统提供的本地gui接口,完成最终的gui
代码只需要调用Tkinter提供的接口就可以了
#导入tkinter包的所有内容
form tkinter import *
#从Frame派生一个Application类,是所有widge的父容器
class Application(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.pack()
self.createWidgets()
def createWidgets(self):
self.helloLabel = Label(self, text='Hello, world!')
self.helloLabel.pack()
self.quitButton = Button(self, text='Quit', command=self.quit)
self.quitButton.pack()
在GUI中,每个Button,Label,输入框等,都是一个Widget。
Frame则是可以容纳其他Widget的Widget,所有的widget组合起来就是一棵树
pack()方法把widget加入到父容器中,并实现布局
pack()是最简答的布局,grid()可以实现更复杂的布局
在createWidgets()方法中,创建一个Label和一个button,当button被点击时,除法self.quit()使程序退出
实例化Application,并启动消息循环:
app = Application()
# 设置窗口标题:
app.master.title('Hello World')
# 主消息循环:
app.mainloop()
gui程序的主线程负责监听来自操作系统的消息,并依次处理每条消息。因此,如果消息处理非常耗时,就需要在新线程中处理。
运行gui程序:
输入文本
加入一个文本框,让用户可以输入文本,然后点按钮后,弹出消息对话框
from tkinter import *
import tkinter.messagebox as messagebox
class Application(Frame):
def __init__(self, master=None):
Frame.__init__(self, master)
self.pack()
self.createWidgets()
def createWidgets(self):
self.nameInput = Entry(self)
self.nameInput.pack()
self.alertButton = Button(self, text='Hello', command=self.hello)
self.alertButton.pack()
def hello(self):
name = self.nameInput.get() or 'world'
messagebox.showinfo('Message', 'Hello, %s' % name)
app = Application()
# 设置窗口标题:
app.master.title('Hello World')
# 主消息循环:
app.mainloop()
当用户点击按钮时,触发hello(),通过self.nameInput.get()获得用户输入的文本后,使用tkMessageBox.showinfo()可以弹出消息对话框
python内置的Tkinter可以满足基本的GUI程序的要求,如果非常复杂的GUI程序,用操作系统原生支持的语言和库来编写
海龟绘图
通过编程指挥一个小海龟turtle在屏幕上绘图
海归绘图(TurtleGraphics)后来呗移植到各种高级语言中,python内置了turtle库,基本上100%复制了原始的TurtleGraphics的所有功能
绘制长方形:
# 导入turtle包的所有内容:
from turtle import *
# 设置笔刷宽度:
width(4)
# 前进:
forward(200)
# 右转90度:
right(90)
# 笔刷颜色:
pencolor('red')
forward(100)
right(90)
pencolor('green')
forward(200)
right(90)
pencolor('blue')
forward(100)
right(90)
# 调用done()使得窗口等待被关闭,否则将立刻关闭窗口:
done()
海归绘图就是指挥海龟前进,转向,海龟移动的轨迹就是绘制的线条。
要绘制一个长方形,只需要让海龟前进,右转90°,反复四次
调用width()函数可以设置笔刷宽度,调用pencolor()函数可以设置颜色。
绘图完成后,调用done()函数,让窗口进入信息循环,等待被关闭,否则,由于python进程会立刻结束,导致窗口被立刻关闭
turtle包本身只是一个绘图库,但配合python代码,就可以绘制各种复杂的图形。
绘制五个五角星
from turtle import *
def drawStar(x, y):
pu()
goto(x, y)
pd()
# set heading: 0
seth(0)
for i in range(5):
fd(40)
rt(144)
for x in range(0, 250, 50):
drawStar(x, 0)
done()
使用递归,可以绘制出非常复杂的图形。
绘制一棵分型树:
from turtle import *
# 设置色彩模式是RGB:
colormode(255)
lt(90)
lv = 14
l = 120
s = 45
width(lv)
# 初始化RGB颜色:
r = 0
g = 0
b = 0
pencolor(r, g, b)
penup()
bk(l)
pendown()
fd(l)
def draw_tree(l, level):
global r, g, b
# save the current pen width
w = width()
# narrow the pen width
width(w * 3.0 / 4.0)
# set color:
r = r + 1
g = g + 2
b = b + 3
pencolor(r % 200, g % 200, b % 200)
l = 3.0 / 4.0 * l
lt(s)
fd(l)
if level < lv:
draw_tree(l, level + 1)
bk(l)
rt(2 * s)
fd(l)
if level < lv:
draw_tree(l, level + 1)
bk(l)
lt(s)
# restore the previous pen width
width(w)
speed("fastest")
draw_tree(l, 4)
done()
网络编程
所有的程序都是网络程序,很少有单机版的程序
计算机网络就是把各个计算机连接到一起,让网络中的计算机可以互相通信。
网络编程就是如何在程序中实现两台计算机的通信。
网络通信是两台计算机上的两个进程之间的通信
网络编程对所有的开发语言都是一样的,用python进行网络编程,就是在python程序本身这个进程内,连接别的服务器进程的通信端口进行通信
TCP/IP简介
计算机为了联网,就必须规定通信协议,早期的计算机网络都是由各厂商自己规定一套协议,IBM,Apple和Microsoft都有各自的网络协议,互不兼容。
为了把全世界的所有不同类型的计算机都连接起来,就必须规定一套全球通用的协议,为了实现互联网这个目标,互联网协议簇就是通用协议标准。
互联网协议包含了上百种协议标准,但是最重要的两个协议是TCP和IP协议。
通信的时候,双方必须知道对方的标识,好比发邮件必须知道对方的邮件地址。
互联网上每个计算机的唯一标识就是IP地址。
如果一台计算机同事接入到两个或更多的网络,比如路由器,它就会有两个或多个ip地址,所以ip地址对应的实际上是计算机的网络接口,通常是网卡。
IP协议负责把数据从一台计算机通过网络发送到另一台计算机。数据被分割成一小块一小块,然后通过IP包发送出去。由于互联网链路复杂,两台计算机之间经常有多条线路,因此,路由器就负责决定如何把一个IP包转发出去。
ip包的特点是按块发送,途径多个路由,但不保证能到达,也不保证顺序到达。
ip地址实际上是一个32位整数(IP v4)以字符串表示的IP地址如192.168.0.1实际上是把32位整数按8位分组后的数字表示,目的是便于阅读。
IPv6地址实际上是一个128位整数,它是目前使用的IPv4的升级版,以字符串表示。
TCP协议则是建立在IP协议之上的。TCP协议负责在两台计算机之间建立可靠连接,保证数据包按顺序到达。
tcp协议会通过握手建立连接,然后对每个IP包编号,确保对方按顺序收到,如果包丢掉了,就自动重发
浏览器的HTTP协议,发送邮件的SMTP协议都是建立在TCP协议基础上的
一个TCP报文除了包含要传输的数据外,还包含源IP地址和目标IP地址,源端口和目标端口
端口:在两台计算机通信时,只发IP地址不够,因为同一台计算机上跑着多个网络程序。一个TCP报文来了之后需要分配给哪个网络程序,需要端口号来区分。
每个网络程序都向操作系统申请唯一的端口号,这样两个进程在两台计算机之间建立网络连接就需要各自的IP地址和各自的端口号。
一个进程也可能同事与多个计算机建立连接,因此它会申请很多端口
TCP编程
Socket是网络编程的一个抽象概念。通常用一个Socket表示“打开了一个网络连接”而打开一个Socket需要知道目标计算机的IP地址和端口号,再指定协议类型。
客户端
大多数连接都是可靠的TCP连接。
创建TCP连接时,主动发起连接的叫客户端,被动响应连接的叫服务器。
再浏览器中访问新浪时,自己的计算机就是客户端,浏览器会主动向新浪的服务器发起连接。一切顺利,新浪的服务器接受了这边的连接,一个TCP连接就建立起来的,后面的通信就是发送网页内容
创建一个基于TCP连接的Socket:
# 导入socket库:
import socket
# 创建一个socket:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 建立连接:
s.connect(('www.sina.com.cn', 80))
创建Socket时,AF_INET指定使用IPv4协议。如果要用IPv6就指定AF_INET6
SOCK_STREAM指定使用面向流的TCP协议,这样,一个Socket对象就创建成功,但是还没有建立连接。
客户端要主动发起TCP连接,必须知道服务器的IP地址和端口号。
作为服务器,提供什么样的服务,端口号就必须固定下来。
建立连接的参数是一个tuple,包含地址和端口号
建立TCP连接后,就可以向服务器发送请求,要求返回首页的内容:
# 发送数据:
s.send(b'GET / HTTP/1.1\r\nHost: www.sina.com.cn\r\nConnection: close\r\n\r\n')
TCP连接创建的是双向通道,双方都可以同时给对方发数据。但是谁先发谁后发,怎么协调,需要根据具体的协议来决定。
发送的文本格式必须符合HTTP标准,如果格式没问题,就可以接收服务器返回的数据:
# 接收数据:
buffer = []
while True:
# 每次最多接收1k字节:
d = s.recv(1024)
if d:
buffer.append(d)
else:
break
data = b''.join(buffer)
接收数据时,调用recv(max)方法,一次最多接收指定的字节数,因此,在一个while循环中反复接收,自导recv()返回空数据,表示接收完毕,退出循环。
当接收完数据后,调用close()方法关闭Socket,这样,一次完整的网络通信就结束了:
# 关闭连接:
s.close()
接收到的数据包括HTTP头和网页本身,只需要把HTTP头和网页分离一下,把HTTP头打印出来,网页内容保存到文件:
header, html = data.split(b'\r\n\r\n', 1)
print(header.decode('utf-8'))
# 把接收的数据写入文件:
with open('sina.html', 'wb') as f:
f.write(html)
在浏览器中打开.html文件就可以看到网站的首页
服务器
和客户端编程相比,服务器就要复杂一些。
服务器进程首先要绑定一个端口并监听来自其他客户端的连接。如果某个客户端连接过来,服务器就与该客户端建立Socket连接,随后的通信就靠这个Socket连接了。
所以,服务器会打开固定端口(80)监听,每来一个客户端连接,就创建该Socket连接。由于服务器会有大量来自客户端的连接,所以,服务器要能够区分一个Socket连接是和哪个客户端绑定的。一个Socket依赖4项:服务器地址,服务器端口,客户端地址,客户端端口来唯一确定一个Socket
但是服务器还需要同时响应多个客户端的请求,所以每个连接都需要一个新的进程或者新的线程来处理,否则服务器一次就只能服务一个客户端。
编写一个简单的服务器程序,接收客户端连接,把客户端发过来的字符串加上Hello再发回去
首先,创建一个基于IPv4和TCP协议的Socket:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
然后,绑定监听的地址和端口。
服务器可能有多块网卡,可以绑定到某一块网卡的IP地址上,也可以用0.0.0.0绑定到所有的网络地址,还可以用127.0.0.1绑定到本机地址。127.0.0.1是一个特殊的IP地址,表示本机地址,如果绑定到这个地址,客户端必须同时再本机运行才能连接,也就是说外部的计算机无法连接进来。
端口号需要预先指定。因为这个服务不是标准服务,所以用9999这个端口号
小于1024的端口号必须要有管理员权限才能绑定:
# 监听端口:
s.bind(('127.0.0.1', 9999))
调用listen()方法开始监听端口,传入的参数指定等在连接的最大数量:
s.listen(5)
print('Waiting for connection...')
服务器程序通过一个永久循环来接受来自客户端的连接,accept()会等待并返回一个客户端的连接:
while True:
# 接受一个新连接:
sock, addr = s.accept()
# 创建新线程来处理TCP连接:
t = threading.Thread(target=tcplink, args=(sock, addr))
t.start()
每个连接都必须创建新线程(进程)来处理,否则,单线程再处理连接的过程中,无法接受其他客户端的连接:
def tcplink(sock, addr):
print('Accept new connection from %s:%s...' % addr)
sock.send(b'Welcome!')
while True:
data = sock.recv(1024)
time.sleep(1)
if not data or data.decode('utf-8') == 'exit':
break
sock.send(('Hello, %s!' % data.decode('utf-8')).encode('utf-8'))
sock.close()
print('Connection from %s:%s closed.' % addr)
建立连接后,服务器首先发送一条欢迎消息,然后等待客户端数据,并加上Hello再发送给客户端。
如果客户端发送了exit字符串,就直接关闭连接。
测试服务器程序,需要编写一个客户端程序:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 建立连接:
s.connect(('127.0.0.1', 9999))
# 接收欢迎消息:
print(s.recv(1024).decode('utf-8'))
for data in [b'Michael', b'Tracy', b'Sarah']:
# 发送数据:
s.send(data)
print(s.recv(1024).decode('utf-8'))
s.send(b'exit')
s.close()
客户端程序运行完毕就退出了,服务器程序会永远运行下去,必须按Ctrl+c退出程序
用tcp协议进行Socket编程再python中十分简单,对于客户端,要主动连接服务器的ip和指定端口,对于服务器,要首先监听指定端口,然后对每一个新的连接,创建一个线程或进程来处理。
通常服务器程序会无限运行下去
同一个端口被一个Socket绑定之后,就不能被别的Socket绑定了
UDP编程
tcp是建立可靠连接,并且通信双方都可以以流的形式发送数据。相对TCP,udp则是面向无连接的协议。
使用udp协议时,不需要建立连接,只需要知道对方的IP地址和端口号,就可以直接发数据包。但是能不能到达不知道
udp不可靠,但是比tcp快,对于不要求可靠到达的数据,就可以使用udp协议
首先绑定端口:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 绑定端口:
s.bind(('127.0.0.1', 9999))
创建socket时,SOCK_DGRAM制定了这个socket的类型时udp。绑定端口和tcp一样,但是不需要调用listen()方法,而是直接接收来自任何客户端的数据:
print('Bind UDP on 9999...')
while True:
# 接收数据:
data, addr = s.recvfrom(1024)
print('Received from %s:%s.' % addr)
s.sendto(b'Hello, %s!' % data, addr)
recvfrom()方法返回数据和客户端的地址与端口,这样,服务器接收到数据后,直接调用sendto()就可以把数据用udp发给客户端
客户端使用udp时,首先仍然创建基于udp的socket,然后,不需要调用connect(),直接通过sendto()给服务器发数据:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
for data in [b'Michael', b'Tracy', b'Sarah']:
# 发送数据:
s.sendto(data, ('127.0.0.1', 9999))
# 接收数据:
print(s.recv(1024).decode('utf-8'))
s.close()
从服务器接收数据仍然调用recv()方法
仍然用两个命令行分别启动服务器和客户端测试:
udp的使用与tcp类似,但是不需要建立连接
服务器绑定udp端口和tcp端口互不冲突,也就是树,udp的9999端口与tcp的9999端口可以各自绑定