2020-12-3 22:37:48

UDP编程

TCP是建立可靠连接,并且通信双方都可以以流的形式发送数据。相对TCP,UDP则是面向无连接的协议。

使用UDP协议时,不需要建立连接,只需要知道对方的IP地址和端口号,就可以直接发数据包。但是,能不能到达就不知道了。

虽然用UDP传输数据不可靠,但它的优点是和TCP比,速度快,对于不要求可靠到达的数据,就可以使用UDP协议。

我们来看看如何通过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('Hello, %s!' % data, addr)

recvfrom() 方法返回数据和客户端的地址与端口,这样,服务器收到数据后,直接调用 sendto() 就可以把数据用UDP发给客户端。

注意这里省掉了多线程,因为这个例子很简单。

客户端使用UDP时,首先仍然创建基于UDP的Socket,然后,不需要调用 connect() ,直接通过 sendto() 给服务器发数据:

s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
for data in ['Michael', 'Tracy', 'Sarah']:
    # 发送数据:
    s.sendto(data, ('127.0.0.1', 9999))
    # 接收数据:
    print s.recv(1024)
s.close()

从服务器接收数据仍然调用 recv() 方法。

仍然用两个命令行分别启动服务器和客户端测试,结果如下:

UDP的使用与TCP类似,但是不需要建立连接。此外,服务器绑定UDP端口和TCP端口互不冲突,也就是说,UDP的9999端口与TCP的9999端口可以各自绑定。

本书作者源码参考:https://github.com/michaelliao/learn­python/tree/master/socket

电子邮件

Email的历史比Web还要久远,直到现在,Email也是互联网上应用非常广泛的服务。

几乎所有的编程语言都支持发送和接收电子邮件,但是,先等等,在我们开始编写代码之前,有必要搞清楚电子邮件是如何在互联网上运作的。

我们来看看传统邮件是如何运作的。假设你现在在北京,要给一个香港的朋友发一封信,怎么做呢?

首先你得写好信,装进信封,写上地址,贴上邮票,然后就近找个邮局,把信仍进去。

信件会从就近的小邮局转运到大邮局,再从大邮局往别的城市发,比如先发到天津,再走海运到达香港,也可能走京九线到香港,但是你不用关心具体路线,你只需要知道一件事,就是信件走得很慢,至少要几天时间。

信件到达香港的某个邮局,也不会直接送到朋友的家里,因为邮局的叔叔是很聪明的,他怕你的朋友不在家,一趟一趟地白跑,所以,信件会投递到你的朋友的邮箱里,邮箱可能在公寓的一层,或者家门口,直到你的朋友回家的时候检查邮箱,发现信件后,就可以取到邮件了。

电子邮件的流程基本上也是按上面的方式运作的,只不过速度不是按天算,而是按秒算。

现在我们回到电子邮件,假设我们自己的电子邮件地址是 me@163.com ,对方的电子邮件地址是 friend@sina.com (注意地址都是虚构的哈),现在我们用 Outlook 或者 Foxmail 之类的软件写好邮件,填上对方的Email地址,点“发送”,电子邮件就发出去了。这些电子邮件软件被称为MUA:Mail User Agent——邮件用户代理。

Email从MUA发出去,不是直接到达对方电脑,而是发到MTA:Mail Transfer Agent——邮件传输代理,就是那些Email服务提供商,比如网易、新浪等等。由于我们自己的电子邮件是 163.com ,所以,Email首先被投递到网易提供的MTA,再由网易的MTA发到对方服务商,也就是新浪的MTA。这个过程中间可能还会经过别的MTA,但是我们不关心具体路线,我们只关心速度。

Email到达新浪的MTA后,由于对方使用的是 @sina.com 的邮箱,因此,新浪的MTA会把Email投递到邮件的最终目的地MDA:Mail Delivery Agent——邮件投递代理。Email到达MDA后,就静静地躺在新浪的某个服务器上,存放在某个文件或特殊的数据库里,我们将这个长期保存邮件的地方称之为电子邮箱。

同普通邮件类似,Email不会直接到达对方的电脑,因为对方电脑不一定开机,开机也不一定联网。对方要取到邮件,必须通过MUA从MDA上把邮件取到自己的电脑上。

所以,一封电子邮件的旅程就是:

发件人 -> MUA -> MTA -> MTA -> 若干个MTA -> MDA <- MUA <- 收件人

有了上述基本概念,要编写程序来发送和接收邮件,本质上就是:

 

  1. 编写MUA把邮件发到MTA;
  2. 编写MUA从MDA上收邮件。

发邮件时,MUA和MTA使用的协议就是SMTP:Simple Mail Transfer Protocol,后面的MTA到另一个MTA也是用SMTP协议。

收邮件时,MUA和MDA使用的协议有两种:POP:Post Office Protocol,目前版本是3,俗称POP3;IMAP:Internet Message Access Protocol,目前版本是4,优点是不但能取邮件,还可以直接操作MDA上存储的邮件,比如从收件箱移到垃圾箱,等等。

邮件客户端软件在发邮件时,会让你先配置SMTP服务器,也就是你要发到哪个MTA上。假设你正在使用163的邮箱,你就不能直接发到新浪的MTA上,因为它只服务新浪的用户,所以,你得填163提供的SMTP服务器地址: smtp.163.com ,为了证明你是163的用户,SMTP服务器还要求你填写邮箱地址和邮箱口令,这样,MUA才能正常地把Email通过SMTP协议发送到MTA。

类似的,从MDA收邮件时,MDA服务器也要求验证你的邮箱口令,确保不会有人冒充你收取你的邮件,所以,Outlook之类的邮件客户端会要求你填写POP3或IMAP服务器地址、邮箱地址和口令,这样,MUA才能顺利地通过POP或IMAP协议从MDA取到邮件。

在使用Python收发邮件前,请先准备好至少两个电子邮件,如 xxx@163.com , xxx@sina.com , xxx@qq.com 等,注意两个邮箱不要用同一家邮件服务商。

SMTP发送邮件

SMTP是发送邮件的协议,Python内置对SMTP的支持,可以发送纯文本邮件、HTML邮件以及带附件的邮件。

Python对SMTP支持有 smtplib 和 email 两个模块, email 负责构造邮件, smtplib 负责发送邮件。

首先,我们来构造一个最简单的纯文本邮件:

from email.mime.text import MIMEText
msg = MIMEText('hello, send by Python...', 'plain', 'utf-8')

注意到构造 MIMEText 对象时,第一个参数就是邮件正文,第二个参数是MIME的subtype,传入 ‘plain’ ,最终的MIME就是 ‘text/plain’ ,最后一定要用 utf‐8 编码保证多语言兼容性。IME就是 ‘text/plain’ ,最后一定要用 utf‐8 编码保证多语言兼容性。

然后,通过SMTP发出去:

# 输入Email地址和口令:
from_addr = raw_input('From: ')
password = raw_input('Password: ')
# 输入SMTP服务器地址:
smtp_server = raw_input('SMTP server: ')
# 输入收件人地址:
to_addr = raw_input('To: ')

import smtplib
server = smtplib.SMTP(smtp_server, 25) # SMTP协议默认端口是25
server.set_debuglevel(1)
server.login(from_addr, password)
server.sendmail(from_addr, [to_addr], msg.as_string())
server.quit()
我们用 set_debuglevel(1) 就可以打印出和SMTP服务器交互的所有信息。SMTP协议就是简单的文本命令和响 应。 login() 方法用来登录SMTP服务器, sendmail() 方法就是发邮件,由于可以一次发给多个人,所以传入一 个 list ,邮件正文是一个 str , as_string() 把 MIMEText 对象变成 str 。

如果一切顺利,就可以在收件人信箱中收到我们刚发送的Email:

参考:QQ邮箱的POP3与SMTP服务器是什么?_QQ邮箱帮助中心

参考:关于发邮件报错535 Error:authentication failed解决方法_化风的专栏-CSDN博客

参考:SMTP 服务地址_SMTP 参考_邮件推送 – 阿里云

参考:smtplib.SMTPServerDisconnected: Connection unexpectedly closed解决方法_骑着蜗牛去散步的博客-CSDN博客

QQ邮箱 POP3 和 SMTP 服务器地址设置如下:

邮箱	POP3服务器(端口995)	SMTP服务器(端口465或587)
qq.com	pop.qq.com	smtp.qq.com

oxg****fhec


SMTP 服务地址(华东 1): smtpdm.aliyun.com

SMTP 服务地址(新加坡):smtpdm-ap-southeast-1.aliyun.com

SMTP 服务地址(悉尼):smtpdm-ap-southeast-2.aliyun.com

SMTP端口号: 25,80,465(SSL加密)

注意:ECS 基于安全考虑,目前已禁用 25 端口。
如果您的发送程序部署在阿里云 ECS 上,建议您不勾选 SSL 时,使用 80 端口;勾选 SSL 时,使用 465 端口。云虚拟主机服务器同上。

wanglei@zowneo.com
0001*******~1

1548274293@qq.com
oxgp******hfhec
# _*_ coding: gbk _*_
from email.mime.text import MIMEText
msg = MIMEText('hello, send by Python...', 'plain', 'utf-8')

# 输入Email地址和口令:
from_addr = raw_input('From: ')
password = raw_input('Password: ')
# 输入SMTP服务器地址:
smtp_server = raw_input('SMTP server: ')
# 输入收件人地址:
to_addr = raw_input('To: ')

import smtplib
server = smtplib.SMTP(smtp_server,587) # SMTP协议默认端口是25
server.set_debuglevel(1)
server.login(from_addr, password)
server.sendmail(from_addr, [to_addr], msg.as_string())
server.quit()
C:\Users\Administrator\Desktop>python smtp_send_email.py
From: 1548274293@qq.com
Password: oxgp****hec
SMTP server: smtp.qq.com
To: wanglei@zowneo.com
send: 'ehlo [10.0.2.15]\r\n'
reply: '250-newxmesmtplogicsvrsza7.qq.com\r\n'
reply: '250-PIPELINING\r\n'
reply: '250-SIZE 73400320\r\n'
reply: '250-STARTTLS\r\n'
reply: '250-AUTH LOGIN PLAIN\r\n'
reply: '250-AUTH=LOGIN\r\n'
reply: '250-MAILCOMPRESS\r\n'
reply: '250 8BITMIME\r\n'
reply: retcode (250); Msg: newxmesmtplogicsvrsza7.qq.com
PIPELINING
SIZE 73400320
STARTTLS
AUTH LOGIN PLAIN
AUTH=LOGIN
MAILCOMPRESS
8BITMIME
send: 'AUTH PLAIN ADE1NDgyNzQyOTNAcXEuY29tAG94Z3BsdXBmendmaGZoZWM=\r\n'
reply: '235 Authentication successful\r\n'
reply: retcode (235); Msg: Authentication successful
send: 'mail FROM:<1548274293@qq.com> size=128\r\n'
reply: '250 OK.\r\n'
reply: retcode (250); Msg: OK.
send: 'rcpt TO:<wanglei@zowneo.com>\r\n'
reply: '250 OK\r\n'
reply: retcode (250); Msg: OK
send: 'data\r\n'
reply: '354 End data with <CR><LF>.<CR><LF>.\r\n'
reply: retcode (354); Msg: End data with <CR><LF>.<CR><LF>.
data: (354, 'End data with <CR><LF>.<CR><LF>.')
send: 'Content-Type: text/plain; charset="utf-8"\r\nMIME-Version: 1.0\r\nContent
-Transfer-Encoding: base64\r\n\r\naGVsbG8sIHNlbmQgYnkgUHl0aG9uLi4u\r\n.\r\n'
reply: '250 OK: queued as.\r\n'
reply: retcode (250); Msg: OK: queued as.
data: (250, 'OK: queued as.')
send: 'quit\r\n'
reply: '221 Bye.\r\n'
reply: retcode (221); Msg: Bye.

C:\Users\Administrator\Desktop>

wanglei@zowneo.com  收件箱  https://qiye.aliyun.com/alimail/auth/login

仔细观察,发现如下问题:

  1. 邮件没有主题;
  2. 收件人的名字没有显示为友好的名字,比如 Mr Green <green@example.com> ;

  3. 明明收到了邮件,却提示不在收件人中。

这是因为邮件主题、如何显示发件人、收件人等信息并不是通过SMTP协议发给MTA,而是包含在发给MTA的文本中的,所以,我们必须把 From 、 To 和 Subject 添加到 MIMEText 中,才是一封完整的邮件:

# _*_ coding: gbk _*_

from email import encoders
from email.header import Header
from email.mime.text import MIMEText
from email.utils import parseaddr, formataddr
import smtplib

def _format_addr(s):
    name, addr = parseaddr(s)
    return formataddr(( \
        Header(name, 'utf-8').encode(), \
        addr.encode('utf-8') if isinstance(addr, unicode) else addr))

from_addr = raw_input('From: ')
password = raw_input('Password: ')
to_addr = raw_input('To: ')
smtp_server = raw_input('SMTP server: ')

msg = MIMEText('hello, send by Python...', 'plain', 'utf-8')
msg['From'] = _format_addr(u'Python爱好者 <%s>' % from_addr)
msg['To'] = _format_addr(u'管理员 <%s>' % to_addr)
msg['Subject'] = Header(u'来自SMTP的问候……', 'utf-8').encode()

server = smtplib.SMTP(smtp_server, 587)
server.set_debuglevel(1)
server.login(from_addr, password)
server.sendmail(from_addr, [to_addr], msg.as_string())
server.quit()
我们编写了一个函数 _format_addr() 来格式化一个邮件地址。注意不能简单地传入 name <addr@example.com> ,因为如果包含中文,需要通过 Header 对象进行编码。
msg['To'] 接收的是字符串而不是list,如果有多个邮件地址,用 , 分隔即可。
再发送一遍邮件,就可以在收件人邮箱中看到正确的标题、发件人和收件人:

你看到的收件人的名字很可能不是我们传入的 管理员 ,因为很多邮件服务商在显示邮件时,会把收件人名字自动替换为用户注册的名字,但是其他收件人名字的显示不受影响。

如果我们查看Email的原始内容,可以看到如下经过编码的邮件头:

From: =?utf-8?b?UHl0aG9u54ix5aW96ICF?= <xxxxxx@163.com>
To: =?utf-8?b?566h55CG5ZGY?= <xxxxxx@qq.com>
Subject: =?utf-8?b?5p2l6IeqU01UUOeahOmXruWAmeKApuKApg==?=

这就是经过 Header 对象编码的文本,包含utf­8编码信息和Base64编码的文本。如果我们自己来手动构造这样的编码文本,显然比较复杂。

作者 wanglei

发表评论