ホームページ >バックエンド開発 >Golang >golangを使って簡単なメールサーバーを実装する方法

golangを使って簡単なメールサーバーを実装する方法

PHPz
PHPzオリジナル
2023-04-04 17:28:031866ブラウズ

インターネットの発展と普及により、電子メールは人々の生活に欠かせないものになりました。この記事では、golang プログラミング言語を使用して簡単なメール サーバーを実装する方法を紹介します。

1. 環境セットアップ

まず、ローカルに golang 開発環境をセットアップする必要があります。 golang をインストールした後、GOPATH を設定する必要があります。この環境変数は golang の作業ディレクトリを指定します。このディレクトリに作成されたすべてのファイルは golang のソース コードとみなされます。

次に、次のコマンドを使用して、POP3 ライブラリと SMTP ライブラリをインストールします。

go get github.com/jordan-wright/email
go get github.com/beego/mux

上記 2 つのライブラリは、それぞれ電子メールの送信と HTTP リクエストの処理に使用されます。

2. POP3 サーバーの実装

POP3 はメール受信プロトコルであり、POP3 プロトコルを使用してメールサーバーからメールをダウンロードできます。 POP3 サーバーを実装するには、TCP サーバーを golang で記述する必要があります。 golang では、「net」パッケージを使用して TCP サーバー開発を実装できます。

次は簡単な POP3 サーバー コードです:

package main

import (
    "bufio"
    "fmt"
    "net"
    "strings"
)

func handlePOP3(conn net.Conn) {
    fmt.Fprintf(conn, "+OK POP3 ready\r\n")
    scanner := bufio.NewScanner(conn)
    for scanner.Scan() {
        command := scanner.Text()
        if strings.HasPrefix(command, "QUIT") {
            fmt.Fprintf(conn, "+OK Bye\r\n")
            conn.Close()
            return
        }
        fmt.Fprintf(conn, "-ERR unknown command\r\n")
    }
}

func main() {
    listener, err := net.Listen("tcp", ":110")
    if err != nil {
        fmt.Println("Error listening:", err.Error())
        return
    }
    defer listener.Close()

    fmt.Println("Listening on :110")
    for {
        conn, err := listener.Accept()
        if err != nil {
            fmt.Println("Error accepting connection:", err.Error())
            return
        }
        go handlePOP3(conn)
    }
}

上記のコードはポート 110 (POP3 のデフォルト ポート) をローカルでリッスンします。クライアントが接続すると、接続を処理するために goroutine が開始されます。 。 POP3 サーバーが受信するコマンドはすべて文字列であるため、bufio パッケージが提供するスキャナーを使用してコマンドを解析します。

handlePOP3 関数では、まず「OK POP3 Ready」をクライアントに送信して、サーバーの準備ができていることを示します。その後、クライアントから送信されたコマンドをループで読み取り続け、「QUIT」コマンドに遭遇すると、「OK Bye」を送信してセッションを終了し、接続を閉じます。別の不明なコマンドを受信した場合は、「-ERR 不明なコマンド」を送信して、コマンドが無効であることをクライアントに伝えます。

3. SMTP サーバーの実装

SMTP はメール送信プロトコルであり、SMTP プロトコルを使用してメールサーバーにメールを送信できます。 SMTP サーバーを実装するには、POP3 サーバーに基づいて SMTP コマンドを処理するコードを追加する必要があります。

次は単純な SMTP サーバー コードです:

package main

import (
    "fmt"
    "net"
    "net/mail"
    "net/smtp"
)

func handlePOP3(conn net.Conn) {
    // ...
}

func handleSMTP(conn net.Conn) {
    fmt.Fprintf(conn, "220 localhost SMTP Ready\r\n")
    state := 0
    var from, to, data string
    for {
        buf := make([]byte, 1024)
        _, err := conn.Read(buf)
        if err != nil {
            fmt.Println("Error reading:", err.Error())
            return
        }
        line := string(buf)
        switch state {
        case 0:
            if line[:4] != "HELO" {
                fmt.Fprintf(conn, "500 Error: bad command sequence\r\n")
                continue
            }
            fmt.Fprintf(conn, "250 localhost\r\n")
            state = 1
        case 1:
            if line[:4] != "MAIL" {
                fmt.Fprintf(conn, "500 Error: bad command sequence\r\n")
                continue
            }
            from = line[5 : len(line)-2]
            fmt.Fprintf(conn, "250 OK\r\n")
            state = 2
        case 2:
            if line[:4] != "RCPT" {
                fmt.Fprintf(conn, "500 Error: bad command sequence\r\n")
                continue
            }
            to = line[5 : len(line)-2]
            fmt.Fprintf(conn, "250 OK\r\n")
            state = 3
        case 3:
            if line[:4] == "DATA" {
                fmt.Fprintf(conn, "354 End data with <CR><LF>.<CR><LF>\r\n")
                state = 4
            } else if line[:4] == "HELO" {
                fmt.Fprintf(conn, "250 localhost\r\n")
            } else {
                fmt.Fprintf(conn, "500 Error: bad command sequence\r\n")
            }
        case 4:
            if line[:3] == "QUIT" {
                fmt.Fprintf(conn, "221 Bye\r\n")
                conn.Close()
                return
            }
            if line == ".\r\n" {
                fmt.Fprintf(conn, "250 OK: message accepted\r\n")
                msg, _ := mail.ReadMessage(strings.NewReader(data))
                smtp.SendMail("localhost:25", nil, "test@test.com", []string{to}, []byte(data))
                state = 1
            } else {
                data += line
            }
        }
    }
}

func main() {
    // ...
    router := mux.NewRouter()
    router.HandleFunc("/pop3", handlePOP3)
    router.HandleFunc("/smtp", handleSMTP)
    http.ListenAndServe(":8080", router)
}

上記のコードは、ポート 25 (SMTP のデフォルト ポート) でローカルにリッスンします。クライアントが接続すると、接続を処理するために goroutine が開始されます。 。 SMTP サーバーが受信するすべてのコマンドも文字列であるため、net パッケージによって提供される Conn.Read メソッドを使用してコマンドを読み取ります。

handleSMTP 関数では、最初に「220 localhost SMTP Ready」をクライアントに送信して、SMTP サーバーの準備ができていることを示します。次に、メール送信プロセスを処理するステート マシンを維持します。

  • ステート 0: クライアントの HELO コマンドを処理し、ステート 1 に入ります。
  • ステート 1: クライアントの MAIL コマンドを処理し、そして状態 2 に入る
  • 状態 2: クライアントの RCPT コマンドを処理し、状態 3
  • 状態 3: クライアントが DATA コマンドを送信するのを待つか、HELO/QUIT コマンドを処理するか、またはエラーが発生し、対応する状態に移行します。
  • 状態 4: クライアントから送信された電子メール データ ("." で終わる) を受信し、電子メールを送信して、状態 1

に移行します。無効なコマンドを受信した場合は、「500 エラー: 不正なコマンド シーケンス」を送信して、コマンドが無効であることをクライアントに伝えます。 QUIT コマンドを受信すると、セッションの終了を示す「221 Bye」が送信され、接続が閉じられます。状態 4 では、net/mail パッケージを使用して電子メール データを解析し、net/smtp パッケージを使用して電子メールを送信します。

4. テスト

上記のコードを使用して実装されたメールボックス サーバーは単なる例であり、実際の運用環境で使用する場合は、多くの側面のテストと最適化が必要です。以下は、Python で書かれた単純な SMTP クライアント コードです。このクライアントを使用して、SMTP サーバーに電子メールを送信し、SMTP サーバーが適切に動作しているかどうかをテストできます:

import smtplib

server = smtplib.SMTP('localhost', 25)
server.ehlo()
server.mail('test@test.com')
server.rcpt('test1@test.com')
server.data('Subject: Test Mail\n\nThis is a test email.\n')
server.quit()

5。この記事の概要 golang プログラミング言語を使用して簡単な電子メール サーバーを実装する方法を学びます。 golang を使用して SMTP/POP3 サーバー コードを記述することは、理解と拡張が容易であると同時に、golang のコルーチン機能は高い同時実行性をサポートできるため、ネットワーク プログラミングの開発作業に非常に適しています。

以上がgolangを使って簡単なメールサーバーを実装する方法の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。