网络编程
- 网络:
- 网络协议: 一套规则
网络模型:
七层模型-七层-理论
- 物理层
- 数据链路层
- 网络层
- 传输层
- 会话层
- 表示层
- 应用层
四层模型-实际应用
- 链路层
- 网络层
- 传输层
- 引用层
- 每一层都有相应的协议负责交换信息或者协同工作
- TCP/IP 协议族
IP地址:负责在网络上唯一定位一个机器
- IP地址分ABCDE类
- 是由四个数字段组成,每个数字段的取值是0-255
- 192.168.xxx.xxx: 局域网ip
- 127.0.0.1: 本机
- IPv4, IPv6
端口
范围: 0-65535
- 知名端口:0-1023
- 非知名端口:1024-
ICP/UDP协议
UDP: 非安全的不面向链接的传输
- 安全性差
- 大小限制64kb
- 没有顺序
- 速度快
TCP
- 基于链接的通信
SOCKET编程
- socket(套接字):是一个网络通信的端点,能实现不同主机的进程通信,网络大多基于socket通信
- 通过IP+端口定位对方并发送消息的通信机制
- 分为UDP和TCP
- 客户端Client,发起访问的一方
- 服务器端Server,接受访问的一方
UDP编程
Server端流程
- 建立socket,socket是负责具体通信的一个实例
- 绑定,为创建的socket指派固定的端口和ip地址
- 接受对方发送内容
- 给对方发送反馈,此步骤为非必须步骤
Client端流程
- 建立通信的socket
- 发送内容到指定服务器
- 接受服务器给定的反馈内容
服务器案例v01
''' Server端流程 1. 建立socket,socket是负责具体通信的一个实例 2. 绑定,为创建的socket指派固定的端口和ip地址 3. 接受对方发送内容 4. 给对方发送反馈,此步骤为非必须步骤 ''' # socket模块负责socket编程 import socket # 模拟服务器的函数 def serverFunc(): # 1. 建立socket # socket.AF_INET: 使用ipv4协议族 # socket.SOCK_DGRAM: 使用UDP通信 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 2. 绑定ip和port # 127.0.0.1: 这个ip地址代表的是机器本身 # 7852: 随机指定的端口号 # 地址是一个tuple类型,(ip, port) addr = ("127.0.0.1", 7852) sock.bind(addr) # 3. 接受对方消息 # 等待方式为死等,没有其他可能性 # recvfrom接受的返回值是一个tuple,前一项表示数据,后一项表示地址 # 参数的含义是缓冲区大小 # rst = sock.recvfrom(500) data, addr = sock.recvfrom(500) print(data) print(type(data)) # 发送过来的数据是bytes格式,必须通过解码才能得到str格式内容 # decode默认参数是utf8 text = data.decode() print(type(text)) print(text) # 给对方返回的消息 rsp = "Ich hab keine Hunge" # 发送的数据需要编码成bytes格式 # 默认是utf8 data = rsp.encode() sock.sendto(data, addr) if __name__ == '__main__': print("Starting server.........") serverFunc() print("Ending server...........")
客户端案例v02
import socket ''' Client端流程 1. 建立通信的socket 2. 发送内容到指定服务器 3. 接受服务器给定的反馈内容 ''' def clientFunc(): sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) text = "I love you" # 发送的数据必须是bytes格式 data = text.encode() # 发送 sock.sendto(data, ("127.0.0.1", 7852)) data, addr = sock.recvfrom(200) data = data.decode() print(data) if __name__ == '__main__': clientFunc()
- 服务器程序要永久运行,一般用死循环处理
改造的服务器版本v03
''' Server端流程 1. 建立socket,socket是负责具体通信的一个实例 2. 绑定,为创建的socket指派固定的端口和ip地址 3. 接受对方发送内容 4. 给对方发送反馈,此步骤为非必须步骤 ''' # socket模块负责socket编程 import socket # 模拟服务器的函数 def serverFunc(): # 1. 建立socket # socket.AF_INET: 使用ipv4协议族 # socket.SOCK_DGRAM: 使用UDP通信 sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 2. 绑定ip和port # 127.0.0.1: 这个ip地址代表的是机器本身 # 7852: 随机指定的端口号 # 地址是一个tuple类型,(ip, port) addr = ("127.0.0.1", 7852) sock.bind(addr) # 3. 接受对方消息 # 等待方式为死等,没有其他可能性 # recvfrom接受的返回值是一个tuple,前一项表示数据,后一项表示地址 # 参数的含义是缓冲区大小 # rst = sock.recvfrom(500) data, addr = sock.recvfrom(500) print(data) print(type(data)) # 发送过来的数据是bytes格式,必须通过解码才能得到str格式内容 # decode默认参数是utf8 text = data.decode() print(type(text)) print(text) # 给对方返回的消息 rsp = "Ich hab keine Hunge" # 发送的数据需要编码成bytes格式 # 默认是utf8 data = rsp.encode() sock.sendto(data, addr) if __name__ == '__main__': import time while 1: try: serverFunc() except Exception as e: print(e) time.sleep(1)
TCP编程
- 面向链接的传输,即每次传输之前需要先建立一个链接
- 客户端和服务端两个程序需要编写
Server端的编写流程
- 建立socket负责具体通信,这个socket其实只负责接受对方的请求,真正通信的是链接后重新建立的socket
- 绑定端口和地址
- 监听接入的访问socket
- 接受访问的socket,可以理解接受访问即建立了一个通讯的链接通路
- 接受对方的发送内容,利用接收到的socket接受内容
- 如果有必要,给对方发送反馈信息
- 关闭链接通路
Client端的编写流程
- 建立通信socket
- 链接对方,请求跟对方建立通路
- 发送内容到对方服务器
- 接受对方的反馈
- 关闭链接通路
案例v04
import socket def tcp_srv(): # 1. 建立socket负责具体通信,这个socket其实只负责接受对方的请求,真正通信的是链接后重新建立的socket # 需要用到两个参数 # AF_INET: 含义同UDP一致 # SOCK_STREAM: 表明使用的是TCP进行通信 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 2. 绑定端口和地址 # 此地址信息是一个元组类型内容,元组分两部分,第一部分为字符串,代表ip,第二部分为端口,是一个整数,推荐大于10000 addr = ("127.0.0.1", 8998) sock.bind(addr) # 3. 监听接入的访问socket sock.listen() while True: # 4. 接受访问的socket,可以理解为接受访问即建立了一个通讯的链接通路 # accept返回的元组第一个元素赋值给skt,第二个赋值给addr skt, addr = sock.accept() # 5. 接受对方的发送内容,利用接受到的socket接受内容 # 500代表接受使用的buffersize # msg = skt.receive(500) msg = skt.recv(500) # 接受到的是bytes格式内容 # 想得到str格式,需要进行解码 msg = msg.decode() rst = "Receive msg: {0} from {1}".format(msg, addr) print(rst) # 6. 如果有必要,给对方发送反馈信息 skt.send(rst.encode()) # 7. 关闭链接通路 skt.close() if __name__ == '__main__': print("Starting tcp server........") tcp_srv() print("Ending tcp server..........")
案例v05
import socket def tcp_clt(): # 1. 建立通信socket sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 2. 链接对方,请求跟对方建立通路 addr = ("127.0.0.1", 8998) sock.connect(addr) # 3. 发送内容到对方服务器 msg = "I love you" sock.send(msg.encode()) # 4. 接受对方的反馈 rst = sock.recv(500) print(rst.decode()) # 5. 关闭链接通路 sock.close() if __name__ == '__main__': tcp_clt()
FTP编程
- FTP(FileTransferProtoacal)文件传输协议
- 用途:定制一些特殊的上传下载文件的服务
用户分类:登录FTP服务器必须有一个账号
- Real账户:注册账户
- Guest账户:可能临时对某一类人的行为进行授权
- Anonymous账户:匿名账户,允许任何人
FIP工作流程
- 客户端链接远程主机上的FTP服务器
- 客户端输入用户名和密码(或者“anonymous”和电子邮件地址)
- 客户端和服务器进行各种文件传输和信息查询操作
- 客户端从远程FTP服务器退出,结束传输
FTP文件表示
- 分三段表示FTP服务器上的文件
- HOST:主机地址,类似于 ftp.mozilla.org, 以 ftp 开头
- DIR:目录,表示文件所在本地的路径,例如 pub/android/focus/1.1-RC1
- File:文件名称,例如 Klar-1.1-RC1.apk
- 如果想完整精确表示ftp上某一文件,需要上述三部分组合到一起
案例v06
# 需要导入相应包,主要是ftplib import ftplib # 关于FTP的操作都在这个包里边 import os import socket # 三部分精确表示在ftp服务器上的某一个文件 # 好多公开ftp服务器访问会出错或者没有反应 HOST = "ftp.acc.umu.se" DIR = 'Public/EFLIB/' FILE = 'README' # 1. 客户端链接远程主机上的FTP服务器 try: f = ftplib.FTP() # 通过设置调试级别可以方便调试 f.set_debuglevel(2) # 链接主机地址 f.connect(HOST) except Exception as e: print(e) exit() print("***Connected to host {0}".format(HOST)) # 2. 客户端输入用户名和密码(或者“anonymous”和电子邮件地址) try: # 登录如果没有输入用户信息,则默认使用匿名登录 f.login() except Exception as e: print(e) exit() print("***Logged in as 'anonymous'") # 3. 客户端和服务器进行各种文件传输和信息查询操作 try: # 更改当前目录到指定目录 f.cwd(DIR) except Exception as e: print(e) exit() print("*** Changed dir to {0}".format(DIR)) try: # 从FTP服务器上下载文件 # 第一个参数是ftp命令 # 第二个参数是回调函数 # 此函数的意思是,执行RETR命令,下载文件到本地后,运行回调函数 f.retrbinary('RETR {0}'.format(FILE), open(FILE, 'wb').write) except Exception as e: print(e) exit() # 4. 客户端从远程FTP服务器退出,结束传输 f.quit()
Mail编程
电子邮件的历史
起源
- 1969 Leonard K. 教授发给同事的 “LO”
- 1971 美国国防部自主的阿帕网(Arpanet)的通讯机制
- 通讯地址里用@
- 1987年中国的第一份电子邮件
“Across the Great Wall we can reach every corner in the world”
管理程序
- Euroda使邮件普及
- Netscape,outlook,forxmail后来居上
- Hotmail使用浏览器发送邮件
参考资料
邮件工作流程
- MUA(MailUserAgent) 邮件用户代理
- MTA(MailTransferAgent) 邮件传输代理
- MDA(MailDeliveryAgent) 邮件投递代理
- laoshi@qq.com,老师,北京海淀
- xuesheng@sina.com,学生,上海江岸区
流程
- MUA->MTA,邮件已经在服务器上了
- qq MTA->........-> sina MTA, 邮件在新浪的服务器上
- sina MTA-> sina MDA, 此时邮件已经在你的邮箱里了
- sina MDA -> MUA(Foxmail/Outlook), 邮件下载到本地电脑
编写程序
- 发送: MUA->MTA with SMTP: SimpleMailTransferProtocal, 包含MTA->MTA
- 接受: MDA->MUA with POP3 and IMAP: PostOfficeProtocal v3 and InternetMessageAccessProtocal v4
准备工作
- 注册邮箱(以qq邮箱为例)
第三方邮箱需要特殊设置,以qq邮箱为例
- 进入设置中心
- 取得授权码
Python for mail
SMTP协议负责发送邮件
使用email模块构建邮件
- 纯文本邮件
案例v07
# 导入相应的包 import smtplib from email.mime.text import MIMEText # MIMEText三个主要参数 # 1. 邮件内容 # 2. MIME子类型,在此案例我们用plain表示text类型 # 3. 邮件编码格式 msg = MIMEText("Hello, i am xxxx", "plain", "utf-8") # 发送email地址 from_addr = "1441865605@qq.com" # 此处密码是经过申请设置后的授权码 from_pwd = "ajwvzqdlfigahiae" # 授权码 # 收件人信息 # 此时使用qq邮箱 to_addr = "1441865605@qq.com" # 输入SMIP服务器地址 # 此处根据不同的邮件服务商有不同的值 # 现在基本任何一家邮件服务商,如果采用第三方收发邮件,都需要开启授权选项 # 腾讯qq邮箱所用的SMTP地址是 smtp.qq.com smtp_srv = "smtp.qq.com" try: # 两个参数 # 第一个是服务器地址,但一定是bytes格式,所以需要编码 # 第二个参数是服务器的接受访问端口 srv = smtplib.SMTP_SSL(smtp_srv.encode(), 465) # SMTP 协议默认端口25 # 登录邮箱发送 srv.login(from_addr, from_pwd) # 发送邮件 # 三个参数 # 1. 发送地址 # 2. 接受地址,必须是list格式 # 3. 发送内容,作为字符串发送 srv.sendmail(from_addr, [to_addr], msg.as_string()) srv.quit() except Exception as e: print(e)
HTML格式邮件发送
- 准备HTML代码作为内容
- 把邮件的subtype设为html
- 发送
案例v08
from email.mime.text import MIMEText mail_content = """ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1> 这是一封HTML格式邮件</h1> </body> </html> """ msg = MIMEText(mail_content, "html", "utf-8") # 构建发送者地址和登录信息 from_addr = "1441865605@qq.com" # 此处密码是经过申请设置后的授权码 from_pwd = "ajwvzqdlfigahiae" # 授权码 # 构建邮件接受者信息 to_addr = "971254246@qq.com" smtp_srv = "smtp.qq.com" try: import smtplib srv = smtplib.SMTP_SSL(smtp_srv.encode(), 465) srv.login(from_addr, from_pwd) srv.sendmail(from_addr, [to_addr], msg.as_string()) srv.quit() except Exception as e: print(e)
发送带附件的邮件
- 可以把邮件看作是一个文本邮件和一个附件的合体
- 一封邮件如果涉及多个部分,需要使用MIMEMultipart格式构建
- 添加一个MIMEText正文
- 添加一个MIMEBase或者MIMEText作为附件
案例v09
from email.mime.text import MIMEText # 构建附件使用 from email.mime.multipart import MIMEBase, MIMEMultipart # 构建基础邮件使用 mail_mul = MIMEMultipart() # 构建邮件正文 mail_text = MIMEText("Hello, i am xxxx", "plain", "utf-8") # 把构建好的邮件正文附加入邮件中 mail_mul.attach(mail_text) # 构建附加 # 构建附件,需要从本地读入附件 # 打开一个本地文件 # 以rb格式打开 with open("02.html", "rb") as f: s = f.read() # 设置附件的MIME和文件名 m = MIMEText(s, 'base64', "utf-8") m["Content-Type"] = "application/octet-stream" # 需要注意 # 1. attachment后分号为英文状态 # 2. filename 后面需要用引号包裹,注意与外面引号错开 m["Content-Disposition"] = "attachment; filename='02.html'" # 添加到MIMEMultipart mail_mul.attach(m) # 发送email地址 from_addr = "1441865605@qq.com" # 此处密码是经过申请设置后的授权码 from_pwd = "ajwvzqdlfigahiae" # 授权码 # 收件人信息 # 此时使用qq邮箱 to_addr = "1441865605@qq.com" # 输入SMIP服务器地址 # 此处根据不同的邮件服务商有不同的值 # 现在基本任何一家邮件服务商,如果采用第三方收发邮件,都需要开启授权选项 # 腾讯qq邮箱所用的SMTP地址是 smtp.qq.com smtp_srv = "smtp.qq.com" try: import smtplib # 两个参数 # 第一个是服务器地址,但一定是bytes格式,所以需要编码 # 第二个参数是服务器的接受访问端口 srv = smtplib.SMTP_SSL(smtp_srv.encode(), 465) # SMTP 协议默认端口25 # 登录邮箱发送 srv.login(from_addr, from_pwd) # 发送邮件 # 三个参数 # 1. 发送地址 # 2. 接受地址,必须是list格式 # 3. 发送内容,作为字符串发送 srv.sendmail(from_addr, [to_addr], mail_mul.as_string()) srv.quit() except Exception as e: print(e)
添加邮件头,抄送等信息
- mail["From"] 表示发送者信息,包括姓名和邮件
- mail["To"] 表示接受者信息,包括姓名和邮件地址
- mail["Subject"] 表示摘要或者主题信息
案例v10
from email.mime.text import MIMEText from email.header import Header msg = MIMEText("Hello world", "plain", "utf-8") # 用utf-8编码是因为很可能内容包含非英文字符 header_from = Header("从A发送出去的<A@qq.cn>", "utf-8") msg['From'] = header_from # 填写接受者信息 header_to = Header("去往B<B@sina.com>", "utf-8") msg['To'] = header_to header_sub = Header("这是主题", "utf-8") msg['Subject'] = header_sub # 构建发送者地址和登录信息 from_addr = "1441865605@qq.com" from_pwd = "ajwvzqdlfigahiae" # 构建邮件接受者信息 to_addr = "1441865605@qq.com" smtp_srv = "smtp.qq.com" try: import smtplib srv = smtplib.SMTP_SSL(smtp_srv.encode(), 465) srv.login(from_addr, from_pwd) srv.sendmail(from_addr, [to_addr], msg.as_string()) srv.quit() except Exception as e: print(e)
同时支持html和text格式
- 构建一个MIMEMultipart格式邮件
- MIMEMultipartd额subtype设置成alternative格式
- 添加HTML和Text邮件
案例v11
from email.mime.text import MIMEText from email.mime.multipart import MIMEMultipart # 构建一个MIMEMultipart邮件 msg = MIMEMultipart("alternative") # 构建一个HTML邮件内容 html_content = """ <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1> 这是一封HTML格式邮件</h1> </body> </html> """ # msg_html = MIMEText(html_content, "html", "utf-8") msg.attach(msg_html) msg_text = MIMEText("just text content", "plain", "utf-8") msg.attach(msg_text) # 发送email地址 from_addr = "1441865605@qq.com" from_pwd = "ajwvzqdlfigahiae" # 收件人信息: # 此处使用我的qq邮箱 to_addr = "1441865605@qq.com" # 输入SMTP服务器地址: # 此地址根据每隔邮件服务商有不同的值,这个是发信邮件服务商的smtp地址 # 我用的是qq邮箱发送,此处应该填写腾讯qq邮箱的smtp值,即smtp.163.com, # 需要开启授权码, smtp_srv = "smtp.qq.com" try: import smtplib # 加密传输 #server = smtplib.SMTP_SSL(smtp_srv.encode(), 465) # SMTP协议默认端口是25 # qq邮箱要求使用 TLS加密传输 server = smtplib.SMTP(smtp_srv.encode(), 25) # SMTP协议默认端口是25 server.starttls() # 设置调试级别 # 通过设置调试等级,可以清楚的看到发送邮件的交互步骤 server.set_debuglevel(1) # 登录发送邮箱 server.login(from_addr, from_pwd) server.sendmail(from_addr, [to_addr], msg.as_string()) server.quit() except Exception as e: print(e)
- 使用smtplib模块发送邮件
POP3协议负责接受邮件
- 本质上是MDA到MUA的一个过程
- 从 MDA 下载下来的是一个完整的邮件结构体,需要解析才能得到每个具体可读的内容
步骤:
用poplib下载邮件结构体原始内容
- 准备相应的内容(邮件地址,密码,POP3实例)
- 身份认证
- 一般会先得到邮箱内邮件的整体列表
- 根据相应序号,得到某一封信的数据流
- 利用解析函数进行解析出相应的邮件结构体
- 用email解析邮件的具体内容
案例v12
# 导入相关包 # poplib负责从MDA到MUA下载 import poplib # 以下包负责相关邮件结构解析 from email.parser import Parser from email.header import decode_header from email.utils import parseaddr # 得到邮件的原始内容 # 这个过程主要负责从MDA到MUA的下载并使用Parse粗略解析 def getMsg(): # 准备相应的信息 email = "1441865605@qq.com" # 邮箱的授权码 pwd = "ajwvzqdlfigahiae" # pop3服务器地址 pop3_srv = "pop.qq.com" # 端口995 # ssl代表是安全通道 srv = poplib.POP3_SSL(pop3_srv) # user代表email地址 srv.user(email) # pass_代表密码 srv.pass_(pwd) # 以下操作根据具体业务具体使用 # stat返回邮件数量和占用空间 # 注意stat返回一个tuple格式 msgs, counts = srv.stat() print("Messages: {0}, Size: {1}".format(msgs, counts)) # list返回所有邮件编号列表 # mails是所有邮件编号列表 rsp, mails, octets = srv.list() # 可以查看返回的mails列表类似[b'1 82923', b'2 2184', ...] print(mails) # 获取最新一封邮件,注意,邮件索引号是从1开始, 最新代表索引号最高 index = len(mails) # retr负责返回一个具体索引号的一封信的内容,此内容不具有可读性 # lines 存储邮件的最原始文本的每一行 rsp, lines, octets = srv.retr(index) # 获得整个邮件的原始文本 msg_count = b'\r\n'.join(lines).decode("utf-8") # 解析出邮件整个结构体 # 参数是解码后的邮件整体 msg = Parser().parsestr(msg_count) #关闭链接 srv.quit() return msg # 详细解析得到的邮件内容 # msg代表是邮件的原始内容 # idnent代表的是邮件嵌套的层级 def parseMsg(msg, indent=0): ''' 1. 邮件完全可能是有嵌套格式 2. 邮件只有一个From,To,Subject之类的信息 :param msg: :param indent: 描述邮件里面有几个邮件MIMEXXX类型的内容,展示的时候进行相应缩进 :return: ''' # 想办法提取出头部信息 # 只有在第一层的邮件中才会有相关内容, # 此内容只有一个 if indent == 0: for header in ['From', "To", 'Subject']: # 使用get可以避免如果没有相关关键字报错的可能性 # 如果没有 关键字”From“, 我们使用 msg["From"]会报错 value = msg.get(header, '') if value: # Subject中的内容直接解码就可以,他是字符串类型 if header == 'Subject': value = decodeStr(value) # 如果是From和To字段,则内容大概是 "我的邮箱<xxxxx@qq.com>“这种格式 else: hdr, addr = parseaddr(value) name = decodeStr(hdr) # 最终返回形如 "我的邮箱<xxx@qq.com>的格式 value = "{0}<{1}>".format(name, addr) print("{0}, {1}: {2}".format(indent, header, value)) # 下面代码关注邮件内容本身 # 邮件内容中,有可能是multipart类型,也有可能是普通邮件类型 # 下面的解析使用递归方式 if (msg.is_multipart()): # 如果是multipart类型,则调用递归解析 # 得到多部分邮件的一个基础邮件部分 parts = msg.get_payload() # enumerate 函数是内置函数 # 作用是将一个列表,此处是parts,生成一个有索引和parts原内容构成的新的列表 # 例如 enumerate(['a', 'b', 'c']) 结果是: [(1,'a'), (2, 'b'), (3, 'c')] for n,part in enumerate(parts): # 一个字符串乘以一个数字的意思是对这个字符串进行n倍扩展 # 比如 ”aa" * 2 -> "aaaa" print("{0}spart: {1}".format(' '*indent, n)) parseMsg(part, indent+1) else: # 基础类型 # get_content_type是系统提供函数,得到内容类型 content_type = msg.get_content_type() # text/plain 或者 text/html是固定值 if content_type == 'text/plain' or content_type == 'text/html': content = msg.get_payload(decode=True) charset = guessCharset(msg) if charset: content = content.decode(charset) print("{0}Text: {1}".format(indent, content)) else: #不是文本内容,则应该是附件 print('{0}Attachment: {1}'.format(indent, content_type)) def decodeStr(s): ''' s代表一封邮件中From,To,Subject中的任一项 对s进行解码,解码是编码的逆过程 :param s: :return: ''' value, charset = decode_header(s)[0] # charset完全可能为空 if charset: # 如果指定编码,则用指定编码格式进行解码 value = value.decode(charset) return value def guessCharset(msg): ''' 猜测邮件的编码格式 :param msg: :return: ''' # 调用现成的函数 charset = msg.get_charset() if charset is None: # 找到内容类型,并转换成小写 content_type = msg.get("Content-Type", "").lower() pos = content_type.find("charset=") if pos >= 0: # 如果包含chraset,则内容形如 charset=xxxx charset = content_type[pos+8:].strip() return charset if __name__ == "__main__": # 得到邮件的原始内容 msg = getMsg() print(msg) # 精确解析邮件内容 parseMsg(msg, 0)
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。