一、Socket编程
要想理解socket首先得熟悉一下TCP/IP协议族, TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,定义了主机如何连入因特网及数据如何再它们之间传输的标准,
从字面意思来看TCP/IP是TCP和IP协议的合称,但实际上TCP/IP协议是指因特网整个TCP/IP协议族。不同于ISO模型的七个分层,TCP/IP协议参考模型把所有的TCP/IP系列协议归类到四个抽象层中
应用层:TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet 等等
传输层:TCP,UDP
网络层:IP,ICMP,OSPF,EIGRP,IGMP
数据链路层:SLIP,CSLIP,PPP,MTU
每一抽象层建立在低一层提供的服务上,并且为高一层提供服务,看起来大概是这样子的
- 图片来自网络 -
- 图片来自网络 -
在TCP/IP协议中两个因特网主机通过两个路由器和对应的层连接。各主机上的应用通过一些数据通道相互执行读取操作
- 图片来自网络 -
socket
我们知道两个进程如果需要进行通讯最基本的一个前提能能够唯一的标示一个进程,在本地进程通讯中我们可以使用PID来唯一标示一个进程,但PID只在本地唯一,网络中的两个进程PID冲突几率很大,这时候我们需要另辟它径了,我们知道IP层的ip地址可以唯一标示主机,而TCP层协议和端口号可以唯一标示主机的一个进程,这样我们可以利用ip地址+协议+端口号唯一标示网络中的一个进程。
能够唯一标示网络中的进程后,它们就可以利用socket进行通信了,什么是socket呢?我们经常把socket翻译为套接字,socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信。
- 图片来自网络 -
socket起源于UNIX,在Unix一切皆文件哲学的思想下,socket是一种"打开—读/写—关闭"模式的实现,服务器和客户端各自维护一个"文件",在建立连接打开后,可以向自己文件写入内容供对方读取或者读取对方内容,通讯结束时关闭文件。
socket通信流程
socket是"打开—读/写—关闭"模式的实现,以使用TCP协议通讯的socket为例,其交互流程大概是这样子的
- 图片来自网络 -
服务器根据地址类型(ipv4,ipv6)、socket类型、协议创建socket
服务器为socket绑定ip地址和端口号
服务器socket监听端口号请求,随时准备接收客户端发来的连接,这时候服务器的socket并没有被打开
客户端创建socket
客户端打开socket,根据服务器ip地址和端口号试图连接服务器socket
服务器socket接收到客户端socket请求,被动打开,开始接收客户端请求,直到客户端返回连接信息。这时候socket进入阻塞状态,所谓阻塞即accept()方法一直到客户端返回连接信息后才返回,开始接收下一个客户端谅解请求
客户端连接成功,向服务器发送连接状态信息
服务器accept方法返回,连接成功
客户端向socket写入信息
服务器读取信息
客户端关闭
服务器端关闭
三次握手
在TCP/IP协议中,TCP协议通过三次握手建立一个可靠的连接
- 图片来自网络 -
第一次握手:客户端尝试连接服务器,向服务器发送syn包(同步序列编号Synchronize Sequence Numbers),syn=j,客户端进入SYN_SEND状态等待服务器确认
第二次握手:服务器接收客户端syn包并确认(ack=j+1),同时向客户端发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态
第三次握手:第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手
定睛一看,服务器socket与客户端socket建立连接的部分其实就是大名鼎鼎的三次握手
- 图片来自网络 -
二、Python发送邮件
2.1 yagmail
1)简介
yagmail 作为一款可代替smtp客户端的工具,其目的是为了尽可能简单发送邮件,它的简单程度可以让你的代码只需要简单的三行,就可以成功发送一封邮件
import yagmail
yag = yagmail.SMTP()
contents = ['This is the body, and here is just text http://somedomain/image.png',
'You can find an audio file attached.', '/local/path/song.mp3']
yag.send('[email protected]', 'subject', contents)
也可以一行解决
yagmail.SMTP('mygmailusername').send('[email protected]', 'subject', 'This is the body')
yagmail 可以安全地从钥匙圈中读取密码。如果在实际应用中不方便直接显示在代码中的话,yagmail 也提供一种替代方式,请继续往下看
yag = yagmail.SMTP('mygmailusername', 'mygmailpassword')
2)安装yagmail
在 Python2.x 和 Python3.x 中安装的区别如下:
pip install yagmail[all]
pip3 install yagmail[all]
如果在 Linux 环境下多个 Python 版本并存的情况下,我们也可以使用如下方式安装
先查看 pip 版本对应的 Python 什么版本
[root@test ~]# pip -V
pip 9.0.1 from /usr/lib/python2.7/site-packages (python 2.7)
这里我们看到对应的是 2.7 版本的 Python,所以我们在使用 pip 安装的时候默认是为 Python2.7 版本安装yagmail 模块,如果是使用 Python3.x 的 yagmail,可能会出现没有 yagmail 模块的现象,我们也可以使用如下方法进行安装
python2 -m pip install 模块名称
python3 -m pip install 模块名称
3)用户密码
Python 密钥环 lib 提供了一种从 python 访问系统密钥环服务的简单方法。 它可以用于需要安全密码存储的任何应用程序。通过打开一个Python解释器并运行
import yagmail
yagmail.register('mygmailusername', 'mygmailpassword')
实际上,它只是 keyring.set_password('yagmail','mygmailusername','mygmailpassword') 的一个包装。
当没有给出密码并且在密钥环中找不到用户时,getpass.getpass()用于提示用户输入密码。 一旦输入此密码,它可以存储在密钥环中,而不会再次询问。
另一个便利可以是将 .yagmail 文件保存在您的主文件夹中,其中只包含电子邮件用户名。 然后您可以省略所有内容,只需使用yagmail.SMTP()进行连接即可。 当然,这不适用于更多账户,但它可能是一个很好的默认值。
4)建立连接
yag = yagmail.SMTP('mygmailusername')
请注意,这个连接是可重用的,可关闭的,当它离开作用域时,它将在CPython中自行清理。
正如 tilgovi 在#39中指出的,SMTP不会在 PyPy 中自动关闭。 在这种情况下应该使用上下文管理器。
5)可用性
a. 定义变量
to = '[email protected]'
to2 = '[email protected]
to3 = '[email protected]'
subject = 'This is obviously the subject'
body = 'This is obviously the body'
html = '<a href="https://pypi.python.org/pypi/sky/">Click me!</a>'
img = '/local/file/bunny.png'
以上变量都是可选的,并且可以自行判断内容,期间甚至不需要发送一封邮件给自己
yag.send(to = to, subject = subject, contents = body)
yag.send(to = to, subject = subject, contents = [body, html, img])
yag.send(contents = [body, img])
此外,如果你不想明确说明,你可以执行如下操作
yag.send(to, subject, [body, img])
6)收件人
通过提供电子邮件字符串列表而不是单个字符串,也可以发送给一组人员:
yag.send(to = to)
yag.send(to = [to,to2]) #emailadresses的列表或元组* *不带*别名
yag.send(to = {to:'Alias1'}) #字典为emailaddress *带*别名
yag.send(to = {to:'Alias1',to2:'Alias2'}
默认情况下将发送一封电子邮件给自己。 从这个意义上说,yagmail.SMTP().send() 已经可以发送电子邮件了。 请注意,如果未使用明确的= ...,则将使用第一个参数发送到。 可以避免像:
yag.send(subject ='to self',contents ='hi!')
请注意,默认情况下,所有电子邮件地址都使用 soft_email_validation==True(默认值) 进行保守验证。
7)实练
[root@test ~]# cat mail-180606.py
#!/usr/local/python3/bin/python3.6
# -*- coding:utf-8 -*-
# @Time : 2018/6/5 20:36
# @Author : zhouyuyao
# @File : demon1.py
import yagmail
args={
"user":"[email protected]",
"password":"xxxxxxx",
"host":"smtp.163.com",
"port":"465"
}
yagmail.SMTP(**args)
emailList=["[email protected]"]
emailCc=['[email protected]']
email = yagmail.SMTP(**args)
email.send(to=emailList,subject="菜品",contents="Have a good day.",attachments="/home/test/1.txt",cc=emailCc)
[root@test ~]#
执行的过程中我们可能会遇到554报错,如下所示
Traceback (most recent call last):
File "mail-180606.py", line 20, in <module>
email.send(to=emailList,subject="Attention",contents="Have a good day.",attachments="/home/test/1.txt",cc=emailCc)
File "/usr/lib/python2.7/site-packages/yagmail/sender.py", line 129, in send
return self._attempt_send(addresses['recipients'], msg.as_string())
File "/usr/lib/python2.7/site-packages/yagmail/sender.py", line 135, in _attempt_send
result = self.smtp.sendmail(self.user, recipients, msg_string)
File "/usr/lib64/python2.7/smtplib.py", line 750, in sendmail
raise SMTPDataError(code, resp)
smtplib.SMTPDataError: (554, 'DT:SPM 163 smtp7,C8CowACHbP0fWBdbAtDFEQ--.43634S2 1528256544,please see http://mail.163.com/help/help_spam_16.htm?ip=120.76.44.79&hostid=smtp7&time=1528256544')
建议邮件内容不要带有敏感词汇,否则将会被当做垃圾邮件屏蔽而无法发送
2.2 smtplib
1)简介
SMTP(Simple Mail Transfer Protocol)即简单邮件传输协议,它是一组用于由源地址到目的地址传送邮件的规则,由它来控制信件的中转方式。
python的smtplib提供了一种很方便的途径发送电子邮件。它对smtp协议进行了简单的封装。
Python创建 SMTP 对象语法如下:
import smtplib
smtpObj = smtplib.SMTP( [host [, port [, local_hostname]]] )
'''
参数说明:
host: SMTP 服务器主机。 你可以指定主机的ip地址或者域名如: runoob.com,这个是可选参数
port: 如果你提供了 host 参数, 你需要指定 SMTP 服务使用的端口号,一般情况下 SMTP 端口号为25
local_hostname: 如果 SMTP 在你的本机上,你只需要指定服务器地址为 localhost 即可
'''
Python SMTP 对象使用 sendmail 方法发送邮件,语法如下:
SMTP.sendmail(from_addr, to_addrs, msg[, mail_options, rcpt_options])
'''
参数说明:
from_addr: 邮件发送者地址
to_addrs: 字符串列表,邮件发送地址
msg: 发送消息
这里要注意一下第三个参数,msg 是字符串,表示邮件
我们知道邮件一般由标题,发信人,收件人,邮件内容,附件等构成
发送邮件的时候,要注意 msg 的格式,这个格式就是 smtp 协议中定义的格式
'''
2)配置sendmail
a. 安装
安装sendmail:
yum -y install sendmail
systemctl start sendmail # 启动sendmail
systemctl enable sendmail # 开机自启
systemctl status sendmail # 启动状态
安装mailx:
yum install -y mailx
b. 发送
通过文件内容发送:
mail -s '主题' [email protected](收件人邮箱地址) < test.txt
通过管道符直接发送:
echo '内容' | mail -s '主题' [email protected](收件人邮箱地址)
c. 设置发件人信息
vim /etc/mail.rc
set [email protected]
set smtp=smtp.163.com
set [email protected]
set smtp-auth-password=客户端登录授权密码
set smtp-auth=login
d. 查看队列
mailq
e. 查看日志
tail /var/log/maillog
f. Possible errors
"553 mail from must equal authorized user"
此种情况可能原因是由于登录账号(set smtp-auth-user)和发件人账户(set from)不一致造成的
如果我们本机没有 sendmail 访问,也可以使用其他邮件服务商的 SMTP 访问(QQ、网易、Google等)。
3)smtplib发送邮件
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# @Time : 2018/6/5 22:10
# @Author : zhouyuyao
# @File : demon2.py
import email.mime.multipart
import email.mime.text
import smtplib
msg = email.mime.multipart.MIMEMultipart()
msg['from'] = '[email protected]'
msg['to'] = '[email protected]'
msg['subject'] = 'Have a good day.' # 主题
context = '''
<h1>晚上好</h1>
你好,
这是一封自动发送的邮件,请勿直接回复。
www.ustchacker.com hello
'''
text = email.mime.text.MIMEText(_text=context, _subtype="html")
msg.attach(text)
em = smtplib.SMTP_SSL()
em.connect("smtp.163.com", 465)
em.login("[email protected]", 'password')
em.sendmail(from_addr='[email protected]', to_addrs='[email protected]', msg=msg.as_string())
em.quit()
运行之后我们配置的收件人邮箱将收到一封邮件
参考资料
1. https://www.python.org/ftp/python/ Python源码下载地址
2. https://github.com/kootenpv/yagmail yagmail模块源码地址
3. http://blog.51cto.com/12173069/2055728 centos7使用sendmail发送邮件
4. https://www.cnblogs.com/dolphinX/p/3460545.html 简单理解Socket
5. http://goodcandle.cnblogs.com/archive/2005/12/10/294652.aspx 揭开Socket编程的面纱
6. http://www.cnblogs.com/skynet/archive/2010/12/12/1903949.html Linux Socket编程(不限Linux)