zoukankan      html  css  js  c++  java
  • 用Python实现gmail邮箱服务,实现两个邮箱之间的绑定(中)

      这篇博客,主要讲解用Python实现邮箱服务的几个需要学习的模块:E-mail Compotion and Decoding(邮件生成和解析)、SMTP、POP、IMAP

      如上篇博客所讲,我学习过程参考《Foundations of Python3 Network Programming. 2nd Edition》,代码部分借鉴了其中的例子,但绝对包含自己的东西,特此声明。

      如果已经了解了这些知识,请看:用Python实现gmail邮箱服务,实现两个邮箱之间的绑定(下)

    E-mail Composition and Decoding 

    一、邮件涉及协议及本文说明
    1. 协议

    • SMTP(Simple Mail Transfer Protocal) 简单邮件传输协议,用于发送邮件。
    • MIME(Mutipurpose Internet Mail Extensions) 多用途互联网邮件扩展,可发送附件。但由于,程序不许要这个功能,因此我们有学习,也就不会出现在本文中。
    • POP(Post Office Protocal) 邮局协议,一般用POP3。可以用以较为简单的方式接收邮件(从邮件服务器上下载邮件到主机上)。
    • IMAP(Internet Mail Access Protocal) 也用于接收邮件,功能较POP3更为强大些。

    2. 在下面中会更详细的介绍这些协议,以及在Python中的使用方式。
    二、邮件格式粗糙解释
      Email在组织的时候遵循header和body的映射模式。而header是固定一些可选的如 From, To, Subject, Date time, Receiver, Message-ID, Content, Attachment。它们的组织形式如:

    • From: ....(显示发送者邮箱)
    • To: ... (显示目的地邮箱)
    • Subject: ... (显示主题)
    • Date: ... (显示发送时间还是到达时间?)
    • Content: ... (邮件主体内容)
    • Attachment: ... (附件)

      对于我来说,我只需要From, To, Subject, Content。
    三、撰写“极简单”邮件
    1. 下面以一个例子说明怎样生成一封邮件。

    from email.message import Message
    # 一个email一般封装在Message类中,所以需要在email.message中引入Message类。
    
    # 这是邮件主体内容
    text = """ Hello,
    This is a test message from vicczx.
    
    --viczzx--"""
    
    msg = Message() # 构造一个Message实例
    msg['To'] = "toUserName@example.com" #接收者邮箱
    msg['From'] = "myUserName@example.com" #自己的邮箱
    msg['Subject'] = 'Test Message' #邮件主题
    msg.set_payload(text) #将上面的邮件内容通过set_payload()函数封装进msg
    
    # 通过上面也可一看到, 邮件格式就是通过映射的方式进行组织的。需要注意的是:'To','From'等区分大小写,否则接收者无法解析
    
    print(msg.as_string()) # 查看邮件内容

    2. 添加Date和Message-ID header
      绝大多数邮件有个Date header,这个可以通过email.utils库进行生成;
      你也可以生成一个Message-ID header,就可以区别世界上所有其他的邮件了。这个也是通过email.utils模块的函数进行生成。
      对于我来说,因为我不打算做一个功能强大的邮件客户端,因此,这些都是可有可无的。不过加上这些内容后,也算比较完整了。如下代码:

    from email.utils
    from email.message import Message
    # 一个email一般封装在Message类中,所以需要在email.message中引入Message类。
    
    # 这是邮件主体内容
    text = """ Hello,
    This is a test message from vicczx.
    
    --viczzx--"""
    
    msg = Message() # 构造一个Message实例
    msg['To'] = "toUserName@example.com" #接收者邮箱
    msg['From'] = "myUserName@example.com" #自己的邮箱
    msg['Subject'] = 'Test Message' #邮件主题
    msg['Date'] = email.utils.formatdate(localtime=1) #函数详细说明请查看官方Python API Reference
    msg['Message-ID'] = email.utils.make_msgid() 
    msg.set_payload(text) #将上面的邮件内容通过set_payload()函数封装进msg
    
    # 通过上面也可一看到, 邮件格式就是通过映射的方式进行组织的。需要注意的是:'To','From'等区分大小写,否则接收者无法解析
    
    print(msg.as_string()) # 查看邮件内容

    四、解析邮件(Parsing Messages)
      知道了怎样生成邮件的,其实解析邮件就能够大致了解了。

    # 已知msg 为下载下来的Message()实例邮件。
    print("This message is from : ", msg['From'] )
    print("This message is to : ", msg['To'])
    print("Subject: ", msg['Subject']) # 主题
    print("Content: ", msg.get_payload()) # 得到主体内容

      但是在实际上,可能没有这么简单。因为需要使用到中文,邮件解析还要考虑这一点;其他问题在这里不再这里过多说明,我会在后面详细的讲解程序开发过程中遇到的种种问题,所以,如果有需要请耐心继续看。

    SMTP

    一、简介
      上面介绍了传统邮件的生成和解析,这些都是non-internet,也就是不需要网络就可一完成的。那么当生成了邮件,下一步就是发送了,本文就讲解利用SMTP协议发送邮件。
      正如SMTP(Simple Mail Transfer Protocal)名字一样,只能发送简单邮件。上面讲解就是生成的简单邮件,完全可以通过SMTP协议来发送。

    二、SMTP使用方法
      Python是通过smtplib模块来实现SMTP的。关于本模块的详细说明,请参考这里
    1. 方法流程
      生成message, 连接你的邮箱smtp服务器,登录自己的邮箱帐号, 发送邮件,退出
    2. 连接邮箱smtp服务器
      一般各大公司的smtp邮箱服务器网址都是形如:smtp.example.com,如gmail的为smtp.gmail.com
      连接邮箱smtp服务器使用smtplib.SMTP()和smtplib.SMTP_SSL()方法。SMTP_SSL()方法使用了安全socket层。由于我不求甚解,所以更加详细的说明请见文档。我使用的gmail使用的是SMTP_SSL(),所以代码如下:

    smtpServer = 'smtp.gmail.com'
    server = smtplib.SMTP_SSL(smtpServer)

      由于可能出现异常错误,所以可以用try...except来处理下,如:

    import smtplib, sys
    
    smtpServer = 'smtp.gmail.com'
    try:
        server = smtplib.SMTP_SSL(smtpServer) #返回SMTP类,所以server是SMTP类的实例
    except ConnectionRefusedError:
        print('Server connecting failed')
        sys.exit(1)

    3. 登录自己的邮箱帐号
      利用SMTP.login(user, passwd)登录,如:

    user = 'myUserName@gmail.com'
    passwd = '***' 
    server.login(user, passwd)

      在文档中说明了可能出现的异常,最长见的是smtp.SMTPAuthenticationError。另外,passwd也可一通过getpass.getpass()方法获得,这种方法与用户进行交互,用户输入密码,但是不显示,可以保护帐号安全。

    import smtplib, sys, getpass
    
    smtpServer = 'smtp.gmail.com'
    user = 'myUserName@gmail.com'
    passwd = getpass.getpass()
    
    try:
        server = smtplib.SMTP_SSL(smtpServer)
    except ConnectionRefusedError:
        print('Server connecting failed')
        sys.exit(1)
    
    try:
        server.login(user, passwd)
    except smtp.SMTPAuthenticationError:
        print('Antentication failed, please check your username or password')
        sys.exit(1)

    4. 发送邮件及退出
      SMTP提供了两种方法来发送邮件,分别是:SMTP.send_message()SMTP.sendmail()。简单来说,第一种发送的就是上一节讲的Message类实例,也就是标准的传统邮件;第二种发送的只是一段文字,也就是Content,不包括其他的。下面通过例子演示一下:

    import smtplib, sys, getpass
    from email.message import Message
    
    smtpServer = 'smtp.gmail.com'
    user = 'myUserName@gmail.com'
    toAddr = 'toUser@example.com'
    
    passwd = getpass.getpass()
    
    text = """Hi, 
    I'm viczzx, this is the message content, reply whenever you saw this.
    Thank you!
    --viczzx--"""
    
    msg = Message()
    msg.set_payload(text)
    # 其他header省略
    
    try:
        server = smtplib.SMTP_SSL(smtpServer)
    except ConnectionRefusedError:
        print('Server connecting failed')
        sys.exit(1)
    
    try:
        server.login(user, passwd)
    except smtp.SMTPAuthenticationError:
        print('Antentication failed, please check your username or password')
        sys.exit(1)
    else:
        server.sendmail(user, toAddr, text) #只发送邮件正文
        server.send_message(msg, user, toAddr) #发送Message实例
    finally:
        server.quit() #这是必须的!!!

    三、 其他的话
      这个smtp小程序是非常简单的,只是把流程上呈现给大家,不过一般情况下这样就足够了。关于SMTP还有其他很多需要注意的地方,比如各种异常处理,由于我在学习的时候没有出现这些问题,因此就没有特别说明,如果需要,请查看相关文档。

    POP

    一、简介
      POP(Post Office Protocal)最长用的POP版本是POP3,因此本文是以POP3为主。POP3非常简单,可以用来从邮件服务器上下载邮件,然后删除这些邮件。功能非常有限,后面讲解的IMAP完胜它,不过作为入门级的,还是有必要介绍一下,也对学习SMTP有帮助。
      Python提供了poplib模块,它提供了使用POP的便利接口。
    二、实例
      由于pop3功能较IMAP非常有限,而且我最后的程序并没有使用pop3,所以,不详细讲解,下面通过一个例子来说明下较为常见的功能。
      这个例子的功能为进入邮箱,查看所有的邮件。首先显示邮件的发件人、主题,查看邮箱主题内容。
    1. 需要模块

    import email, poplib, sys

    2. 连接POP3服务器,登录个人邮箱账户
      poplib提供POP3()方法和POP3_SSL()方法连接POP3服务器,区别和SMTP一样。gmail仍然使用POP3_SSL()方式,并返回class POP3实例

    p = poplib.POP3_SSL('pop.gmail.com') 

      使用POP3.user(), POP3.pass_()方法来登录个人账户

    try:
        p.user(user) 
        p.pass_(passwd)
    except poplib.error_proto: #可能出现的异常
        print('login failed')

    3. 现在已经进入个人账户,下一步,利用POP3.list()函数查看邮箱内邮件信息。

      关于list()函数的详细说明,请点击这里
    list()函数有三个返回值,分别是:response, listings, octets

    • response 应答信息,我测试中出现的结果:

        

      以b开头的字符串是Byte类型,我在实际测试的时候,返回的信息几乎都是Byte类型的。关于此类型及和普通字符串的转化会在后面举例说明。

    • listings 是形如['message_id message_size',...]若干各message-id和message_size构成的list。后面就是通过message_id来检索邮件。我测试中出现的结果:

      

    • octets 不是特别清楚啥意思。
    response, listings, octets = p.list()

    4. 最重要的就是listings数据

      如上面解释的,listings是个list类型的数据,接下来我们取出listings中的message_id,也就是上面的 "1" "2" "3" "4" ...

    for listing in listings: #每次需要一个listing
    number, size = listing.split() #由于number和size是以空格分隔,所以利用split()函数分开,split()默认以' '为分隔

      现在我们就取出了我们需要的message_id,也就是number,注意number需要从Byte类型转化为字符串类型。

    5. POP3.top()函数

      利用此函数,取出邮件的headers,如下:

    response, lines, octets = p.top(number , 0)

      lines存储内容,下面先转化成Message类型(lines默认为标准字符串类型,仅供说明,以实际代码为准)

    message = email.message_from_string('
    '.join(lines))

    6. 已经生成Message类,可以利用头部信息来查看From, Subject等信息

    for header in 'From', 'To', 'Subject', 'Date':
        if header in message:
            print(header + ':' , message[header]) 

      注意,此时的message[header]可能不会输出我们想看到的内容,有可能出现格式错乱问题,比如中英文的转化,所以还需要特殊来处理。处理方式请继续往下看IMAP部分。

    7. 取出邮件所有信息
      上面的top()函数只取出header信息以及根据参数确定的n行内容,如果用户希望查看邮件所有内容,那利用POP3.retr()函数取出

    response, lines, octets = p.retr(number)

      还是将lines中的内容转换成Message类型:

    message = email.message_from_string('
    '.join(lines))

    8. 已经有了邮件所有信息,可以通过Message.get_payload()取出邮件正文了。

      但是,get_payload()函数并不一定返回邮件正文。以下是官方说明:
    Return the current payload, which will be a list of Message objects when is_multipart() is True, or a string when is_multipart() is False.
      在实际测试中,返回的就是a list of Message objects,这个问题困扰我很长时间,最终还是解决了,通过以下方法:

    maintype = message.get_content_maintype()
    if maintype == 'multipart':
        for part in message.get_payload():
            if part.get_content_maintype() == 'text':
                mail_content = part.get_payload(decode=True).strip()
    elif maintype == 'text':
        mail_content = e.get_payload(decode=True).strip()        

    9. 此时,mail_content就是邮件正文了.

      当然,如果是中文的话,这件事仍未完,还需要将它转化未'gbk',利用如下方式:

    mail_content = mail_content.decode('gbk')

    10. 到现在,基本已经大功告成了,能够取出邮箱中所有的邮件,并查看邮件的header内容和邮件正文了^_^
    三、完整代码:

    #-*- encoding:utf-8 -*-
    #-*- encoding:gbk -*-
    
    import email, getpass, poplib, sys
    
    hostname = 'pop.gmail.com'
    user = 'myUserName@gmail.com'
    passwd = '***'
    
    p = poplib.POP3_SSL('pop.gmail.com') #与SMTP一样,登录gmail需要使用POP3_SSL() 方法,返回class POP3实例
    try:
        # 使用POP3.user(), POP3.pass_()方法来登录个人账户
        p.user(user) 
        p.pass_(passwd)
    except poplib.error_proto: #可能出现的异常
        print('login failed')
    else:
        response, listings, octets = p.list()
        for listing in listings:
            number, size = listing.split() #取出message-id
            number = bytes.decode(number) 
            size = bytes.decode(size) 
            print('Message', number, '( size is ', size, 'bytes)')
                print()
            response, lines, octets = p.top(number , 0)
            # 继续把Byte类型转化成普通字符串
            for i in range(0, len(lines)):
                lines[i] = bytes.decode(lines[i])
            #利用email库函数转化成Message类型邮件
            message = email.message_from_string('
    '.join(lines))
            # 输出From, To, Subject, Date头部及其信息
            for header in 'From', 'To', 'Subject', 'Date':
                if header in message:
                print(header + ':' , message[header]) 
            #与用户交互是否想查看邮件内容
            print('Read this message [ny]')
            answer = input()
            if answer.lower().startswith('y'):
                response, lines, octets = p.retr(number) #检索message并返回
                for i in range(0, len(lines)):
                    lines[i] = bytes.decode(lines[i])
                message = email.message_from_string('
    '.join(lines)) 
                print('-' * 72)
                maintype = message.get_content_maintype()
                if maintype == 'multipart':
                    for part in message.get_payload():
                        if part.get_content_maintype() == 'text':
                    mail_content = part.get_payload(decode=True).strip()
                elif maintype == 'text':
                    mail_content = e.get_payload(decode=True).strip()
                try:
                    mail_content = mail_content.decode('gbk')
                except UnicodeDecodeError:
                    print('Decoding to gbk error')
                    sys.exit(1)
                print(mail_content)
            print()
            print('Delete this message? [ny]')
            answer = input()
            if answer.lower().startswith('y'):
                p.dele(number)
                print('Deleted')
    finally:
        print('log out')
        p.quit()
            

    IMAP

    一、简介
    IMAP(Internet Message Access Protocol),这个协议与POP一样,也是从邮件服务器上下载邮件到本机,不过IMAP比POP的功能要更加强大些,IMAP除支持POP所有功能外,还支持以下功能:

    • 多个邮件文件夹(收件箱、发件箱、垃圾邮件...)
    • IMAP服务器上进行标记如:Seen, Replied, Read, Deleted
    • 在服务器端的文件夹之间拷贝和移动邮件
    • ...

      在IMAP的各版本中,最流行的是IMAP4。我们就使用IMAP4
      由于,我需要搜索是否有未读邮件,也就是利用邮件服务器的Flag,所以IMAP是非常适合的,我的程序就利用的是IMAP。
      在Python的标准库包含一个imaplib模块,可以利用这个模块。但是,这个模块的缺陷就是把大量解析的工作留给客户端程序员。
    二、IMAPClient
      IMAPClient是一个非常受欢迎的IMAPCLient包,这个模块不在标准Python库中。IMAPClient包是由一名叫做Menno Smits的Python程序员编写的。官网网址:http://imapclient.freshfoo.com/。可以在这里查看手册文档。这个包是基于标准库imaplib,不过要更强大。下面我们来介绍下怎样安装。
    1. virtualenv
      说实话,我本人对virtualenv的理解也不透彻,以字面上来理解为虚拟环境。可以把一些模块、包安装在特定的virtualenv里,一旦安装了virtualenv,你就创建任意多个自组织的虚拟python环境,在这个环境里,可以安装、下载包。
      好吧,废话就不多说,直接说方法。
      这里是virtualenv的详细说明,上面介绍了非常详细的安装方法,按照我自己的经验,可以简化为以下步骤:

    $ [sudo] pip install virtualenv
    $ [sudo] pip install https://github.com/pypa/virtualenv/tarball/develop
    $ curl -O https://pypi.python.org/packages/source/v/virtualenv/ virtualenv-X.X.tar.gz
    $ tar xvfz virtualenv-X.X.tar.gz
    $ cd virtualenv-X.X
    $ [sudo] python setup.py install

      注意,上面下载的 virtualenv-X.X.tar.gz 中的X是型号,需要把它改成数字,详细版本类型可以参考:https://pypi.python.org/packages/source/v/virtualenv/

      这样,virtualenv已经安装好。下面需要创建虚拟环境实例,步骤如下:
    $ virtualenv --no-site-packages myenv
    $ cd myenv
    2. 安装IMAPClient
      myenv 为自己定义的虚拟环境的名字。这样,我们已经在myenv里面,接下来就可一安装IMAPClient包了。步骤如下:
    $ sudo pip install imapclient
    $ python -c 'import imapclient'

      此时,可以在python下使用imapclient模块,但是不能在python3下使用,在网上查了一些资料,尤其是看了上面的那个介绍virtualenv的网页,没找到有用的,但是,回头发现,这个imapclient是好使的了,不用进入gmapenv,直接使用即可,got it!
      注意,上面用到了pip工具,如果没有的话一定要安装啊。
    $ sudo apt-get install pip

    三、开始正式学习IMAP
    1. 因为可能会出现中文,因此在程序的最上面,必须加上如下代码:

    #-*- encoding: utf-8 -*-
    #-*- encoding: gbk -*-

    2. 所需模块

    import getpass, email, sys
    from imapclient import IMAPClient

    3. 连接服务、登录账户
      这一步也没什么好讲的。代码如下:

    # 通过以下方式连接smtp服务器,没有考虑异常情况,详细请参考官方文档
    c = IMAPClient(hostname = 'imap.gmail.com', ssl= True) 
    try:
        c.login(username, passwd) #登录个人帐号
    except c.Error:
        print('Could not log in')
        sys.exit(1)

    4. 进入收件箱,查看未读邮件

    c.select_folder('INBOX', readonly = True) 
    result = c.search('UNSEEN')

      利用select_folder()函数进行文件夹,'INBOX'为收件箱,readonly = True 表明只读并不修改任何信息
      利用search()函数选择想要的邮件,'UNSEEN'是邮件的flag,关于邮件的flag就不特别说明了,返回邮件的message-id
    5. 有了未读邮件的ID(result),下面利用fetch()函数将邮件取来(下载到本机)

    msgdict = c.fetch(result, ['BODY.PEEK[]'] )

      通过fetch()函数取得邮件内容,fetch()的详细介绍请见这里
          fetch(self, message, data) 其中self参数可忽略,message为message_id, data 的作用是抓取message中的哪些部分。  官方文档中没有给出data的其他可选的参数,我一开始怎么都不找到,最终在stackoverflow中进行提问,一位大哥把这个文档介绍给我,在 6.4.5 FETCH Command 。这里面非常详细的介绍了各个函数的各种细节,当然也可以查到data其他可选的参数 6.4.5 表示的是原书的节。特别感谢这位哥们,人类的力量是无穷的啊!

      我们只需要'BODY.PEEK[]'即可。
    6. 已经把邮件取出,下面开始解析邮件

    for message_id, message in msgdict.items():
    e = email.message_from_string(message['BODY[]']) # 生成Message类型

    7. 得到的 e 即为Message类型的邮件,先面开始将又将中解析出'From', 'Subject'

      还记得上面在POP讲解中,我们遇到的不能显示中文的问题吗?在IMAP中仍会出现,下面就讲解解决办法
      由于'From', 'Subject' header有可能有中文,必须把它转化为中文,在这个点上,耽误了我很长时间,最终在网上查到了一个方法:http://blog.csdn.net/bonnshore/article/details/8729984 虽然不是很明白,但是能把问题解决就是王道。代码如下:

    subject = email.header.make_header(email.header.decode_header(e['SUBJECT'])) #必须保证包含subject
    mail_from = email.header.make_header(email.header.decode_header(e['From']))

    8. 从Message e中解析出content正文
      同上一篇的POP一样,根据get_payload()返回的不同类型,选择解析方法,代码如下:

    maintype = e.get_content_maintype()
    if maintype == 'multipart':
        for part in e.get_payload():
        if part.get_content_maintype() == 'text':
            mail_content = part.get_payload(decode=True).strip()
    elif maintype == 'text':
        mail_content = e.get_payload(decode=True).strip()
    
    # 此时,需要把content转化成中文,利用如下方法:    
    try:
        mail_content = mail_content.decode('gbk')
    except UnicodeDecodeError:
        print('decode error')
        sys.exit(1)

    9. 至此,我们已经完成了查看是否有未读邮件。如果有的话将未读邮件的'From', 'Subject', content解析出来。正如上面完成的 mail_from, subject, mail_content一样,现在可以完美的显示,即使有中文!

    四、完整代码

    #-*- encoding: utf-8 -*-
    #-*- encoding: gbk -*-
    
    # 因为可能会用到中文,所以必须有上面的这两句话
    
    # 引入模块及IMAPClient类
    import getpass, email, sys
    from imapclient import IMAPClient
    
    hostname = 'imap.gmail.com' #gmail的smtp服务器网址
    username = 'myUserName@gmail.com'
    passwd = '***'
    
    c = IMAPClient(hostname, ssl= True) # 通过一下方式连接smtp服务器,没有考虑异常情况,详细请参考官方文档
    try:
        c.login(username, passwd) #登录个人帐号
    except c.Error:
        print('Could not log in')
        sys.exit(1)
    else:
        c.select_folder('INBOX', readonly = True) 
    # 利用select_folder()函数进行文件夹,'INBOX'为收件箱,readonly = True 表明只读并不修改任何信息
    result = c.search('UNSEEN') msgdict = c.fetch(result, ['BODY.PEEK[]'] ) # 现在已经把邮件取出来了,下面开始解析邮件 for message_id, message in msgdict.items(): e = email.message_from_string(message['BODY[]']) # 生成Message类型
         # 由于'From', 'Subject' header有可能有中文,必须把它转化为中文 subject = email.header.make_header(email.header.decode_header(e['SUBJECT'])) mail_from = email.header.make_header(email.header.decode_header(e['From']))      
         # 解析邮件正文 maintype = e.get_content_maintype() if maintype == 'multipart': for part in e.get_payload(): if part.get_content_maintype() == 'text': mail_content = part.get_payload(decode=True).strip() elif maintype == 'text': mail_content = e.get_payload(decode=True).strip()
         # 此时,需要把content转化成中文,利用如下方法: try: mail_content = mail_content.decode('gbk') except UnicodeDecodeError: print('decode error') sys.exit(1) else: print('new message') print('From: ', mail_from) print('Subject: ', subject) getstr = input('if you wanna read it, input y: ') if getstr.startswith('y'): print('-'*10, 'mail content', '-'*10) print(mail_content.replace('<br>', ' ')) print('-'*10, 'mail content', '-'*10) finally:
      c.logout()

    五、总结
      至此,我们已经学习了利用Python编写邮件服务的所有非常基本的内容,由于我的需求不是很高,目标不是做成一个功能强大的邮箱客户端,所以诸如:MIME、附件、图片等功能都没有学习,当然也没有介绍。
      因为我们现在接收的邮件,大多数都是MIME格式的,不过上文的包含了点解析MIME格式邮件的代码。详细请参考《Foundations of Python3 Network Programming. 2nd Edition》Chaper E-mail Composition and Decoding。

      

  • 相关阅读:
    linux下动态链接库.so文件 静态链接库.a文件创建及使用
    matlab 自动阈值白平衡算法 程序可编译实现
    C++ 迭代器介绍 [转摘]
    C++ Primer 第三章 标准库类型vector+迭代器iterator 运算
    matlab灰度变彩色+白平衡算法实现
    我和奇葩的故事之失联第七天
    C++ Primer 第三章 标准库类型string运算
    OpenCV白平衡算法之灰度世界法(消除RGB受光照影响)
    查看网络情况netstat指令与动态监控top指令
    linux服务
  • 原文地址:https://www.cnblogs.com/zixuan-zhang/p/3402787.html
Copyright © 2011-2022 走看看