Python进阶第六课--因特网客户端编程

1.前言

        消失了这么长时间以后,我胡汉三又回来了,这次回来是带着好东西回来的。这将近一个多月的时间没有更新,是在基础学完以后研究了《Python核心编程》第三版这本书,这本书适合有一定基础的Python学习者使用,里面涵盖的内容很多,主要分为:通用应用部分、Web开发部分、补充实验章节,一些我们耳熟能详的内容有:网络编程、数据库编程、多线程编程、GUI编程、Web框架:Django等等,从这节开始,我们将会一点一点的介绍这些东西,主要是一些代码的实例和我对这些内容的一些理解。话不多说,我们开始吧!

2.因特网客户端

        因特网客户端是什么呢?简单地来说就是传输数据的地方,数据在服务提供者和服务使用者之间传输,服务器就是生产者,提供服务,而客户端使用服务。在这里我们将介绍多个因特网协议,并创建相应的客户端程序,你会发现这些程序所代表的协议API都非常相似,因为我们要保持接口的一致性。下面就来看看要介绍的一些协议。

3.文件传输

3.1 文件传输协议

       文件传输协议(File Transfer Protocol,FTP)已经被记录在RFC中,主要用于匿名下载公共文件,也可以用于在两台计算机之间传输文件,特别是在使用Windows进行工作。

      FTP要求输入用户名和密码才能访问远程FTP服务器,但也允许没有账号的用户匿名登录,不过这需要管理员事先设置FTP服务器允许匿名登录,但是匿名用户只能使用有限的几个FTP命令。一般的FTP工作流程如下:

  1. 客户端连接远程主机上的FTP服务器。
  2. 客户端输入用户名和密码。
  3. 客户端进行各种文件传输和信息查询操作。
  4. 客户端从远程FTP服务器退出,结束传输。

     在上述的情况中也会出现一些例外,有时候由于网络两边的计算机的崩溃或者网络的问题,会导致整个传输在完成之前就中断,一般地,如果客户端超过15分钟还没有响应,FTP连接就会超时并中断。

    在底层,FTP只使用TCP而不使用UDP。客户端和服务器都使用两个套接字来通信:一个是控制和命令端口(21号端口),另一个是数据端口(20号端口)。数据端口好也有这样的情况,FTP有两种模式:主动和被动模式,只有在主动模式情况下服务器才使用数据端口,并使用20号端口。在被动模式的情况下,服务器只是告诉客户端随机的数据端口号,客户端必须主动建立数据连接。

3.2 Python中的FTP

     在Python中我们怎样编写FTP客户端呢,如下面这样的流程:

  1. 连接到服务器
  2. 登录
  3. 发出服务请求
  4. 退出

    在使用Python的FTP支持时,所需要的只是导入tfplib模块,并实例化一个ftplib.TFP类对象,从而我们有了下面这样的伪代码:

from ftplib import FTP
f=FTP('some.ftp.server')
f.login('anonymous','youremail.address')
...
...
f.quit()

    我们在Python中编写FTP客户端程序时候也需要一些高效的方法来帮助我们,来看看有那些好用的方法提供给我们:

方法 描述
login(username='anonymous',passwd=‘’,acct='') 登录FTP服务器,所有参数都可选的
pwd() 获得当前工作目录
cwd(path) 把当前工作目录设置为path所显示的内容
dir([path[,..[cb]]) 显示path目录里面的内容,可选参数cb是一个回调函数,会传递给retrlines()方法
nlst([path[,.....]]) 和dir类似,但返回一个文件名列表,而不是显示这些文件名
retrlines(cmd[,cb]) 给定FTP命令,用于下载文本文件,可选的回调函数cb用于处理文件的每一行
rename(old,new) 把远程文件old命名为new
delete(path) 删除位于path的远程文件
mkd(directory) 创建远程目录
rmd(directory) 删除远程目录
quit()

关闭连接并退出

    方法有了,下来我们就看看具体的例子,到代码中去理解这些方法。下面是客户端FTP程序实例:这个程序用于使用FTP来下载网站中的文件,其中也包含一些错误检查。

import ftplib
import os
import socket


HOST='ftp.mozilla.org'
DIRN='pub/mozilla.org/webtolls'
FILE='bugzilla-LATEST.tar.gz'


def main():
    try:
        f=ftplib.FTP(HOST)
    except (socket.error,socket.gaierror) as e:
        print 'ERROR: cannot reach "%s"' %HOST
        return
    print  '*** Connected to host "%s"' %HOST

    try:
        f.login()
    except ftplib.error_perm:
        print 'ERROR: cannot login anonymously'
        f.quit()
        return
    print '*** Logged in as "anonymous"'

    try:
        f.cwd(DIRN)
    except ftplib.error_perm:
        print  'ERROR: cannot CD to "%s"' %DIRN
        f.quit()
        return
    print '*** Changed to "%s" folder' %DIRN

    try:
        f.retrbinary('RETR %s' %FILE,open(FILE,'wb').write)
    except ftplib.error_perm:
        print 'ERROR: cannot read file "%s"' %FILE
        os.unlink(FILE)
    else:
        print '***Download "%s" to CWD' % FILE
    f.quit()


if __name__ == '__main__':
        main()

    代码很好理解,前面主要是导入一些要使用的模块,设置一些常量。主要是main函数里面的结构:创建一个FTP对象,尝试连接到FTP服务器,接着返回。如果发生任何错误就退出。接着便会尝试使用‘anonymous’登录,如果还是不行就结束。接下来一步就是转到发布目录,最后下载文件。

    我们注意到main函数最后的错误检查部分,我们向retrbinary()传递了一个回调函数,没接受到一块二进制数据的时候都会调用这个回调函数。这个函数就是创建文件的本地版本时需要用到的文件对象的write()方法。当传输结束后,Python解释器会自动关闭这个文件对象,因此不会丢失数据。

    最后是运行独立脚本的惯用方法,我们会经常见到这样的形式。

4.网络新闻

4.1 网络新闻传输协议

    用户使用网络新闻传输协议NNTP在新闻组中下载或者发表帖子,作为客户端/服务器架构的另一个例子,NNTP和FTP的操作方式相似。在FTP中,登录、传输数据和控制需要不同的端口来实现,而NNTP只使用一个标准端口119来通信。用户向服务器发送一个请求,服务器就做出相应的响应。

4.2 Python中的NNTP

  类似的,我们在这里也有和FTP一样的处理流程。在这里,我们只给出伪代码,很容易就能理解:

from nntplib import NNTP
n=NNTP('your.nntp.server')
r,c,f,l,g=n.group('comp.lang.python')
...
...
n.quit()

  当然,对比与FTP我们也有一些nntplib.NNTP类方法来供我们使用,来看看:

方法 描述
group(name) 选择一个组的名字,返回一个元组(rsp,ct,fst,lst,group),分别表示服务器响应信息、文章数量、第一个和最后一个文章的编号、组名,所有数据都是字符串。(返回的group与传进去的name应该是相同的)
xhdr(hdr,artrg[,ofile]) 返回文章范围artrg内文章hdr头的列表,或把数据输出到文件ofile中
body(id,[,ofile]) 根据id获取文章正文,id可以是消息的ID,也可以是文章编号,返回一个元组(rsp,anum,mid,data),分别表示服务器响应信息、文章编号、消息ID、文章所有行的列表,或把数据输出到文件ofile中
head(id) 与body()返回相同的元组,只是返回的行列表中值包括文章标题
article(id) 与body()返回相同的元组,只是返回的行列表中同时包括文章标题和正文
stat(id) 让文章的“指针”指向id,返回一个与body()相同的元组(rsp,anum,mid),但不包含文章的数据
next() 把文章指正移到下一篇文章,返回与stat()相似的元组
last() 把文章指正移到最后一篇文章,返回与stat()相似的元组
post(ufile) 上传ufile文件对象里面的内容,并发布到当前新闻组中
quit() 关闭连接并退出

    与FTP一样咯,我们也给出一个实际的例子来看看:下面这个程序下载并显示Python新闻组comp.lang.python中的一篇文章的前20个有意义的行。

import nntplib
import socket


HOST='your.nntp.server'
GRNM='comp.lang.python'
USER='wesley'
PASS='youllneverGuess'


def main():

    try:
        n=nntplib.NNTP(HOST)
    except socket.gaierror as e:
        print 'ERROR: cannnot reach host "%s"' %HOST
        print '("%s")' %eval(str(e))[1]
        return
    except nntplib.NNTPPermanentError as e:
        print 'ERROR: access denied on "%s"' %HOST
        print  '     ("%s")' %str(e)
        return
    print '***Connected to host "%s"' %HOST


    try:
        rsp, ct, fst, lst, grp=n.group(GRNM)
    except nntplib.NNTPTemporaryError as ee:
        print 'ERROR: cannot load group "%s"' %GRNM
        print '      ("%s")' %str(ee)
        print '       Server may require authentication'
        print '       Uncomment/edit login line above'
        n.quit()
        return
    except nntplib.NNTPTemporaryError as ee:
        print 'ERROR: group "%s" unanailable' %GRNM
        print '      (%s)' %str(ee)
        n.quit()
        return
    print '*****Found newsgroup "%s"' %GRNM

    rng='%s-%s' %(lst,lst)
    rsp,frm=n.xhdr('from',rng)
    rsp,sub=n.xhdr('subject',rng)
    rsp,dat=n.xhdr('date',rng)
    print '''***Found last article(#%s):
    
    From:%s
    Subject:%s
    Date:%s
    '''(lst,frm[0][1],sub[0][1],dat[0][1])
    rsp,anum,mid,data=n.body(lst)
    displayFirst20(data)
    n.quit()

def displayFirst20(data):
    print  '***First(<=20) meaningful lines:\n'
    count=0
    lines=(line.rstrip() for line in data)
    lastBlank=True
    for line in lines:
        if line:
            lower=line.lower()
            if (lower.startswith('>') and not \
                lower.startswith('>>>')) or \
                lower.startswith('|') or \
                lower.startswith('in article') or \
                lower.endwith('writes:') or \
                lower.endwith('wrote:'):
                    continue
        if not lastBlank or (lastBlank and line):
            print '     %s' %line
            if line:
                count+=1
                lastBlank=False
            else:
                lastBlank=True
        if count==20:
            break
if __name__=='__main__':
    main()

    和FTP的例子一样,我们首先在前面导入我们需要的一些库并定义一些常量。还是重点关注main函数里面的部分:首先尝试连接到NNTP主机服务器,如果失败就退出。注意到被注释掉的那一行,如果需要输入用户名和密码进行身份验证,就可以将这一行前面的注释去掉,接着尝试读取指定的新闻组。同样的,如果新闻组不存在或者服务器没有保存这个新闻组,或需要身份验证就退出。

    主要的处理任务被分配在displayFirst()函数来完成,该函数接受文章的一些内容,并做一些处理,如把计数器清0,创建一个生成器表达式对文章的所有行做一些处理.

   着重说一下函数中if语句的处理,表示只有在上一行不为空,或者上一行为空但当前行不为空的时候显示。也就是说,如果显示了当前行,就说明要么当前行不为空,要么当前行为空但上一行不为空。这是一个比较有技巧的地方,如果遇到一个非空行,计数器加1,并将lastBlank标志设置为False,以表示这一行非空,否则,如果遇到了空行,则把标志设置成True。

    除了上面讲述的两种协议以外,我们还与有电子邮件相关的传输协议,我们接着来看看。

5.电子邮件

5.1 电子邮件系统组件和协议

    电子邮件系统中有很多重要的组件,其中最重要的组件是消息传输代码(MTA)。这是在邮件交换主机上运行的服务器进程,它负责邮件的路由、队列处理和发送工作。MTA就是邮件从发送主机到接收主机所要经过的主机和跳板,所以也称消息传输的代理。为了发送电子邮件,邮件客户端必须要连接到一个MTA,MTA靠某种协议进行通信。MTA之间通过消息传输系统(MTS)互相通信。只有两个MTA都使用这个协议的时候,才能进行通信。

    由于以前存在很多不同的计算机系统,每个系统都使用不同的网络软,因此这种通信很危险,具有不可预知性,更为复杂的是,有的计算机使用互联的网络,而有的计算机使用调制解调器拨号,消息发送的时间也是不可预知的。这种种的复杂性导致了现代电子邮件的基础之一--简单邮件传输协议(SMTP)的诞生。

5.2 Python和SMTP

   和NNTP一样,登录是可选的,只有在服务器启用了SMTP身份验证时才需要登录。SMTP-AUTH在RFC2554中定义。还是和NNTP一样,SMTP通信时只要一个端口,这里端口是25。当然我们也需要来看一下smtplib.SMTP的类方法。

方法 描述
sendmail(from,to,msg[,mopts,ropts]) 将msg从from发送至to(以列表或者元组表示),还可以选择性地设置ESMTP邮件(mopts)和收件人(ropts)选项
ehlo()或helo() 使用EHLO或HELO初始化SMTP或者ESMTP服务器的会话,这是可选的,因为sendmail()会在需要的时候自动调用相关内容
starttls(keyfile=None,cerftile=None) 让服务器启动TLS模式,如果给定了keyfile或certfile,则它们用来创建安全套接字
set_debuglevel(level) 为服务器通信设置调试级别
quit() 关闭连接并退出
login(user,passwd) 使用用户名和密码登录SMTP服务器

6.实战:生成电子邮件

    这部分的内容主要的工作是创建电子邮件:下面的代码中有两个创建电子邮件消息的实例,make_mpa_msg()和make_img_msg(),两者都创建了一条带有附件的电子邮件消息。前者创建并发送了一条多部分信息,后者创建并发送了一条电子邮件消息,其中含有一幅图片,一起来看看:

from email.mime.image import MIMEImage
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from smtplib import SMTP


#multipart alternative: text and html
def make_mpa_msg():
    email=MIMEMultipart('alternative')
    text=MIMEText('Hello World!\r\n','plain')
    email.attach(text)
    html=MIMEText(
        '

Hello,World!

' '','html' ) return email #multipart: images def make_img_msg(fn): f=open(fn,'r') data=f.read() f.close() email=MIMEImage(data,name=fn) email.add_header('Content-Disposition','attachment; filename="%s"' %fn) return email def sendMsg(fr,to,msg): s=SMTP('localhost') errs=s.sendmail(fr,to,msg) s.quit() if __name__=='__main__': print 'Sending multipart alternative msg....' msg=make_mpa_msg() msg['From']=SENDER msg['To']=','.join(RECIPS) msg['Subject']='mutipart alternative test' sendMsg(SENDER,RECIPS,msg.as_string()) print 'Sending image msg.....' msg=make_img_msg(SOME_IMG_FILE) msg['From']=SENDER msg['To']=','.join(RECIPS) msg['Subject']='image file test' sendMsg(SENDER,RECIPS,msg.as_string())

    导入模块的时候我们除了导入标准的模块以外还导入了MIMEImage、MIMEMultipart、MIMEText、SMTP等类。

    接下来是多部分选择消息,也就是代码中加注释的#multipart: text and html部分,这部分内容通常包括两部分:一种是以纯文本表示的邮件消息正文,以及等价的HTML格式。由邮件客户端来决定显示哪一部分。为了创建这样的消息,我们要使用email.mine.multipart.MIMEMultipart类,并传递alternative作为唯一的参数来实例化这个类。如果不传递这个参数,则前面的纯文本和HTML部分会成为消息中的附件。

    然后就是make_img_msg()函数使用一个文件名作为参数,使用文件中的数据生成一个新的email.mime.image.MIMEImage实例。添加一个Content-Disposition头,接着将消息返回给用户。

    而sendMsg()的唯一目的是获取基本的电子邮件发送信息 ,接着传达消息,然后返回给调用者。和我们前面说过的一样,脚本执行的时候都需要这样的格式,mian里面主要测试两个函数,这两个函数创建消息,然后添加From、To、Subject字段,然后将消息传递给这些收件人。为了让应用能正常工作,需要填充下面的字段:SENDER、RECIPS、SOME_FILE。

你可能感兴趣的:(Python)