#encoding=utf-8 ####**************** 网络客户端编程 ******************* #本篇学习三个因特网协议——FTP, NNTP 和POP3,并写出它们的客户端程序。 #************* Part 1: 文件传输 ************************ ## 1.1 文件传输因特网协议 #因特网中最流行的事情就是文件的交换。文件交换无处不在。有很多协议可以供因特网上传输 #文件使用。最流行的有文件传输协议(FTP),Unix-to-Unix 复制协议(UUCP),以及网页的超文本传输 #协议(HTTP)。另外,还有(Unix 下的)远程文件复制指令rcp(以及更安全,更灵活的scp 和rsync)。 # #迄今为止,HTTP,FTP 和scp/rsync 还是非常流行的。HTTP 主要用于网页文件的下载和访问Web #服务上。它一般不要求用户输入登录的用户名密码就可以访问服务器上的文件和服务。HTTP 文件传 #输请求主要是用于获取网页(文件下载)。 # #相对的,scp 和rsync 要求用户登录到服务器,否则不能上传或下载文件。至于FTP,跟scp/rsync #一样,可以上传或下载文件,还采用了Unix 的多用户的概念,用户一定要输入有效的用户名和密码 #才能使用。不过,FTP 也允许匿名登录。 ## 1.2 文件传输协议(FTP) #文件传输协议由已故的Jon Postel 和Joyce Reynolds 开发,记录在RFC(Request for #Comment)959 号文档中,于1985 年10 月发布。它主要用于匿名下载公共文件。也可以用于在两台 #电脑之间传输文件,尤其是在使用Unix 系统做为文件存储系统,使用其它机器来工作的情况。早 #在网络流行之前,FTP 就是在因特网上文件传输,软件和源代码下载的主要手段之一。 # #FTP 要求输入用户名和密码才能访问远程的FTP 服务器,但它也允许没有帐号的用户以匿名用户 #登录。不过,管理员要先设置FTP 服务器允许匿名用户登录。这时,匿名用户的用户名是“anonymous”, #密码一般是用户的e-mail 地址。与特定的用户拥有特定的帐户不同,这有点像是把FTP 公开出来让 #大家访问。匿名用户通过FTP 协议可以使用的命令与一般的用户相比来说,限制更多。 #一般,在客户端超过15 分钟(900 秒)不活动之后,连接就会被关闭。 #在底层上,FTP 只使用TCP。而且,FTP 是客户端/服务器编程中很“与众不同”的例子。 #客户端和服务器都使用两个套接字来通讯:一个是控制和命令端口(21 号端口),另一个是数据端口(有时是20 号端口)。 #“有时”是因为FTP 有两种模式:主动和被动。只有在主动模式服务器才使用数据端口。 #在服务器把20 号端口设置为数据端口后,它“主动”连接客户端的数据端口。而被动模式中,服务 #器只是告诉客户端它的随机端口的号码,客户端必须主动建立数据连接。在这种模式下,你会看到, #FTP 服务器在建立数据连接时是“被动”的。最后,现在已经有了一种扩展被动模式来支持第6 版本 #的因特网协议(IPv6)地址。 ## 1.3 Python 和FTP #使用Python 的FTP 支持时,需要导入ftplib 模块,并实例化一个ftplib.FTP类对象。 #所有的FTP 操作(如登录,传输文件和登出等)都要使用这个对象来完成。Python的伪代码: # #from ftplib import FTP #f = FTP('ftp.python.org') #f.login('anonymous', 'guess@who.org') #: #f.quit() ## 1.4 ## 1.4 ftplib.FTP 类方法 #FTP 对象的方法 #方法 描述 #login(user='anonymous',passwd='', acct='') 登录到FTP 服务器,所有的参数都是可选的 #pwd() 得到当前工作目录 #cwd(path) 把当前工作目录设置为path #dir([path[,...[,cb]]) 显示path 目录里的内容,可选的参数cb 是一个回调函数,它会被传给retrlines()方法 #nlst([path[,...]) 与dir()类似,但返回一个文件名的列表,而不是显示这些文件名 #retrlines(cmd [, cb]) 给定FTP 命令(如“RETR filename”),用于下载文本文件。可选的回调函数cb 用于处理文件的每一行 #retrbinary(cmd, cb[,bs=8192[, ra]]) 与retrlines()类似,只是这个指令处理二进制文件。回调函数cb 用于处理每一块(块大小默认为8K)下载的数据。 #storlines(cmd, f) 给定FTP 命令(如“STOR filename”),以上传文本文件。要给定一个文件对象f #storbinary(cmd, f[,bs=8192]) 与storlines()类似,只是这个指令处理二进制文件。要给定一个文件对象f,上传块大小bs 默认为8Kbs=8192]) #rename(old, new) 把远程文件old 改名为new #delete(path) 删除位于path 的远程文件 #mkd(directory) 创建远程目录 #rmd(directory) 删除远程目录 #quit() 关闭连接并退出 # #在一般的FTP 通讯中,要使用到的指令有login(), cwd(), dir(), pwd(), stor*(), retr*()和quit()。 ## 1.5 FTP 连接FTP示例 #from ftplib import FTP #f=FTP('192.168.1.168') #f.login('tmp', 'tmp') #f.dir() #print('---create a directory----') #f.mkd('dave') #f.dir() #f.quit() #-->输出结果 #drw-rw-rw- 1 user group 0 Sep 19 15:38 . #drw-rw-rw- 1 user group 0 Sep 19 15:38 .. #---create a directory---- #drw-rw-rw- 1 user group 0 Sep 19 15:53 . #drw-rw-rw- 1 user group 0 Sep 19 15:53 .. #drw-rw-rw- 1 user group 0 Sep 19 15:53 dave ## 1.6 客户端FTP 程序举例:从FTP 下载文件:dave.txt #import ftplib #import os #import socket # #HOST = '192.168.1.168' #USER = 'tmp' #PWD = 'tmp' #DIRN = 'dave' #FILE = 'dave.txt' # #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(USER,PWD) # except ftplib.error_perm: # print('ERROR: cannot login anonymously') # f.quit() # return # print('*** Logged in as [%s]' % USER) # # 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('*** Downloaded "%s" to CWD' % FILE) # f.quit() # return # #if __name__ == '__main__': # main() # #-->输出日志: #*** Connected to host "192.168.1.168" #*** Logged in as [tmp] #*** Changed to "dave" folder #*** Downloaded "dave.txt" to CWD #************* Part 2: 网络新闻 ************************ ## 2.1 Usenet 与新闻组 #Usenet 新闻系统是一个全球存档的“电子公告板”。各种主题的新闻组一应俱全,从诗歌到政治, #从自然语言学到计算机语言,软件到硬件,种植到烹饪,招工,应聘,音乐,魔术,分手,求爱等。 #新闻组可以是面向全球泛泛而谈,也可以是只面向某个地理区域。 # #整个系统是一个由大量计算机组成的一个庞大的全球网络,计算机之间共享Usenet 上的帖子.如 #果某一个用户发了一个帖子到本地的Usenet 计算机上,这个帖子会被传播到其它相连的计算机上, #并再由这些计算机传到与它们相连的计算机上,直到这个帖子传播到了全世界,每个人都收到这个帖 #子为止. # #每个系统都有一个它已经“订阅”的新闻组的列表,它只接收它感兴趣的新闻组里的帖子—— #而不是服务器上所有新闻组的帖子。Usenet 新闻组服务内容取决于服务提供者,很多都是可供公众 #访问的,也有一些只允许特定的用户使用,例如付费用户,特定大学的学生等。如果Usenet 系统 #管理员设置了的话,有可能会要求输入用户名和密码。管理员也可以设置是否只允许上传或只允许 #下载。 ## 2.2 网络新闻传输协议(NNTP) #供用户在新闻组中下载或发表帖子的方法叫网络新闻传输协议(NNTP)。它由Brain Kantor(加 #利福尼亚大学圣地亚哥分校)和Phil Lapsley(加利福尼亚大学伯克利分校)创建并记录在RFC 977 #中,于1986 年2 月公布。其后的更新记录在RFC 2980,于2000 年10 月公布。 # #作为客户端/服务器架构的另一个例子,NNTP 与FTP 的操作方式很像,而且简单得多。FTP 需 #要不同的端口来做登录,数据传输和控制,而NNTP 只使用一个标准端口119 来做通讯。 ## 2.3 Python 和NNTP #协议操作步骤: #1. 连接到服务器 #2. 登录(如果需要的话) #3. 发送请求 #4. 退出 #伪代码: #from nntplib import NNTP #n = NNTP('your.nntp.server') #r,c,f,l,g = n.group('comp.lang.python') #... #n.quit() #一般来说,在你登录完成后,你要调用group()方法来选择一个感兴趣的新闻组。方法返回服务 #器的返回信息,文章的数量,第一个和最后一个文章的ID,以及组的名字。 ## 2.4 nntplib.NNTP 类方法 #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(同上,是一个消息的ID 或是文章的号码)。 # 返回一个跟body 一样的元组(rsp, anum, mid),但不包含文章的数据。 #next() 用法和stat()类似,把文章指针移到下一篇文章,返回与stat()相似的元组 #last() 用法和stat()类似,把文章指针移到最后一篇文章,返回与stat()相似的元组 #post(ufile) 上传ufile 文件对象里的内容(使用ufile.readline()),并在当前新闻组发表。 #quit() 关闭连接,然后退出 ## 2.5 交互式NNTP 示例 #from nntplib import NNTP #n = NNTP('your.nntp.server') #rsp, ct, fst, lst, grp = n.group('comp.lang.python') #rsp, anum, mid, data = n.article('110457') #for eachLine in data: # print(eachLine) #n.quit() #************* Part 3: 电子邮件 ************************ ## 3.1 E-mail 系统组件和协议 #发送电脑要查询到某一台中间主机,这台中间主机能到达最后的收件主机。然后这台中间主机 #要找一台离目的主机更近一些的主机。所以,在发送主机和目的主机之间,可能会有多台叫做“跳 #板”的主机。如果你仔细看看你收到的e-mail 的邮件头,你会看到一个“passport”标记,其中记 #录了邮件寄给你这一路上都到过了哪些地方。 # #在e-mail 系统的各个组件中。最主要的组件是消息传输代理(MTA)。 #这是一个在邮件交换主机上运行的一个服务器程序,它负责邮件的路由,队列和发送工作。 #它们就是邮件从源主机到目的主机所要经过的跳板。所以也被称为是“信息传输”的“代理”。 #要让所有这些工作起来,MTA 要知道两件事情:1) 如何找到消息应该去的下一台MTA 2) 如何 #与另一台MTA 通讯。第一件事由域名服务(DNS)来查找目的域名的MX(邮件交换Mail eXchange) #来完成。这对于最后的收件人是不必要的,但对其它的跳板来说,则是必要的。 ## 3.2 ## 3.2.1 SMTP 简单邮件传输协议 # SMTP 于1982 年出现,由已故的Jonathan Postel(加利福尼亚大学信息学院)创建,记录在RFC 821 中,于 #1982 年8 月公布。其后的修改记录在RFC 2821 中,于2001 年4 月公布。 #一些已经实现了SMTP的著名MTA 包括: # ###开源MTA # Sendmail # Postfix # Exim # qmail (免费发布,但不开源) # ###商业MTA # Microsoft Exchange # Lotus Notes Domino Mail Server #注意,虽然它们都实现了RFC 2821 中定义的最小化SMTP 协议,它们中的大多数,尤其是一些 #商业MTA,都在服务器中加入了协议定义之外的特有的功能。 # #SMTP 是在因特网上MTA 之间用于消息交换的最常用的MTS。它被MTA 用来把e-mail 从一台主机 #传送到另一台主机。在你发e-mail 的时候,你必须要连接到一个外部的SMTP 服务器,这时,你的 #邮件程序是一个SMTP 客户端。你的SMTP 服务器也因此成为了你的消息的第一个跳板。 ## 3.2.2 Python 和SMTP #Python 连接STMP 通过 smtplib 模块来实现。 #Python 的伪代码: #from smtplib import SMTP #n = SMTP('smtp.yourdomain.com') #pass #n.quit() ## 3.2.3 smtplib.SMTP 类方法 #对大多数e-mail 发送程序来说,只有两个方法是必须的:sendmail()和quit()。 #sendmail()的所有参数都要遵循RFC 2822,即e-mail 地址必须要有正确的格式,消息体要有正 #确的前导头,前导头后面是两个回车和换行(\r\n)对。 #SMTP 对象的方法 #方法 描述 #Sendmail(from, to, msg[,mopts, ropts]) 把msg 从from 发送给to(列表或元组)。ESMTP 设置(mopts)和收件人设置(ropts)为可选。 #quit() 关闭连接,然后退出 #login(user, passwd) 使用user 用户和passwd 密码登录到SMTP 服务器 ## 3.2.3 交互式SMTP 示例 #import smtplib # #def prompt(prompt): # return input(prompt).strip() # #fromaddr = prompt("From: ") #toaddrs = prompt("To: ").split() #print("Enter message, end with ^D (Unix) or ^Z (Windows):") # ## Add the From: and To: headers at the start! #msg = ("From: %s\r\nTo: %s\r\n\r\n" # % (fromaddr, ", ".join(toaddrs))) #while True: # try: # line = input() # except EOFError: # break # if not line: # break # msg = msg + line # #print("Message length is", len(msg)) # #server = smtplib.SMTP('smtp.gmail.com') #server.starttls() #启动TLS模式,Gmail要求 #server.set_debuglevel(1) #server.login('username', 'pwd') #server.sendmail(fromaddr, toaddrs, msg) #server.quit() ##-->输出日志 #From: dvd.dba@gmail.com #To: dvd.dba@gmail.com #Enter message, end with ^D (Unix) or ^Z (Windows): #david dai # #Message length is 59 # #send: 'ehlo [192.168.3.115]\r\n' #reply: b'250-mx.google.com at your service, [60.191.73.11]\r\n' #reply: b'250-SIZE 35882577\r\n' #reply: b'250-8BITMIME\r\n' #reply: b'250-AUTH LOGIN PLAIN XOAUTH\r\n' #reply: b'250 ENHANCEDSTATUSCODES\r\n' #reply: retcode (250); Msg: b'mx.google.com at your service, [60.191.73.11]\nSIZE 35882577\n8BITMIME\nAUTH LOGIN PLAIN XOAUTH\nENHANCEDSTATUSCODES' #send: 'AUTH PLAIN AGR2ZC5kYmEAJFlzYW5xYkgkMTY4\r\n' #reply: b'235 2.7.0 Accepted\r\n' #reply: retcode (235); Msg: b'2.7.0 Accepted' #send: 'mail FROM:<dvd.dba@gmail.com> size=59\r\n' #reply: b'250 2.1.0 OK n10sm15106361pbe.4\r\n' #reply: retcode (250); Msg: b'2.1.0 OK n10sm15106361pbe.4' #send: 'rcpt TO:<dvd.dba@gmail.com>\r\n' #reply: b'250 2.1.5 OK n10sm15106361pbe.4\r\n' #reply: retcode (250); Msg: b'2.1.5 OK n10sm15106361pbe.4' #send: 'data\r\n' #reply: b'354 Go ahead n10sm15106361pbe.4\r\n' #reply: retcode (354); Msg: b'Go ahead n10sm15106361pbe.4' #data: (354, b'Go ahead n10sm15106361pbe.4') #send: b'From: dvd.dba@gmail.com\r\nTo: dvd.dba@gmail.com\r\n\r\ndavid dai\r\n.\r\n' #reply: b'250 2.0.0 OK 1316596683 n10sm15106361pbe.4\r\n' #reply: retcode (250); Msg: b'2.0.0 OK 1316596683 n10sm15106361pbe.4' #data: (250, b'2.0.0 OK 1316596683 n10sm15106361pbe.4') #send: 'quit\r\n' #reply: b'221 2.0.0 closing connection n10sm15106361pbe.4\r\n' #reply: retcode (221); Msg: b'2.0.0 closing connection n10sm15106361pbe.4' ## 3.2.4 接收E-mail #邮件用户代理(MUA):MUA 从服务器上下载邮件,在这个过程中可能会自动删除它们(也可能不删除,留在服务器上,让用户手动删除)。 #不过,MUA 也必须要能发送邮件。也就是说,在发送邮件的时候,它要能直接与MTA 用SMTP 进行通讯。 ## 3.2.5 POP 和IMAP #用于下载邮件的第一个协议叫邮局协议(POP),记录在RFC 918 中,于1984 年10 月公布。“邮局协议 #(POP)的目的是让用户的工作站可以访问邮箱服务器里的邮件。邮件要能从工作站通过简单邮件传 #输协议(SMTP)发送到邮件服务器”。POP 协议的最新版本是第3 版,也叫POP3。 #在POP 之后几年,出现了另一个协议,叫交互式邮件访问协议(IMAP)。第一个版本是实验性的. #直到第2 版时,其RFC 1064 才在1988 年被公布。现在被使用的IMAP 版本是IMAP4rev1,它也被广 #泛地使用。事实上,当今世界上占有邮件服务器大多数市场的Microsoft Exchange 就使用IMAP 作 #为其下载机制。IMAP4rev1 协议定义在RFC 3501,于2003 年3 月公布。IMAP 的目的是要提供一个更 #全面的解决方案. ## 3.2.6 Python 和POP3 #导入poplib,实例化poplib.POP3 类 #Python 的伪代码如下: #from poplib import POP3 #p = POP3('pop.python.is.cool') #p.user(...) #p.pass_(...) #... #p.quit() ## 3.2.7 交互式POP3 举例 #import poplib # #M = poplib.POP3('pop.qq.com') #M.user('username') #M.pass_('pwd') #numMessages = len(M.list()[1]) #for i in range(numMessages): # for j in M.retr(i+1)[1]: # print(j) ## 3.2.7 poplib.POP3 类方法 #POP3 对象的常用方法 #方法 描述 #user(login) 发送用户名login 到服务器,并等候服务器的正在等待用户密码的返回信息 #pass_(passwd) 发送密码passwd(在使用user()登录之后使用)。如果登录失败,引发一个异常 #stat() 返回邮件的状态,一个长度为2 的元组(msg_ct, mbox_siz):消息的数量和消息的总大小也即字节数 #list([msgnum]) stat()的扩展,从服务器返回长度为3 的元组的消息列表(rsp, msg_list,rsp_siz):服务器的返回信息,消息的列表,返回信息的大小。 # 如果给了msgnum 的话,只返回指定消息的数据。 #retr(msgnum) 从服务器中得到消息msgnum,并设置其“已读”标志。 # 返回一个长度为3 的元组(rsp, msglines, msgsiz):服务器的返回信息,消息msgnum的所有行,消息的字节数 #dele(msgnum) 把消息msgnum 标记为删除,大多数服务器在调用quit()后执行删除操作。 #quit() 登出,保存修改(如,执行“已读”和“删除”标记等),解锁邮箱,结束连接,然后退出 #在登录时,user()方法不仅向服务器发送了用户名,也要等待服务器正在等待用户密码的返回信 #息。如果pass_()方法认证失败,会引发一个poplib.error_proto 的异常。 #如果成功,会得到一个以'+'号开头的返回信息,如“+OK ready”,然后服务器上的该邮箱就被锁定了,直到调用了quit()方法为止。 #调用list()方法时,msg_list 的格式为:[‘msgnum msgsiz’,…],其中,msgnum 和msgsiz分别是每个消息的编号和消息的大小。
-------------------------------------------------------------------------------------------------------
Blog: http://blog.csdn.net/tianlesoftware
Weibo: http://weibo.com/tianlesoftware
Email: dvd.dba@gmail.com
DBA1 群:62697716(满); DBA2 群:62697977(满)DBA3 群:62697850(满)
DBA 超级群:63306533(满); DBA4 群:83829929(满) DBA5群: 142216823(满)
DBA6 群:158654907(满) DBA7 群:69087192(满)DBA8 群:172855474
DBA 超级群2:151508914 DBA9群:102954821 聊天 群:40132017(满)
--加群需要在备注说明Oracle表空间和数据文件的关系,否则拒绝申请