美文网首页
17--Python 网络编程和邮件

17--Python 网络编程和邮件

作者: Roger田翔 | 来源:发表于2019-06-19 18:30 被阅读0次
    @Author : Roger TX (425144880@qq.com)
    @Link : https://github.com/paotong999

    一、计算机网络协议

    计算机网络就是把各个计算机连接到一起,让网络中的计算机可以互相通信。网络编程就是如何在程序中实现两台计算机的通信。而确切地说,网络通信是两台计算机上的两个进程之间的通信。

    1、OSI 参考模型和 TCP/IP 分层模型

    1. OSI 参考模型把计算机网络分成物理层、数据链路层、网络层、传输层、会话层、表示层、应用层七层,OSI 模式已成为各种计算机网络结构的参考标准。
    2. IP协议(Internet Protocol)又称网际协议,是支持网间互联的数据报协议。IP 协议提供了网间连接的完善功能,包括 IP 数据报规定的互联网络范围内的地址格式。
    3. TCP协议(Transmission Control Protocol)又称传输控制协议,它规定了一种可靠的数据信息传递服务。

    虽然 IP 和 TCP 这两个协议的功能不尽相同,也可以分开单独使用,但它们是在同一个时期作为一个协议来设计的,并且在功能上是互补的,因此在实际使用中常常把这两个协议统称为 TCP/IP 协议。

    OSI 参考模型和 TCP/IP 分层模型的大致对应关系

    参考模型

    2、TCP/IP协议和端口号

    TCP/IP协议

    1. IP地址实际上是一个32位整数(称为IPv4),以字符串表示的IP地址如192.168.0.1实际上是把32位整数按8位分组后的数字表示,目的是便于阅读。
    2. IPv6地址实际上是一个128位整数,它是目前使用的IPv4的升级版,以字符串表示类似于 2001:0db8:85a3:0042:1000:8a2e:0370:7334
    3. TCP协议则是建立在IP协议之上的。TCP协议负责在两台计算机之间建立可靠连接,保证数据包按顺序到达。TCP协议会通过握手建立连接,然后,对每个IP包编号,确保对方按顺序收到,如果包丢掉了,就自动重发。
    4. 一个TCP报文除了包含要传输的数据外,还包含源IP地址和目标IP地址,源端口和目标端口。

    端口号
    不同的应用程序处理不同端口上的数据,在同一台机器中不能有两个程序使用同一个端口。
    端口号可以为 0~65535,通常将端口分为如下三类:

    • 公认端口(Well Known Port):端口号为 0~1023,它们紧密地绑定(Binding)一些特定的服务。
    • 注册端口(Registered Port):端口号为 1024~49151,它们松散地绑定一些服务。应用程序通常应该使用这个范围内的端口。
    • 动态和/或私有端口(Dynamic and/or Private Port):端口号为 49152~65535,这些端口是应用程序使用的动态端口,应用程序一般不会主动使用这些端口。

    二、Python网络编程模块

    四层网络模型及对应的协议
    • 网络接口层和网络层都是协议底层,通常来说,很少会直接基于底层进行应用程序编程。
    • 传输层协议主要是 TCP 和 UDP,Python 提供了 socket 等模块针对传输层协议进行编程。
    • 应用层协议更丰富,FTP、HTTP、TELNET 等协议都属于应用层协议,Python 同样为基于应用层协议的编程提供了丰富的支持。

    Python 标准库中的网络相关模块

    模块 描述
    socke 基于传输层 TCP、UDP 协议进行网络编程的模块
    asyncore socket 模块的异步版,支持基于传输层协议的异步通信
    asynchat asyncore 的增强版
    cg 基本的 CGI(Common Gateway Interface,早期开发动态网站的技术)支持
    email E-mail 和 MLME 消息处理模块
    ftplib 支持 FTP 协议的客户端模块
    httplib、http.client 支持 HTTP 协议以及 HTTP 客户揣的模块
    imaplib 支持 IMAP4 协议的客户端模块
    mailbox 操作不同格式邮箱的模块
    mailcap 支持 Mailcap 文件处理的模块
    nntplib 支持 NTTP 协议的客户端模块
    smtplib 支持 SMTP 协议(发送邮件)的客户端模块
    poplib 支持 POP3 协议的客户端模块
    telnetlib 支持TELNET 协议的客户端模块
    urllib及其子模块 支持URL 处理的模块
    xmlrpc、xmlrpc.server、xmlrpc.client 支持XML-RPC协议的服务器端和客户端模块

    三、Python socket建立TCP连接

    程序在使用 socket 之前,必须先创建 socket 对象,可通过该类的如下构造器来创建 socket 实例:

    socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)

    1. family 参数用于指定网络类型。该参数支持 socket.AF_UNIX(UNIX 网络)、socket.AF_INET(基于 IPv4 协议的网络)和 socket.AF_INET6(基于 IPv6 协议的网络)这三个常量。
    2. type 参数用于指定网络 Sock 类型。该参数可支持 SOCK_STREAM(默认值,创建基于 TCP 协议的 socket)、SOCK_DGRAM(创建基于 UDP 协议的 socket)和 SOCK_RAW(创建原始 socket)。
    3. proto 参数用于指定协议号,如果没有特殊要求,该参数默认为 0 ,并可以忽略。

    作为服务器端使用的 socket 必须被绑定到指定 IP 地址和端口,并在该 IP 地址和端口进行监听,接收来自客户端的连接。 TCP 通信的服务器端编程的基本步骤:

    • 服务器端先创建一个 socket 对象(socket.socket())。
    • 服务器端 socket 将自己绑定到指定 IP 地址和端口(socket.bind(address,host))。
    • 服务器端 socket 调用 listen() 方法监听网络。
    • 程序采用循环不断调用 socket 的 accept() 方法接收来自客户端的连接。
    • 发送数据:使用 send() 方法。注意,sendto() 方法用于 UDP 协议的通信。
    # 导入 socket 模块
    import socket
    # 创建socket对象
    s = socket.socket()
    # 将socket绑定到本机IP和端口
    s.bind(('192.168.1.88', 30000))
    # 服务端开始监听来自客户端的连接
    s.listen()
    while True:
        # 每当接收到客户端socket的请求时,该方法返回对应的socket和远程地址
        c, addr = s.accept()
        print(c)
        print('连接地址:', addr)
        c.send('您好,您收到了服务器的新年祝福!'.encode('utf-8'))
        # 关闭连接
        c.close()
    

    作为客户端先创建一个 socket 对象,然后调用 socket 的 connect() 方法建立与服务器端的连接,这样就可以建立一个基于 TCP 协议的网络连接。TCP 通信的客户端编程的基本步骤大致归纳如下:

    • 客户端先创建一个 socket 对象(socket.socket())。
    • 客户端 socket 调用 connect() 方法连接远程服务器。
    • 接收数据:使用 recv() 方法。
    # 导入socket模块
    import socket
    # 创建socket对象
    s = socket.socket()
    # 连接远程主机
    s.connect(('192.168.1.88', 30000))    # ①
    print('--%s--' % s.recv(1024).decode('utf-8'))
    s.close()
    

    四、Python socket建立UDP连接

    UDP协议,全称 User Datagram Protocol,中文名称为用户数据报协议,主要用来支持那些需要在计算机之间传输数据的网络连接。
    UDP 是一种面向非连接的协议,面向非连接指的是在正式通信前不必与对方先建立连接,不管对方状态就直接发送数据。至于对方是否可以接收到这些数据,UDP 协议无法控制,所以说 UDP 是一种不可靠的协议。

    UDP 协议和 TCP 协议简单对比如下:

    • TCP 协议:可靠,传输大小无限制,但是需要连接建立时间,差错控制开销大。
    • UDP 协议:不可靠,差错控制开销较小,传输大小限制在 64 KB以下,不需要建立连接。
    • UDP 在通信实例的两端各建立一个 socket,但它们只是发送、接收数据的对象,没有客户端和服务器端的概念

    socket建立UDP连接
    程序在创建 socket 时,可通过 type 参数指定该 socket 的类型,如果将该参数指定为 SOCK_DGRAM,则意味着创建基于 UDP 协议的 socket。程序可以通过如下两个方法来发送和接收数据:

    • socket.sendto(bytes, address):将 bytes 数据发送到 address 地址。
    • socket.recvfrom(bufsize[, flags]):接收数据。该方法可以同时返回 socket 中的数据和数据来源地址。

    使用 UDP 协议的 socket 在发送数据时必须使用 sendto() 方法,这是因为程序必须指定发送数据的目标地址
    使用 UDP 协议的 socket 在接收数据时,既可使用普通的 recv() 方法,也可使用 recvfrom() 方法。如果程序需要得到数据报的来源,则应该使用 recvfrom() 方法

    作为服务器端使用的 socket

    import socket
    PORT = 30000;
    # 定义每个数据报的大小最大为4KB
    DATA_LEN = 4096;
    # 定义一个字符串数组,服务器端发送该数组的元素
    books = ("疯狂Python讲义",
        "疯狂Kotlin讲义",
        "疯狂Android讲义",
        "疯狂Swift讲义")
    # 通过type属性指定创建基于UDP协议的socket
    s = socket.socket(type=socket.SOCK_DGRAM)
    # 将该socket绑定到本机的指定IP和端口
    s.bind(('192.168.199.1', PORT))
    # 采用循环接收数据
    for i in range(1000):
        # 读取s中的数据的数据的发送地址
        data, addr = s.recvfrom(DATA_LEN)
        # 将接收到的内容转换成字符串后输出
        print(data.decode('utf-8'),addr)
        # 从字符串数组中取出一个元素作为发送数据
        send_data = books[i % 4].encode('utf-8')
        # 将数据报发送给addr地址
        s.sendto(send_data, addr)
    s.close()
    

    作为客户端使用的 socket

    import socket
    PORT = 30000;
    # 定义每个数据报的大小最大为4KB
    DATA_LEN = 4096;
    DEST_IP = "192.168.199.1";
    # 通过type属性指定创建基于UDP协议的socket
    s = socket.socket(type=socket.SOCK_DGRAM)
    # 不断地读取键盘输入
    while True:
        line = input('')
        if line is None or line == 'exit':
            break
        data = line.encode('utf-8')
        # 发送数据报
        s.sendto(data, (DEST_IP, PORT))
        # 读取socket中的数据
        data = s.recv(DATA_LEN)
        print(data.decode('utf-8'))
    s.close()
    

    五、Python smtplib模块:发送邮件

    使用 Python 的 smtplib 模块来发送邮件非常简单,只需要按照如下 3 步来发送邮件即可:

    1. 连接 SMTP 服务器,并使用用户名、密码登录服务器。
    2. 创建 EmailMessage 对象,该对象代表邮件本身。
    3. 调用代表与 SMTP 服务器连接的对象的 sendmail() 方法发送邮件。
    4. 要为邮件设置主题、发件人名字和收件人名字,需要设置 EmailMessage 对象的相应属性。
    5. 要将邮件改为 HTML ,需要调用 EmailMessage的set_content() 方法的第二个参数,设置为 html。
    import smtplib
    from email.message import EmailMessage
    
    smtp_server = 'smtp.qq.com'    # 定义SMTP服务器地址:
    from_addr = '425144880@qq.com'    # 定义发件人地址
    password = '123456'    # 定义登录邮箱的密码
    to_addr = 'wozhufuni999@163.com'    # 定义收件人地址
    # 创建SMTP连接
    #conn = smtplib.SMTP(smtp_server, 25)
    conn = smtplib.SMTP_SSL(smtp_server,465)
    conn.set_debuglevel(1)    # smtplib 调试模式
    conn.login(from_addr, password)
    first_id = '0000001'
    # 创建邮件对象
    msg = EmailMessage()
    # 设置邮件内容
    msg.set_content('<h2>邮件内容</h2>'
    '<p>您好,这是一封来自Python的邮件<p>'
    '<img src="cid:' + first_id +'"><p>'
    '来自<a href="https://github.com/paotong999">Github</a>', 'html', 'utf-8')
    msg['subject'] = '一封HTML邮件'
    msg['from'] = '田翔<%s>' % from_addr
    msg['to'] = '新用户 <%s>' % to_addr
    with open('E:/logo.jpg', 'rb') as f:
        # 添加附件
        msg.add_attachment(f.read(), maintype='image',
            subtype='jpeg', filename='test.png', cid=first_id)
    # 发送邮件
    conn.sendmail(from_addr, [to_addr], msg.as_string())
    # 退出连接
    conn.quit()
    

    为了给邮件添加附件,只需调用 EmailMessage的add_attachment() 方法即可。该方法支持很多参数,最常见的参数如下:
    maintype:指定附件的主类型。比如指定 image 代表附件是图片。
    subtype:指定附件的子类型。比如指定为 png,代表附件是 PNG 图片。一般来说,子类型受主类型的限制。
    filename:指定附件的文件名。
    cid=img:指定附件的资源 ID,邮件正文可通过资源 ID 来引用该资源。

    六、Python poplib模块:收取邮件

    使用 poplib 收取邮件可分为两步:

    1. 使用 poplib.POP3 或 poplib.POP3_SSL 按 POP3 协议从服务器端下载邮件。
    2. 使用 email.parser.Parser 或 email.parser.BytesParser 解析邮件内容,得到 EmailMessage 对象,从 EmailMessage 对象中读取邮件内容。
    import poplib, os.path, mimetypes
    from email.parser import BytesParser, Parser
    from email.policy import default
    # 输入邮件地址, 口令和POP3服务器地址:
    email = 'tx@henghaodata.com'
    password = '123456'
    pop3_server = 'pop-ent.21cn.com'
    # 连接到POP 3服务器:
    #conn = poplib.POP3(pop3_server, 110)
    conn = poplib.POP3_SSL(pop3_server, 995)
    # 可以打开或关闭调试信息:
    #conn.set_debuglevel(1)
    # 可选:打印POP 3服务器的欢迎文字:
    print(conn.getwelcome().decode('utf-8'))
    # 输入用户名、密码信息
    # 相当于发送POP 3的user命令
    conn.user(email)
    # 相当于发送POP 3的pass命令
    conn.pass_(password)
    # 获取邮件统计信息,相当于发送POP 3的stat命令
    message_num, total_size = conn.stat()
    print('邮件数: %s. 总大小: %s' % (message_num, total_size))
    # 获取服务器上的邮件列表,相当于发送POP 3的list命令
    # resp保存服务器的响应码
    # mails列表保存每封邮件的编号、大小
    resp, mails, octets = conn.list()
    print(resp, mails)
    # 获取指定邮件的内容(此处传入总长度,也就是获取最后一封邮件)
    # 相当于发送POP 3的retr命令
    # resp保存服务器的响应码
    # data保存该邮件的内容
    resp, data, octets  = conn.retr(len(mails))
    # 将data的所有数据(原本是一个字节列表)拼接在一起
    msg_data = b'\r\n'.join(data)
    # 将字符串内容解析成邮件,此处一定要指定policy=default
    msg = BytesParser(policy=default).parsebytes(msg_data)       #①
    print(type(msg))
    print('发件人:' + msg['from'])
    print('收件人:' + msg['to'])
    print('主题:' + msg['subject'])
    print('第一个收件人名字:' + msg['to'].addresses[0].username)
    print('第一个发件人名字:' + msg['from'].addresses[0].username)
    for part in msg.walk():
        counter = 1
        # 如果maintype是multipart,说明是容器(用于包含正文、附件等)
        if part.get_content_maintype() == 'multipart' :
            continue
        # 如果maintype是multipart,说明是邮件正文部分
        elif part.get_content_maintype() == 'text':
            print(part.get_content())
        # 处理附件
        else :
            # 获取附件的文件名
            filename = part.get_filename()
            # 如果没有文件名,程序要负责为附件生成文件名
            if not filename:
                # 根据附件的contnet_type来推测它的后缀名
                ext = mimetypes.guess_extension(part.get_content_type())
                # 如果推测不出后缀名
                if not ext:
                    # 使用.bin作为后缀名
                    ext = '.bin'
                # 程序为附件来生成文件名
                filename = 'part-%03d%s' % (counter, ext)
            counter += 1
            # 将附件写入的本地文件
            with open(os.path.join('.', filename), 'wb') as fp:
                fp.write(part.get_payload(decode=True))
    # 退出服务器,相当于发送POP 3的quit命令
    conn.quit()
    

    上面程序通过 poplib 模块使用 POP3 命令从服务器端下载邮件,其实就是依次发送 user、pass、stat、list、retr 命令的过程。当 retr 命令执行完成后,将得到最后一封邮件的数据 data,该 data 是一个 list 列表,因此程序需要先将这些数据拼接成一个整体,然后将邮件数据恢复成 EmailMessage 对象。这里有一点需要指出,程序在创建 BytesParser(解析字节串格式的邮件数据)或 Parser(解析字符串格式的邮件数据)时,必须指定 policy=default

    如果程序要读取 EmailMessage 的各部分,则需要调用该对象的 walk() 方法,该方法返回一个可迭代对象,程序使用 for 循环遍历 walk() 方法的返回值,对邮件内容进行逐项处理:

    1. 如果邮件某项的 maintype 是 'multipart',则说明这一项是容器,用于包含邮件内容、附件等其他项。
    2. 如果邮件某项的 maintype 是 'text',则说明这一项的内容是文本,通常就是邮件正文或文本附件。对于这种文本内容,程序直接将其输出到控制台中。
    3. 如果邮件某项的 maintype 是其他,则说明这一项的内容是附件,程序将附件内容保存在本地文件中。

    运行上面程序,可以看到程序收取了指定邮件的最后一封邮件,并将邮件内容输出到控制台中,将邮件附件保存在本地文件中。

    相关文章

      网友评论

          本文标题:17--Python 网络编程和邮件

          本文链接:https://www.haomeiwen.com/subject/sfyhqctx.html