「Python 基础」网络编程、电子邮件
创始人
2024-06-02 13:21:34

文章目录

  • 1. 网络编程
    • TCP/IP
    • TCP 编程
      • 服务端
      • 客户端
    • UDP 编程
      • 服务端
      • 客户端
  • 2. 电子邮件
    • SMTP 发送邮件
    • POP3 收取邮件

1. 网络编程

网络通信就是两个进程之间的通信;

TCP/IP

  • IP 地址 计算机的网络接口,通常是网卡,可有多个,是 32 位整数(IPv4),IPv6 是 128 位整数;

  • IP 协议 负责把数据从一台计算机通过网络发送到另一台计算机,数据被分割成小块,IP 包特点是速度快,途径多个路由,不保证到达,也不保证顺序;

  • TCP 协议 在 IP 协议基础上,负责在两台计算机上建立起可靠连接,保证数据包顺序到达。对每个 IP 包编号,顺序发收,失败的自动重发;

  • TCP 报文 传输的数据,源 IP、目标 IP、源端口号、目标端口号;

HTTP 协议、SMTP 协议都建立在 TCP 协议基础上;

一个进程可能与多个计算机建立连接,因此可能申请很多个端口;

TCP 编程

传输控制协议;

  • Socket,通常是表示打开一个网络链接,需要知道目标计算机的 IP 地址、端口号,还有指定协议类型;

服务端

一个服务端 Socket 依赖 4 项确定唯一:服务器地址,服务器端口,客户端地址,客户端端口;

服务端接收的每个连接需要一个新进程/线程来处理,否则服务器一次只能服务一个客户端;

import socket, threading, time
# 创建一个 Socket
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定地址和端口
# 0.0.0.0 是广播地址,集所有网络地址
# 127.0.0.1 表示本机地址
s.bind(('localhost', 6666))
# 开始监听,5 是最大连接数
s.listen(5)
print('Waiting for connection...')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':breakprint(f"receive, {data.decode('utf-8')}")sock.send(('hello, %s.' % data.decode('utf-8')).encode('utf-8'))sock.close()print('connection from %s:%s closed.' % addr)# 通过永久循环接收客户端连接
while True:# 接收并返回一个客户端连接sock, addr = s.accept()# 构造一个线程处理这个连接t = threading.Thread(target=tcplink, args=(sock, addr))# 启动线程t.start()

客户端

import socket
# AF_INET 表示 IPv4
# SOCK_STREAM 表示 TCP 协议
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# HTTP 协议规定客户端必须先发起请求,由服务端接收后再发送数据给客户端
# 端口 1024 以内为标准端口,如 SMTP 25,FTP 21
s.connect(('localhost', 6666))
# 接收指定长度的字节数据
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()

TCP 协议进行 Socket 编程,客户端需要主动连接服务器 IP 和端口,服务端需要先监听指定端口,通常服务器程序会无限运行下去,同一个端口 Socket 绑定后,就不能被另一个 Socket 绑定(同协议类型);

UDP 编程

用户数据报协议;

相对 TCP 的可靠连接,UDP 是面向无连接的协议,UDP 协议知道对方 IP 和端口就能发送数据包,但不能保证送达,速度快;

服务端

import sockets = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# UDP 不需要listen(),直接接收
s.bind(('localhost', 6666))print('bind udp on 6666...')while True:# 返回数据和客户端IP、端口data, addr = s.recvfrom(1024)print('Received from %s:%s.' % addr)# 向客户端回发s.sendto(b'Hello, %s.' % data, addr)

客户端

import sockets = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)for data in [b'Michael', b'Tracy', b'Sarah']:# 不需要 connect()s.sendto(data, ('localhost', 6666))print(s.recv(1024).decode('utf-8'))
s.close()

服务器绑定相同的 UDP 端口和 TCP 端口不冲突

2. 电子邮件

发送邮件过程如下:

发件人 -> MUA -> MTA -> 若干 MTA -> MDA <- MUA <- 收件人
  • MUA Mail User Agent,邮件用户代理

  • MTA Mail Transfer Agent,邮件传输代理

  • MDA Mail Delivery Agent,邮件投递代理

  • 发送 编写邮件用 MUA 发到 MTA

  • 收件 编写 MUA 从 MDA 收取邮件

  • SMTP Simple Mail Transfer Protocol,负责 MUA -> MTA, MTA -> MTA

  • POP3 Post Office Protocol v3,负责 MUA -> MDA

  • IMAP Internet Message Access Protocol,负责 MUA -> MDA,可收取邮件和操作 MDA 上的邮件

使用邮件客户端需:

  • 发送,配置 SMTP 服务器
  • 收件,配置 POP3 或 IMAP 服务器

SMTP 发送邮件

email 模块用于构造邮件,smtplib 用于发送邮件;

from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.base import MIMEBase
from email import encoders
from email.header import Header
from email.utils import parseaddr, formataddr
import smtplibdef _format_addr(s):name, addr = parseaddr(s)return formataddr((Header(name, 'utf-8').encode(), addr))# 输入Email地址和口令:
from_addr = 'aurelius-shu@outlook.com'
# QQ 邮箱密码需换成短信授权码
password = input('password: ')
# 输入收件人地址:
to_addr = 'aurelius-shu@qq.com'
# 输入SMTP服务器地址:
smtp_server = 'smtp.office365.com'# 邮件对象:
msg = MIMEMultipart('alternative')
msg['From'] = _format_addr('发件人 <%s>' % from_addr)
# 多个时以逗号分隔
msg['To'] = _format_addr('收件人 <%s>' % to_addr)
msg['Subject'] = Header('标题', 'utf-8').encode()# msg = MIMEText(
#     '

Hello

' + # '

send by Python...

' + # '', 'html', 'utf-8') msg.attach(MIMEText('hello, send by Python...', 'plain', 'utf-8')) # 邮件正文是MIMEText: # msg.attach(MIMEText('send with file...', 'plain', 'utf-8')) msg.attach(MIMEText('

Hello

','html', 'utf-8'))# 添加附件就是加上一个MIMEBase,从本地读取一个图片: with open(r'D:\Users\Aurelius\Pictures\threefish.jpg', 'rb') as f:# 设置附件的MIME和文件名,这里是png类型:mime = MIMEBase('image', 'png', filename='threefish.jpg')# 加上必要的头信息:mime.add_header('Content-Disposition','attachment',filename='threefish.jpg')mime.add_header('Content-ID', '<0>')mime.add_header('X-Attachment-Id', '0')# 把附件的内容读进来:mime.set_payload(f.read())# 用Base64编码:encoders.encode_base64(mime)# 添加到MIMEMultipart:msg.attach(mime)# SMTP协议默认端口是 25 # SMTP 安全连接端口 587 server = smtplib.SMTP(smtp_server, 587) # set_debuglevel(1) 可以打印 SMTP 服务器交互信息 server.set_debuglevel(1) # 表示自己需要身份验证 server.ehlo() # 创建 ssl 安全连接,SMTP encryption method STARTTLS server.starttls() server.login(from_addr, password) # [to_addr] 可以指定发送多人 server.sendmail(from_addr, [to_addr], msg.as_string()) server.quit()

发送 HTML 邮件

内容正文换成HTML文本,plain换成html

发送附件

可以看作包含若干部分的邮件,文本和若干附件用 MIMEMultipart 对象代表邮件本身,附加邮件正文(MIMEText)和附件(MIMEBase);

图片嵌入

先把图片当作附件添加,再在 HTML 通过应用 src='cid:x' 把附件图片嵌入,避免直接在 HTML 邮件链接图片地址,外部链接会被大部分邮件服务商屏蔽;

同时支持 HTML 与 Plain

指定 MIMEMultipartsubtypealternative,然后附加 HTMLPlain

加密 SMTP

先创建ssl安全连接,再使用SMTP协议发送邮件;

email.mine

构建一个邮件对象就是构建一个Message,文本对象是MIMEText对象,MIMEImage对象是图片附件,MIMEMultipart对象是组合对象,MIMEBase是任何对象;

Message
<- MIMEBase<- MIMEMultipart<- MIMENonMultipart<- MIMEMessage<- MIMEText<- MIMEImage

POP3 收取邮件

poplib模块实现了POP协议,email模块用来解析原始邮件对象;

from email.parser import Parser
from email.header import decode_header
from email.utils import parseaddr
import poplib# 输入邮件地址, 口令和POP3服务器地址:
email = 'aurelius-shu@outlook.com'
password = input('Password: ')
pop3_server = 'outlook.office365.com'# 连接到POP3服务器:
server = poplib.POP3(pop3_server)
# 可以打开或关闭调试信息:
server.set_debuglevel(1)
# 可选:打印POP3服务器的欢迎文字:
print(server.getwelcome().decode('utf-8'))# 身份认证:
server.user(email)
server.pass_(password)# stat()返回邮件数量和占用空间:
print('Messages: %s. Size: %s' % server.stat())
# list()返回所有邮件的编号:
resp, mails, octets = server.list()
# 可以查看返回的列表类似[b'1 82923', b'2 2184', ...]
print(mails)# 获取最新一封邮件, 注意索引号从1开始:
index = len(mails)
resp, lines, octets = server.retr(index)# lines存储了邮件的原始文本的每一行,
# 可以获得整个邮件的原始文本:
msg_content = b'\r\n'.join(lines).decode('utf-8')
# 稍后解析出邮件:
msg = Parser().parsestr(msg_content)# 可以根据邮件索引号直接从服务器删除邮件:
# server.dele(index)
# 关闭连接:
server.quit()# indent用于缩进显示:
def print_info(msg, indent=0):if indent == 0:for header in ['From', 'To', 'Subject']:value = msg.get(header, '')if value:if header == 'Subject':value = decode_str(value)else:hdr, addr = parseaddr(value)name = decode_str(hdr)value = u'%s <%s>' % (name, addr)print('%s%s: %s' % ('  ' * indent, header, value))if (msg.is_multipart()):parts = msg.get_payload()for n, part in enumerate(parts):print('%spart %s' % ('  ' * indent, n))print('%s--------------------' % ('  ' * indent))print_info(part, indent + 1)else:content_type = msg.get_content_type()if content_type == 'text/plain' or content_type == 'text/html':content = msg.get_payload(decode=True)charset = guess_charset(msg)if charset:content = content.decode(charset)print('%sText: %s' % ('  ' * indent, content + '...'))else:print('%sAttachment: %s' % ('  ' * indent, content_type))def decode_str(s):value, charset = decode_header(s)[0]if charset:value = value.decode(charset)return valuedef guess_charset(msg):charset = msg.get_charset()if charset is None:content_type = msg.get('Content-Type', '').lower()pos = content_type.find('charset=')if pos >= 0:charset = content_type[pos + 8:].strip()return charset

POP3 收取的 Message 对象可能是一个嵌套对象,所以解析要递归的进行;


  • 上一篇:「Python 基础」常用模块
  • 专栏:《Python 基础》

PS:欢迎各路道友阅读评论,感谢道友点赞关注收藏

相关内容

热门资讯

大学最新或2023(历届)元旦... 一、活动目的  为向我系新生展现我系全体师生最热情活泼的一面,让新老生充分交流,也为新生的大学生活增...
大学最新或2023(历届)元旦... 大学最新或2023(历届)元旦晚会活动策划一  20xx年即将过去,20xx年的钟声即将敲响,让我们...
最新或2023(历届)元旦晚会...  最新或2023(历届)元旦晚会策划书一:  20XX年即将过去,20XX年的钟声即将敲响,让我们一...
最新或2023(历届)中学法制...   最新或2023(历届)中学法制教育主题班会教案一  目的:  加强法制教育宣传,增强学生的自我保...
增强看齐意识用系列讲话武装头脑... 增强看齐意识用系列讲话武装头脑研讨会发言稿选集一  连日来,习近平总书记在党的新闻舆论工作座谈会上的...