Maison  >  Article  >  développement back-end  >  Comment implémenter un serveur de messagerie simple en utilisant Golang

Comment implémenter un serveur de messagerie simple en utilisant Golang

PHPz
PHPzoriginal
2023-04-04 17:28:031803parcourir

Avec le développement et la popularisation d'Internet, le courrier électronique est devenu un élément indispensable de la vie des gens. Cet article explique comment utiliser le langage de programmation Golang pour implémenter un serveur de messagerie simple.

1. Configuration de l'environnement

Tout d'abord, vous devez créer un environnement de développement Golang localement. Après avoir installé golang, vous devez définir GOPATH. Cette variable d'environnement spécifie le répertoire de travail de golang. Tous les fichiers créés dans ce répertoire sont considérés comme le code source de golang.

Ensuite, installez les bibliothèques POP3 et SMTP via les commandes suivantes :

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

Les deux bibliothèques ci-dessus sont utilisées respectivement pour envoyer des e-mails et gérer les requêtes HTTP.

2. Implémenter le serveur POP3

POP3 est un protocole de réception de courrier. Le courrier peut être téléchargé à partir du serveur de messagerie à l'aide du protocole POP3. Afin d'implémenter le serveur POP3, nous devons écrire le serveur TCP en golang. Dans Golang, vous pouvez utiliser le package "net" pour implémenter le développement de serveur TCP.

Ce qui suit est un simple code de serveur 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)
    }
}

Le code ci-dessus écoute localement le port 110 (port POP3 par défaut) Lorsqu'un client se connecte, une goroutine est démarrée pour gérer la connexion. Toutes les commandes reçues par le serveur POP3 sont des chaînes, nous utilisons donc le Scanner fourni par le package bufio pour analyser les commandes.

Dans la fonction handlePOP3, on envoie d'abord "+OK POP3 ready" au client pour indiquer que le serveur est prêt. Ensuite, il lit en boucle les commandes envoyées par le client. S'il rencontre une commande "QUIT", il envoie "+OK Bye" pour terminer la session et fermer la connexion. Si une autre commande inconnue est reçue, envoyez « -ERR commande inconnue » pour indiquer au client que la commande n'est pas valide.

3. Implémenter le serveur SMTP

SMTP est un protocole d'envoi d'e-mails qui peuvent être envoyés au serveur de messagerie à l'aide du protocole SMTP. Afin d'implémenter le serveur SMTP, nous devons ajouter du code basé sur le serveur POP3 pour traiter les commandes SMTP.

Ce qui suit est un simple code de serveur 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)
}

Le code ci-dessus écoute localement sur le port 25 (port SMTP par défaut) Lorsqu'un client se connecte, une goroutine est démarrée pour gérer la connexion. Toutes les commandes reçues par le serveur SMTP sont également des chaînes, nous utilisons donc la méthode Conn.Read fournie par le package net pour lire les commandes.

Dans la fonction handleSMTP, nous envoyons d'abord "220 localhost SMTP Ready" au client pour indiquer que le serveur SMTP est prêt. Ensuite, maintenez une machine d'état pour gérer le processus d'envoi de courrier :

  • État 0 : traitez la commande HELO du client et entrez dans l'état 1.
  • État 1 : traitez la commande MAIL du client et entrez dans l'état 2.
  • État 2 : traitez le client. Commande RCPT sur le client et entre dans l'état 3
  • État 3 : en attente que le client envoie une commande DATA, ou en traitement d'une commande HELO/QUIT, ou une erreur se produit, et entre dans l'état correspondant
  • État 4 : reçoit des données de courrier électronique envoyée par le client (avec " ." à la fin), envoyez un email et entrez l'état 1

Si une commande invalide est reçue, "500 Error: bad command séquence" est envoyé pour indiquer au client que la commande n'est pas valide . Si une commande QUIT est reçue, « 221 Bye » est envoyé pour indiquer la fin de la session et la connexion est fermée. Dans l'état 4, nous utilisons le package net/mail pour analyser les données de courrier électronique et le package net/smtp pour envoyer des e-mails.

4. Tests

Le serveur de boîtes aux lettres implémenté à l'aide du code ci-dessus n'est qu'un exemple simple S'il doit être utilisé dans un environnement de production réel, de nombreux aspects de test et d'optimisation sont nécessaires. Ce qui suit est un simple code client SMTP écrit en python, à travers lequel vous pouvez envoyer des e-mails à notre serveur SMTP et tester si le serveur SMTP fonctionne correctement :

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. Résumé

Cet article présente comment utiliser le langage de programmation Golang pour implémenter un serveur de messagerie simple. Utiliser Golang pour écrire du code de serveur SMTP/POP3 est facile à comprendre et à développer. En même temps, la fonctionnalité coroutine de Golang peut prendre en charge une concurrence élevée et est très adaptée au travail de développement de programmation réseau.

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn