電子メールの送信


  1. 目的
    ここでは、メールサーバーを利用して、簡単なメール送信プロジェクトを作成します。メールには、専用のAPIがあり、これを利用すると、本文以外にも漢字が利用可能ですし、添付ファイルも送ることができます。
     ここでは、ネットワークソフトの入門として、簡単なメール送信プログラムを紹介します。

  2. 必要な機能
    1. smtpサーバーと送信プロトコル
      メールの送信には smtp サーバーを利用します。smtpのメール送信は、25番ポートを利用した文字列で実行できますから、telnetでポート番号を25としてメールの送信が可能です。">"はサーバーへの送信、"<"はサーバーからの応答です。#は説明で、実際には表示されません

      %telnet 
      xxx 25
      <220
      #メール送信開始
      >HELLO <domainName>
      <250 ...
      #送り主と宛先の指定
      >MAIL FROM:<mail address>
      <250 ..
      >RCPT TO::<mail address>
      <250 ..
      #本文
      >DATA..
      <354
      ><message>
      >.
      <250 ..
      #終了
      >QUIT

    2. ソケット
      メールサーバーと送受信するには、ソケットを利用します。

       Socket s = new Socket(textFieldServer.getText(), 25);

      で、jtextFieldServer にかかれたサーバー名の25番ポートのソケットを作成します。ソケットから入出力を行うクラス、in,outを取得します。

        out = new PrintWriter(s.getOutputStream());
       in = new BufferedReader(new InputStreamReader(s.getInputStream()));

      これで、
       out.write(s);
      で文字列sを送信し、
       String line = in.readLine();
      で、lineにメッセージを読むことができます。BufferedWriterなしでも送信可能ですが、一般にBufferedWriterを利用する方が、効率がよくなります。

    3. アプレットとsmtpサーバー
      アプレットではサーバーソケットを作成できませんが、クライアントとしてソケットを作成することはできます。ただし、直接接続するプロバイダのサーバーが中継機能を許してない場合(これが普通ですが)、smtpサーバーとしてプロバイダのSMTPサーバーを利用する必要があります。
       他のサーバーを利用することはできません。

  3. プロジェクト

    1. プロジェクトの生成
      アプレットで作成します。プロジェクト名をsendmailapletl で作成します。

    2. レイアウト
      from,to、Server、subject のラベルとtextFieldを配置します。from、toは、送信元と送信先のメールアドレス、Serverはメールサーバー名、subjectはメールのタイトルです。
       メールサーバーは通常のメールソフトでは設定オプションで設定しるサーバーを使用します。その下に、スクロール可能な文字編集エリア(textAreaMsg)、その下に、通信記録を保存するlogエリア(textAreaComm)を作成します。
       最後に、送信ボタンを配置します。

    3. 主要メソッド
      1. jbInit()
         部品配置用のメソッドです。これは、JBuilderX で自動生成されたものです。

      2. sendMail()
        SENDボタンで呼び出されるメソッドです。smtpのプロトコルを実行します。最初にポート25のソケットを作成し、in,out クラスを生成します。
         InetAddress.getLocalHost().getHostName();
        で、自分のホスト名を取得します。"HELO "に続けて送信します。次に、送信元と送信先のメールアドレスを送ります。以下、本文を送りますが、1行単位で送るため、StringTokenizerクラスを利用しています。これは、指定した文字(ここでは改行 "\n" )で文字列を区切りながら読み出すことができます。tokenizer.nextToken() で次の文字列(トークン)を読み出し、hasMoreTokens()で、残り文字列の有無を確認できます。
         public void sendMail()
          {
            try
            {
              Socket s = new Socket(jmaleServer.getText(), 25);
        
              out = new PrintWriter(s.getOutputStream());
              in = new BufferedReader(new InputStreamReader(s.getInputStream()));
        
              String hostName
              = InetAddress.getLocalHost().getHostName();
        
              receive();
              send("HELO " + hostName);
              receive();
              send("MAIL FROM: <" + jFromText1.getText() + ">");
              receive();
              send("RCPT TO: <" + jToText.getText() + ">");
              receive();
              send("DATA");
              receive();
              StringTokenizer tokenizer = new StringTokenizer(
                  jMessagePane1.getText(), "\n");
              while (tokenizer.hasMoreTokens())
                send(tokenizer.nextToken());
              send(".");
              receive();
              s.close();
            }
            catch (IOException exception)
            {
              jLogArea.append("Error: " + exception);
            }
          }
      3. send(String s)

        send()は、文字列sを textAreaComm 領域に記録しながら送信します。最後のout.flush()は肝要で、これをしないと、最後の改行まで出力されない場合があり、この場合、サーバーとの交信が途絶えてしまいます。
          public void send(String s) throws IOException
          {
          textAreaComm.append(">"+s);
          textAreaComm.append("\n");
          out.print(s);
          out.print("\r\n");
          out.flush();
        
          }

      4. receive()
        これは、受信した文字を、log領域に「記録する」だけです。内容の確認はしていません。実際は、先頭に送られる数値で正常に受信されているか、確認する必要があります。ここでは、エラー処理は省いています。
          public void receive() throws IOException
        {
          String line = in.readLine();
          if (line != null)
          {
            textAreaComm.append(line);
            textAreaComm.append("\n");
          }
        }

      5. buttonSend_actionPerformed(ActionEvent e)
        送信ボタンの処理を行います。受信処理を行うと、受信が完了するまで、コントロールが無効になりますから、スレッドで実行するのが望ましいのですが、ここではさぼっています。
         最初にソケットを作成し、ここから、ストリーム入出力 out,in を生成します。ソケットの作成が成功すると、サーバーからメッセージが返ります。
         250 mira2.aitai.ne.jp Hello global221-28-032.aitai.ne.jp [221.118.28.32], pleased to meet you
        これを recieve 受信し、 HELO コマンドを送ります。
         以後、メールのプロトコルにしたがい、処理を進めます。BODY 以後本文を送りますが、最初に

         Subject :文字列

        の行を送ると、メールのタイトル(Subject)として扱われます。

      6. class Applet1_buttonSend_actionAdapter
         これは、ボタン処理用のイベント処理メソッドを定義するアダプタクラスです。これは、JbuilderX で自動生成されたコードです。

      7. ソース

        package sendmailaplet;
        
        import java.awt.*;
        import java.awt.event.*;
        import java.applet.*;
        import java.util.*;
        import java.net.*;
        import java.io.*;
        
        
        public class Applet1 extends Applet {
        
          private BufferedReader in;
          private PrintWriter out;
        
          private boolean isStandalone = false;
          TextField textFieldFrom = new TextField();
          Label label1 = new Label();
          TextField textFieldTo = new TextField();
          TextField textFieldServer = new TextField();
          TextArea textAreaMessage = new TextArea();
          TextArea textAreaComm = new TextArea();
          Label label2 = new Label();
          Label label3 = new Label();
          Label label4 = new Label();
          Label label5 = new Label();
          Button buttonSend = new Button();
          TextField textFieldSbj = new TextField();
          Label label6 = new Label();
        
        
          //Construct the applet
          public Applet1() {
          }
        
          //Initialize the applet
          public void init() {
            try {
              jbInit();
            }
            catch(Exception e) {
              e.printStackTrace();
            }
          }
        
          //Component initialization
          private void jbInit() throws Exception {
            textFieldFrom.setText("yt7m-itu@asahi-net.or.jp");
            textFieldFrom.setBounds(new Rectangle(96, 18, 239, 18));
            this.setLayout(null);
            label1.setText("from");
            label1.setBounds(new Rectangle(36, 23, 61, 18));
            textFieldTo.setText("yt7m-itu@asahi-net.or.jp");
            textFieldTo.setBounds(new Rectangle(99, 40, 238, 20));
            textFieldServer.setText("hm.aitai.ne.jp");
            textFieldServer.setBounds(new Rectangle(101, 65, 169, 17));
            textAreaMessage.setText("textArea1");
            textAreaMessage.setBounds(new Rectangle(27, 132, 342, 92));
            textAreaComm.setBounds(new Rectangle(25, 243, 346, 129));
            label2.setText("message");
            label2.setBounds(new Rectangle(23, 109, 86, 20));
            label3.setText("communication");
            label3.setBounds(new Rectangle(15, 223, 71, 21));
            label4.setText("to");
            label4.setBounds(new Rectangle(39, 39, 52, 20));
            label5.setText("server");
            label5.setBounds(new Rectangle(38, 66, 55, 18));
            buttonSend.setLabel("send");
            buttonSend.setBounds(new Rectangle(294, 71, 62, 33));
            buttonSend.addActionListener(new Applet1_buttonSend_actionAdapter(this));
            textFieldSbj.setText("subject");
            textFieldSbj.setBounds(new Rectangle(99, 92, 168, 16));
            label6.setText("subject");
            label6.setBounds(new Rectangle(33, 91, 51, 16));
            this.add(label1, null);
            this.add(textAreaComm, null);
            this.add(textAreaMessage, null);
            this.add(label2, null);
            this.add(label3, null);
            this.add(label4, null);
            this.add(textFieldServer, null);
            this.add(textFieldSbj, null);
            this.add(buttonSend, null);
            this.add(textFieldTo, null);
            this.add(label5, null);
            this.add(label6, null);
            this.add(textFieldFrom, null);
          }
        
        
          void buttonSend_actionPerformed(ActionEvent e) {
            try
            {
              Socket s = new Socket(textFieldServer.getText(), 25);
        
              out = new PrintWriter(s.getOutputStream());
              in = new BufferedReader(new InputStreamReader(s.getInputStream()));
        
              String hostName
              = InetAddress.getLocalHost().getHostName();
        
              receive();
              send("HELO " + hostName);
              receive();
              send("MAIL FROM: <" + textFieldFrom.getText() + ">");
              receive();
              send("RCPT TO: <" + textFieldTo.getText() + ">");
              receive();
              send("DATA");
              receive();
              send("Subject :" + textFieldSbj.getText());
              send("");
              StringTokenizer tokenizer = new StringTokenizer(
                  textAreaMessage.getText(), "\n");
              while (tokenizer.hasMoreTokens())
                send(tokenizer.nextToken());
              send(".");
              receive();
        
              send("QUIT");
              receive();
        
              s.close();
            }
            catch (IOException exception)
            {
              textAreaComm.append("Error: " + exception);
            }
        
          }
        
        
        public void send(String s) throws IOException
        {
          textAreaComm.append(">"+s);
          textAreaComm.append("\n");
          out.print(s);
          out.print("\r\n");
          out.flush();
        }
        
        
        public void receive() throws IOException
        {
          String line = in.readLine();
          if (line != null)
          {
            textAreaComm.append(line);
            textAreaComm.append("\n");
          }
        }
        
        }
        
        class Applet1_buttonSend_actionAdapter implements java.awt.event.ActionListener {
          Applet1 adaptee;
        
          Applet1_buttonSend_actionAdapter(Applet1 adaptee) {
            this.adaptee = adaptee;
          }
          public void actionPerformed(ActionEvent e) {
            adaptee.buttonSend_actionPerformed(e);
          }
        }
        

    4. 実行
      fromとtoに送信元と送信先のメールアドレスを指定します。smtpサーバーは、アプレットを実行するPCが所属するドメインのsmtpサーバーを利用しないと、送信拒否される場合があります。本文は漢字が利用できますが、subjectを含めその他の欄には漢字は利用できません。
       messageエリアにもメッセージを書き込み、「送信ボタンを押します。経過は「comm」エリアに表示されます。下記のような表示があれば送信できています。受信は一般のメールソフトで確認して下さい。

      220 mira2.aitai.ne.jp ESMTP Mirapoint 3.3.3-GR; Sun, 6 Feb 2005 15:26:10 +0900 (JST)
      >HELO makoto
      250 mira2.aitai.ne.jp Hello global221-28-032.aitai.ne.jp [221.118.28.32], pleased to meet you
      >MAIL FROM: <mito@ccad.sccs.chukyo-u.ac.jp>
      250 <mito@ccad.sccs.chukyo-u.ac.jp>... Sender ok
      >RCPT TO: <yt7m-itu@asahi-net.or.jp>
      250 <yt7m-itu@asahi-net.or.jp>... Recipient ok
      >DATA
      354 Enter mail, end with "." on a line by itself
      >Subject :subject3
      >
      >textArea1z
      >.
      250 BYY09422 Message accepted for delivery
      >QUIT
      221 mira2.aitai.ne.jp closing connection


    5. 実行
       このアプレット、自宅のブラウザ上で自宅のプロバイダを利用した送信は成功しましたが、web サーバー上でのメール送信は失敗します。