1.1
在用Python进行网络编程的时候,你会发现大致有两种情况:一是某些协议已经有了Python模块,二是需要自己编写实现协议。即使是已经实现的模块,理解其底层是如何实现的也很有益处。
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# Simple Gopher Clinet - Chapter 1 - gopherclient.py
import socket, sys
port = 70
host = sys.argv[1]
filename = sys.argv[2]
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s.sendall((filename + "\r\n").encode())
while True:
buf = s.recv(2048).decode()
if not len(buf):
break
sys.stdout.write(buf)
这个程序实现Gopher协议。需要两个命令行参数主机名和文件名,实现从主机上请求文档的功能。
试着运行一下,您将得到Gopher服务期根目录的文件列表。
1.2 错误和异常
熟悉C语言的人会想到这个例子根本没有错误检查。事实上,Python会自动检查错误。尝试给一个不存在的主机名,例如:
root@iZ2ze0lvzs7172jj1i9vmnZ:~# ./gopherclient.py nonexistant.example.com /
Traceback (most recent call last):
File "./gopherclient.py", line 12, in <module>
s.connect((host, port))
socket.gaierror: [Errno -2] Name or service not known
Python会检查到一个socket.gaierror异常,程序会终止并打印出错的地方和错误。您可以稍微修改一下程序,让它更友好一些:
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# Simple Gopher Clinet - Chapter 1 - gopherclient.py
import socket, sys
port = 70
host = sys.argv[1]
filename = sys.argv[2]
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
try:
s.connect((host, port))
except socket.gaierror as e:
print("Error connecting to server:%s"%e)
sys.exit(-1)
s.sendall((filename + "\r\n").encode())
while True:
buf = s.recv(2048).decode()
if not len(buf):
break
sys.stdout.write(buf)
现在如果试图连接一个不存在的服务器,程序将终止,您将得到一个友好的错误信息。
sendall()函数,如果有错误,会产生异常;否则,表明信息发送成功。
1.3 文件类对象
Python程序员会熟悉文件对象的方法:readline()、write()、read()等。Python库支持文件和文件类对象。Socket对象则不提供类似的接口。然而Python的确提供了一个makefile()函数来生成供您使用的文件类对象。请看下面这个用文件类接口重写的程序:
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# Simple Gopher Clinet - Chapter 1 - gopherclient.py
import socket, sys
port = 70
host = sys.argv[1]
filename = sys.argv[2]
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
fd = s.makefile("rwb", 0)
fd.write(filename.encode("utf-8")+"\r\n".encode("utf-8"))
for line in fd.readlines():
sys.stdout.write(line.decode())
makefile()函数有两个参数:操作文件类的模式和缓存的模式。操作文件类的模式表明是只读、只写、还是读写;。缓存主要用在磁盘文件,但是对于交互式的网络程序,它可能会阻碍程序的运行,所以最好通过设置为0来关闭它。
既然得到了文件类对象,就可使用熟悉的方法了,这里用了两个:write()和readlines()。他们的功能和一般的文件对象是一样的。
1.4 基本服务器操作
用Python编写服务器代码与客户端一样简单
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# Simple Server ---- Chapter 1 - server.py
import socket
host = '' # Bind to all interfaces
port = 51423
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)#建立一个socket
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)#SO_REUSEADDR把socket设置成可复用的
s.bind((host, port))#绑定一个端口,主机设置成空字符串,这样程序可以接受来自任意地方的连接
s.listen(1)#调用listen函数,开始等候来自客户端的连接,同时设定每次最多只有一个等候处理的连接
print("Server is running on port %d;press Ctrl-C to terminate." % port)
while True:
clientsock, clientaddr = s.accept()#主循环从accept开始。accept返回两个信息:一个新的连接客户端的socket和客户端的IP地址、端口号
clientfile = clientsock.makefile('rwb', 0)#
clientfile.write(("Welcome, " + str(clientaddr) +"\n").encode())
clientfile.write(("Please enter a string: ").encode())
line = clientfile.readline().strip()#从客户端读一个字符串,显示一个应答,
clientfile.write(("You entered %d characters.\n" % len(line)).encode())
clientfile.close()
clientsock.close()#关闭客户端socket
运行这个程序,然后使用telnet命令如下:
$ telnet localhost 51423
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Welcome, ('127.0.0.1', 61937)
Please enter a string: fdsf
You entered 4 characters.
Connection closed by foreign host.
您会发现,我根本没有编写Telnet协议,但是Telnet客户端也能通信。
1.5 高级接口
Python提供了很多协议模块,在开始编程的时候,有一半的工作已经完成了。
#!/usr/bin/env python3
# -*- coding:utf-8 -*-
# Simple Gopher Clinet - Chapter 1 - gopherclient.py
import gopherlib, sys
host = sys.argv[1]
file = sys.argv[2]
f = gopherlib.send_selector(file, host)
for line in f.readlines():
sys.stdout.write(line)
gopherlib模块负责建立socket和连接,这节省了一些工作量。
Python中还有更高级的模块。为了处理URL,Python提供的模块可以让您的代码和几种协议一起工作。
#!/usr/bin/env python3
# High-Level Gopher Client with urllib - Chapter 1 - urlclient.py
import urllib, sys
host = sys.argv[1]
file = sys.argv[2]
f = urllib.urlopen('gopher://%s%s'%(host, file))
for line in f.readlines():
sys.stdout.write(line)