网络通信就是两个进程之间的通信;
IP 地址
计算机的网络接口,通常是网卡,可有多个,是 32 位整数(IPv4),IPv6 是 128 位整数;
IP 协议
负责把数据从一台计算机通过网络发送到另一台计算机,数据被分割成小块,IP 包特点是速度快,途径多个路由,不保证到达,也不保证顺序;
TCP 协议
在 IP 协议基础上,负责在两台计算机上建立起可靠连接,保证数据包顺序到达。对每个 IP 包编号,顺序发收,失败的自动重发;
TCP 报文
传输的数据,源 IP、目标 IP、源端口号、目标端口号;
HTTP 协议、SMTP 协议都建立在 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':
break
print(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 绑定(同协议类型);
用户数据报协议;
相对 TCP
的可靠连接,UDP
是面向无连接的协议,UDP 协议知道对方 IP 和端口就能发送数据包,但不能保证送达,速度快;
import socket
s = 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 socket
s = 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 端口不冲突
发送邮件过程如下:
发件人 -> 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 上的邮件
使用邮件客户端需:
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 smtplib
def _format_addr(s):
name, addr = parseaddr(s)
return formataddr((Header(name, 'utf-8').encode(), addr))
# 输入Email地址和口令:
from_addr = '[email protected]'
# QQ 邮箱密码需换成短信授权码
password = input('password: ')
# 输入收件人地址:
to_addr = '[email protected]'
# 输入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
指定 MIMEMultipart
的 subtype
为 alternative,然后附加
HTML
和 Plain
;
加密 SMTP
先创建ssl
安全连接,再使用SMTP
协议发送邮件;
email.mine
构建一个邮件对象就是构建一个Message
,文本对象是MIMEText
对象,MIMEImage
对象是图片附件,MIMEMultipart
对象是组合对象,MIMEBase
是任何对象;
Message
<- MIMEBase
<- MIMEMultipart
<- MIMENonMultipart
<- MIMEMessage
<- MIMEText
<- MIMEImage
poplib
模块实现了POP
协议,email
模块用来解析原始邮件对象;
from email.parser import Parser
from email.header import decode_header
from email.utils import parseaddr
import poplib
# 输入邮件地址, 口令和POP3服务器地址:
email = '[email protected]'
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 value
def 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
对象可能是一个嵌套对象,所以解析要递归的进行;
PS:欢迎各路道友阅读
与评论
,感谢道友点赞
、关注
、收藏
!