• go smtp示例


    书接上文邮件实现详解,这里我们及我们简单复习一下smtp的指令如下:

    telnet smtp.163.com 25
    [outpout]
    ehlo dz45693
    [outpout]
    auth login
    [outpout]
    输入用户名base64
    [outpout]
    输入密码base64
    mail from:<dz45693@163.com>
    [outpout]
    rcpt to:<dz45693@sina.com>
    [outpout]
    data
    [outpout]
    from:<dz45693@163.com>
    to:<dz45693@sina.com>
    subject:hello world
     
    This is the first email sent by hand using the SMTP protocol
    .
    quit  

    好,那我们下现在用go实现代码让如下: 这里只是一个demo,主要熟悉smtp命令

    package main
    
    import (
        "bufio"
        "encoding/base64"
        "fmt"
        "net"
        "strconv"
        "strings"
    )
    
    
    func main() {
        testSmtp()
    }
    
    var gConn net.Conn
    var gRead *bufio.Reader
    var gWrite *bufio.Writer
    
    //可以放到这样的类里
    type TcpClient struct {
        Conn  net.Conn
        Read  *bufio.Reader
        Write *bufio.Writer
    } //
    
    func Connect(host string, port int) (net.Conn, *bufio.Reader, *bufio.Writer) {
        addr := host + ":" + strconv.Itoa(port)
        conn, err := net.Dial("tcp", addr)
        if err != nil {
            return nil, nil, nil
        }
    
        reader := bufio.NewReader(conn)
        writer := bufio.NewWriter(conn)
    
        return conn, reader, writer
    } //
    
    //收取一行,可再优化
    func RecvLine() string {
        line, err := gRead.ReadString('\n') //如何设定超时?
        if err != nil {
            fmt.Print(err)
            return ""
        }
    
        line = strings.Split(line, "\r")[0] //还要再去掉 "\r",其实不去掉也可以
        return line
    }
    
    func SendLine(line string) {
        gWrite.WriteString(line + "\r\n")
        gWrite.Flush()
    }
    
    //解码一行命令,这里比较简单就是按空格进行分隔就行了
    func DecodeCmd(line string, sp string) []string {
        tmp := strings.Split(line, sp)
        var cmds = []string{"", "", "", "", ""} //先定义多几个,以面后面使用时产生异常
        for i := 0; i < len(tmp); i++ {
            if i >= len(cmds) {
                break
            }
            cmds[i] = tmp[i]
        }
        return cmds
    }
    
    //读取多行结果
    func RecvMCmd() string {
        i := 0
        rs := ""
        mLine := ""
        for i = 0; i < 50; i++ {
            rs = RecvLine() //只收取一行
            mLine = mLine + rs + "\r\n"
            if len(rs) < 4 {
                break
            } //长度要足够
            c4 := rs[4-1] //第4个字符
            if ' ' == c4 {
                break
            } //第4个字符是空格就表示读取完了//也可以判断 "250[空格]"
        }
        return mLine
    }
    
    //简单的测试一下 smtp
    func testSmtp() {
        //连接
        gConn, gRead, gWrite = Connect("smtp.163.com", 25)
        defer gConn.Close()
        //收取一行
        line := RecvLine()
        fmt.Println("recv:" + line)
    
        //解码一下,这样后面的 EHLO 才能有正确的第二个参数
        cmds := DecodeCmd(line, " ")
        domain := cmds[1] //要从对方的应答中取出域名//空格分开的各个命令参数中的第二个
    
        //发送一个命令
        SendLine("EHLO" + " " + domain) //domain 要求其实来自 HELO 命令//HELO <SP> <domain> <CRLF>
    
        //收取多行
        line = RecvMCmd()
        fmt.Println("recv:" + line)
    
        //--------------------------------------------------
        //用 base64 登录
        SendLine("AUTH LOGIN")
    
        //收取一行
        line = RecvLine()
        fmt.Println("recv:" + line)
    
        s := "dz45693" //要换成你的用户名,注意 163 邮箱的话不要带后面的 @域名 部分
        s = base64.StdEncoding.EncodeToString([]byte(s))
        SendLine(s)
    
        //收取一行
        line = RecvLine()
        fmt.Println("recv:" + line)
    
        s = "xxxxx" //要换成您的密码
        s = base64.StdEncoding.EncodeToString([]byte(s))
        SendLine(s)
    
        //收取一行
        line = RecvLine()
        fmt.Println("recv:" + line)
    
        //--------------------------------------------------
        //邮件内容
        from := "dz45693@163.com"
        to := "dz45693@sina.com"
    
        SendLine("MAIL FROM: <" + from + ">") //注意"<" 符号和前面的空格。空格在协议中有和没有都可能,最好还是有
        //收取一行
        line = RecvLine()
        fmt.Println("recv:" + line)
    
        SendLine("RCPT TO: <" + to + ">")
        //收取一行
        line = RecvLine()
        fmt.Println("recv:" + line)
    
        SendLine("DATA")
        //收取一行
        line = RecvLine()
        fmt.Println("recv:" + line)
    
    //发送邮件头
        SendLine("from:<dz45693@163.com>")
        SendLine("to:<dz45693@sina.com>")
        SendLine("subject:hello world")
        SendLine("") //发送空行 后面就是邮件体
        SendLine("This is the first email sent by hand using the SMTP protocol")
    
        SendLine(".") //邮件结束符
    
        //收取一行
        line = RecvLine()
        fmt.Println("recv:" + line)
    
        SendLine("quit") //链接推出
        line = RecvLine()
        fmt.Println("recv:" + line)
    } //

    运行结果图下:

     在go的sdk中提供了SendMail方法【发送邮件后这个方法会关闭链接】,实现如下:

    实现如下:

    func SendMailBySmtp(){
        auth := smtp.PlainAuth("", "dz45693@163.com", "xxx", "smtp.163.com")
        to := []string{"dz45693@sina.com"}
        image,_:=ioutil.ReadFile("d:\\Downloads\\1.png")
        imageBase64:=base64.StdEncoding.EncodeToString(image)
        msg := []byte("from:dz45693@163.com\r\n"+
            "to: dz45693@sina.com\r\n" +
            "Subject: hello,subject!\r\n"+
            "Content-Type:multipart/mixed;boundary=a\r\n"+
            "Mime-Version:1.0\r\n"+
            "\r\n" +
            "--a\r\n"+
            "Content-type:text/plain;charset=utf-8\r\n"+
            "Content-Transfer-Encoding:quoted-printable\r\n"+
            "\r\n"+
            "此处为正文内容!\r\n"+
            "--a\r\n"+
            "Content-type:image/jpg;name=1.jpg\r\n"+
            "Content-Transfer-Encoding:base64\r\n"+
            "\r\n"+
            imageBase64+"\r\n"+
            "--a--\r\n")
        err := smtp.SendMail("smtp.163.com:25", auth, "dz45693@163.com", to, msg)
        if err != nil {
            fmt.Println(err)
        }
    }

    运行效果

     示例如下:

    func SendMailByGomailOne(){
        m := gomail.NewMessage()
        m.SetAddressHeader("From", "dz45693@163.com", "dz45693")
        m.SetHeader("To", "dz45693@sina.com")
        m.SetHeader("Subject", "hello SendMailByGomailOne!")
        m.Embed("d:\\Downloads\\1.png")
        m.SetBody("text/html", "此处为正文121333!")
    
        d := gomail.NewDialer("smtp.163.com", 25, "dz45693@163.com", "xxxx#")
    
        if err := d.DialAndSend(m); err != nil {
            panic(err)
        }
    }

    运行结果:

    来我们看看DialAndSend的实现如下: 

    package gomail
    
    import (
        "crypto/tls"
        "fmt"
        "io"
        "net"
        "net/smtp"
        "strings"
        "time"
    )
    
    // A Dialer is a dialer to an SMTP server.
    type Dialer struct {
        // Host represents the host of the SMTP server.
        Host string
        // Port represents the port of the SMTP server.
        Port int
        // Username is the username to use to authenticate to the SMTP server.
        Username string
        // Password is the password to use to authenticate to the SMTP server.
        Password string
        // Auth represents the authentication mechanism used to authenticate to the
        // SMTP server.
        Auth smtp.Auth
        // SSL defines whether an SSL connection is used. It should be false in
        // most cases since the authentication mechanism should use the STARTTLS
        // extension instead.
        SSL bool
        // TSLConfig represents the TLS configuration used for the TLS (when the
        // STARTTLS extension is used) or SSL connection.
        TLSConfig *tls.Config
        // LocalName is the hostname sent to the SMTP server with the HELO command.
        // By default, "localhost" is sent.
        LocalName string
    }
    
    // NewDialer returns a new SMTP Dialer. The given parameters are used to connect
    // to the SMTP server.
    func NewDialer(host string, port int, username, password string) *Dialer {
        return &Dialer{
            Host:     host,
            Port:     port,
            Username: username,
            Password: password,
            SSL:      port == 465,
        }
    }
    
    // NewPlainDialer returns a new SMTP Dialer. The given parameters are used to
    // connect to the SMTP server.
    //
    // Deprecated: Use NewDialer instead.
    func NewPlainDialer(host string, port int, username, password string) *Dialer {
        return NewDialer(host, port, username, password)
    }
    
    // Dial dials and authenticates to an SMTP server. The returned SendCloser
    // should be closed when done using it.
    func (d *Dialer) Dial() (SendCloser, error) {
        conn, err := netDialTimeout("tcp", addr(d.Host, d.Port), 10*time.Second)
        if err != nil {
            return nil, err
        }
    
        if d.SSL {
            conn = tlsClient(conn, d.tlsConfig())
        }
    
        c, err := smtpNewClient(conn, d.Host)
        if err != nil {
            return nil, err
        }
    
        if d.LocalName != "" {
            if err := c.Hello(d.LocalName); err != nil {
                return nil, err
            }
        }
    
        if !d.SSL {
            if ok, _ := c.Extension("STARTTLS"); ok {
                if err := c.StartTLS(d.tlsConfig()); err != nil {
                    c.Close()
                    return nil, err
                }
            }
        }
    
        if d.Auth == nil && d.Username != "" {
            if ok, auths := c.Extension("AUTH"); ok {
                if strings.Contains(auths, "CRAM-MD5") {
                    d.Auth = smtp.CRAMMD5Auth(d.Username, d.Password)
                } else if strings.Contains(auths, "LOGIN") &&
                    !strings.Contains(auths, "PLAIN") {
                    d.Auth = &loginAuth{
                        username: d.Username,
                        password: d.Password,
                        host:     d.Host,
                    }
                } else {
                    d.Auth = smtp.PlainAuth("", d.Username, d.Password, d.Host)
                }
            }
        }
    
        if d.Auth != nil {
            if err = c.Auth(d.Auth); err != nil {
                c.Close()
                return nil, err
            }
        }
    
        return &smtpSender{c, d}, nil
    }
    
    func (d *Dialer) tlsConfig() *tls.Config {
        if d.TLSConfig == nil {
            return &tls.Config{ServerName: d.Host}
        }
        return d.TLSConfig
    }
    
    func addr(host string, port int) string {
        return fmt.Sprintf("%s:%d", host, port)
    }
    
    // DialAndSend opens a connection to the SMTP server, sends the given emails and
    // closes the connection.
    func (d *Dialer) DialAndSend(m ...*Message) error {
        s, err := d.Dial()
        if err != nil {
            return err
        }
        defer s.Close()
    
        return Send(s, m...)
    }
    
    type smtpSender struct {
        smtpClient
        d *Dialer
    }
    
    func (c *smtpSender) Send(from string, to []string, msg io.WriterTo) error {
        if err := c.Mail(from); err != nil {
            if err == io.EOF {
                // This is probably due to a timeout, so reconnect and try again.
                sc, derr := c.d.Dial()
                if derr == nil {
                    if s, ok := sc.(*smtpSender); ok {
                        *c = *s
                        return c.Send(from, to, msg)
                    }
                }
            }
            return err
        }
    
        for _, addr := range to {
            if err := c.Rcpt(addr); err != nil {
                return err
            }
        }
    
        w, err := c.Data()
        if err != nil {
            return err
        }
    
        if _, err = msg.WriteTo(w); err != nil {
            w.Close()
            return err
        }
    
        return w.Close()
    }
    
    func (c *smtpSender) Close() error {
        return c.Quit()
    }
    
    // Stubbed out for tests.
    var (
        netDialTimeout = net.DialTimeout
        tlsClient      = tls.Client
        smtpNewClient  = func(conn net.Conn, host string) (smtpClient, error) {
            return smtp.NewClient(conn, host)
        }
    )
    
    type smtpClient interface {
        Hello(string) error
        Extension(string) (bool, string)
        StartTLS(*tls.Config) error
        Auth(smtp.Auth) error
        Mail(string) error
        Rcpt(string) error
        Data() (io.WriteCloser, error)
        Quit() error
        Close() error
    }

     DialAndSend ,首先调用Dial方法创建连接,然后发送邮件,最后关闭链接,如果要频繁发邮件,那么是否保持长连接更好了?这里的Dial 调用了smtp.NewClient 创建smtp.Client对象c,然后调用c.Hello ,c.Auth,send 实际是调用c.Mail,c.Rcpt,c.Data,那么我们可以自己调用Dial方法 然后循环调用send方法,最后在close。

    代码如下:

    func SendMailByGomailTwo() {
        d := gomail.NewDialer("smtp.163.com", 25, "dz45693@163.com", "xxx")
        m := gomail.NewMessage()
        m.SetAddressHeader("From", "dz45693@163.com", "dz45693")
        m.SetHeader("To", "dz45693@sina.com")
        m.SetHeader("Subject", "hello SendMailByGomailtwo!")
        m.Embed("d:\\Downloads\\1.png")
        m.SetBody("text/html", "此处为正文121333!SendMailByGomailtwo")
    
        s, err := d.Dial()
        if err != nil {
            panic(err)
        }
        defer s.Close()
    
        err = gomail.Send(s, m)
        if err != nil {
            panic(err)
        }
    
        m.Reset()
        m.SetAddressHeader("From", "dz45693@163.com", "dz45693")
        m.SetHeader("To", "dz45693@sina.com")
        m.SetHeader("Subject", "hello SendMailByGomailthree!")
        m.Embed("d:\\Downloads\\2.png")
        m.SetBody("text/html", "此处为正文1SendMailByGomailthreeSendMailByGomailthree!")
        err = gomail.Send(s, m)
        if err != nil {
            panic(err)
        }
    }

    运行结果:

  • 相关阅读:
    忙活了半宿,写了个小玩意
    luogu P5171 Earthquake
    luogu P1850 换教室
    luogu P2507 [SCOI2008]配对 |动态规划
    luogu P3830 [SHOI2012]随机树
    luogu P3959 宝藏
    牛客竞赛-比赛
    牛客竞赛-Who killed Cock Robin
    luogu P3807 【模板】卢卡斯定理
    牛客竞赛 -被3整除的子序列
  • 原文地址:https://www.cnblogs.com/majiang/p/16001409.html
Copyright © 2020-2023  润新知