POP3に接続するにはポートを110番に指定しなくてはいけません。
また、POP3に送るコマンドは次のようなものがあります。
USER username | ユーザー名の指定 |
PASS password | パスワード入力 |
LIST n | n番のメールのサイズ 番号を省略するとすべてのメールのサイズ |
RETR n | n番のメールを読む |
DELE n | n番のメールをサーバーから削除する |
RSET | メッセージ番号の付け直し(メールを削除した場合) |
QUIT | 通信の終了 |
さて、sendでコマンドを送ってrecvで受け取るのは同じですが、RETRを送った場合 バッファの大きさによりメールの全文がrecvで受け取れるわけではありません(つまりrecvの 2番目の引数のサイズに依存します)。あらかじめ受け取るメールのサイズを調べておいて、動的に バッファのサイズを確保するのが正しいと思われますが、ここではあらかじめ50キロほど グローパル変数に確保しておいて、足りなければ再度recv関数を呼ぶ事にします。
メニューに「受信」と「POP3の設定」が増えました。ダイアログボックス "MYPOPDLG"が増えました。// mail02.rcの一部 ///////////////////////////////////////////////////////////////////////////// // // Menu // MYMENU MENU DISCARDABLE BEGIN POPUP "ファイル(&F)" BEGIN MENUITEM "送信(&S)...", IDM_CONNECT MENUITEM "受信(&R)...", IDM_RCV MENUITEM SEPARATOR MENUITEM "終了(&X)...", IDM_END END POPUP "オプション(&O)" BEGIN MENUITEM "SMTPの設定(&S)...", IDM_SETSMTP MENUITEM "POP3の設定(&P)...", IDM_SETPOP END END ///////////////////////////////////////////////////////////////////////////// // // Dialog // MYDLG DIALOG DISCARDABLE 0, 0, 187, 53 STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "文字列入力" FONT 9, "MS Pゴシック" BEGIN EDITTEXT IDC_EDIT1,7,7,173,20,ES_AUTOHSCROLL DEFPUSHBUTTON "OK",IDOK,7,32,50,14 PUSHBUTTON "キャンセル",IDCANCEL,130,32,50,14 END MYSETDLG DIALOG DISCARDABLE 0, 0, 187, 89 STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "設定" FONT 9, "MS Pゴシック" BEGIN EDITTEXT IDC_EDIT1,78,7,102,12,ES_AUTOHSCROLL EDITTEXT IDC_EDIT2,78,25,102,12,ES_AUTOHSCROLL EDITTEXT IDC_EDIT3,78,43,102,12,ES_AUTOHSCROLL DEFPUSHBUTTON "OK",IDOK,27,68,50,14 PUSHBUTTON "キャンセル",IDCANCEL,103,68,50,14 LTEXT "SMTPアドレス",IDC_STATIC,7,7,41,8 LTEXT "差出人メールアドレス",IDC_STATIC,7,29,63,8 LTEXT "返信",IDC_STATIC,7,47,15,8 END MYPOPDLG DIALOG DISCARDABLE 0, 0, 158, 77 STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "POP3設定" FONT 9, "MS Pゴシック" BEGIN EDITTEXT IDC_EDIT1,53,7,98,12,ES_AUTOHSCROLL EDITTEXT IDC_EDIT2,53,22,98,12,ES_AUTOHSCROLL EDITTEXT IDC_EDIT3,53,37,98,12,ES_PASSWORD | ES_AUTOHSCROLL DEFPUSHBUTTON "OK",IDOK,7,56,50,14 PUSHBUTTON "キャンセル",IDCANCEL,101,56,50,14 LTEXT "POP3サーバー",IDC_STATIC,7,11,44,8 LTEXT "ユーザー名",IDC_STATIC,7,26,35,8 LTEXT "パスワード",IDC_STATIC,7,41,32,8 END
インクルードしているヘッダファイルが少し増えました。(atoi関数を使う関係) また、自作関数MyRcv, GetMailSizeが増えました。// mail02.cpp #ifndef STRICT #define STRICT #endif #ifndef WIN32_LEAN_AND_MEAN #define WIN32_LEAN_AND_MEAN #endif #include <windows.h> #include <winsock2.h> #include <windowsx.h> #include <stdio.h> #include <stdlib.h> #include "resource.h" LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK MyDlgProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK MySettingProc(HWND, UINT, WPARAM, LPARAM); LRESULT CALLBACK MyPopSetProc(HWND, UINT, WPARAM, LPARAM); ATOM InitApp(HINSTANCE); BOOL InitInstance(HINSTANCE, int); void MyConnect(HWND); void MyRcv(HWND); int GetMailSize(char *); char szClassName[] = "mail02"; //ウィンドウクラス char szStr[1024], szStrRcv[1024 * 50]; char szServerName[256], szFrom[256], szReplyTo[256];//SMTP char szPopServer[256], szUserName[64], szPass[64];//POP3 HINSTANCE hInst;
これは、いつもと同じです。int WINAPI WinMain(HINSTANCE hCurInst, HINSTANCE hPrevInst, LPSTR lpsCmdLine, int nCmdShow) { MSG msg; hInst = hCurInst; if (!InitApp(hCurInst)) return FALSE; if (!InitInstance(hCurInst, nCmdShow)) return FALSE; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; } //ウィンドウ・クラスの登録 ATOM InitApp(HINSTANCE hInst) { WNDCLASSEX wc; wc.cbSize = sizeof(WNDCLASSEX); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; //プロシージャ名 wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst;//インスタンス wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); wc.lpszMenuName = "MYMENU"; //メニュー名 wc.lpszClassName = (LPCSTR)szClassName; wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); return (RegisterClassEx(&wc)); } //ウィンドウの生成 BOOL InitInstance(HINSTANCE hInst, int nCmdShow) { HWND hWnd; hWnd = CreateWindow(szClassName, "猫でもわかるメーラー", //タイトルバーにこの名前が表示されます WS_OVERLAPPEDWINDOW, //ウィンドウの種類 CW_USEDEFAULT, //X座標 CW_USEDEFAULT, //Y座標 CW_USEDEFAULT, //幅 CW_USEDEFAULT, //高さ NULL, //親ウィンドウのハンドル、親を作るときはNULL NULL, //メニューハンドル、クラスメニューを使うときはNULL hInst, //インスタンスハンドル NULL); if (!hWnd) return FALSE; ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); return TRUE; }
メニューからIDM_SETPOPが選択されたら、"MYPOPDLG"ダイアログを出します。//ウィンドウプロシージャ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) { int id; switch (msg) { case WM_COMMAND: switch (LOWORD(wp)) { case IDM_END: SendMessage(hWnd, WM_CLOSE, 0, 0); break; case IDM_SETSMTP: DialogBox(hInst, "MYSETDLG", hWnd, (DLGPROC)MySettingProc); break; case IDM_SETPOP: DialogBox(hInst, "MYPOPDLG", hWnd, (DLGPROC)MyPopSetProc); break; case IDM_CONNECT: MyConnect(hWnd); break; case IDM_RCV: MyRcv(hWnd); break; } break; case WM_CLOSE: id = MessageBox(hWnd, "終了してもよいですか", "終了確認", MB_YESNO | MB_ICONQUESTION); if (id == IDYES) { DestroyWindow(hWnd); } break; case WM_DESTROY: PostQuitMessage(0); break; default: return (DefWindowProc(hWnd, msg, wp, lp)); } return 0; }
IDM_RCVが選択されたら自作関数MyRcvを呼び出します。
これは、前章と同じです。void MyConnect(HWND hWnd) { WSADATA wsaData; LPHOSTENT lpHost; LPSERVENT lpServ; SOCKET s; int iProtocolPort, iRtn; SOCKADDR_IN sockadd; char szTo[256], szSubject[256]; if (WSAStartup(MAKEWORD(1, 1), &wsaData) != 0) { MessageBox(hWnd, "エラーです", "Error", MB_OK); return; } if (strcmp(szServerName, "") == 0) { MessageBox(hWnd, "サーバーアドレスを入力してください。", "サーバー", MB_OK); DialogBox(hInst, "MYDLG", hWnd, (DLGPROC)MyDlgProc); strcpy(szServerName, szStr); } lpHost = gethostbyname(szServerName); if (lpHost == NULL) { wsprintf(szStr, "%sが見つかりません", szServerName); MessageBox(hWnd, szStr, "Error", MB_OK); return; } s = socket(PF_INET, SOCK_STREAM, 0); if (s == INVALID_SOCKET) { MessageBox(hWnd, "ソケットをオープンできません", "Error", MB_OK); return; } lpServ = getservbyname("mail", NULL); if (lpServ == NULL) { MessageBox(hWnd, "ポート指定がされていないので、デフォルトを使います", "OK", MB_OK); iProtocolPort = htons(IPPORT_SMTP); } else { iProtocolPort = lpServ->s_port; } sockadd.sin_family = AF_INET; sockadd.sin_port = iProtocolPort; sockadd.sin_addr = *((LPIN_ADDR)*lpHost->h_addr_list); if (connect(s, (PSOCKADDR)&sockadd, sizeof(sockadd))) { MessageBox(hWnd, "サーバーソケットに接続失敗", "Error", MB_OK); return; } memset(szStrRcv, '\0', sizeof(szStrRcv)); recv(s, szStrRcv, sizeof(szStrRcv), 0); MessageBox(hWnd, szStrRcv, "Server", MB_OK); strcpy(szStr, "HELO "); strcat(szStr, szServerName); strcat(szStr, "\r\n"); send(s, szStr, strlen(szStr), 0); memset(szStrRcv, '\0', sizeof(szStrRcv)); recv(s, szStrRcv, sizeof(szStrRcv), 0); MessageBox(hWnd, szStrRcv, "Server", MB_OK); if (strcmp(szFrom, "") == 0) { MessageBox(hWnd, "差出人メールアドレスを入力してください", "差出人", MB_OK); DialogBox(hInst, "MYDLG", hWnd, (DLGPROC)MyDlgProc); strcpy(szFrom, szStr); } wsprintf(szStr, "MAIL FROM : <%s>\r\n", szFrom); send(s, szStr, strlen(szStr), 0); memset(szStrRcv, '\0', sizeof(szStrRcv)); recv(s, szStrRcv, sizeof(szStrRcv), 0); MessageBox(hWnd, szStrRcv, "Server", MB_OK); MessageBox(hWnd, "あて先メールアドレスを入力してください", "あて先", MB_OK); DialogBox(hInst, "MYDLG", hWnd, (DLGPROC)MyDlgProc); strcpy(szTo, szStr); wsprintf(szStr, "RCPT TO :<%s>\r\n", szTo); send(s, szStr, strlen(szStr), 0); memset(szStrRcv, '\0', sizeof(szStrRcv)); recv(s, szStrRcv, sizeof(szStrRcv), 0); MessageBox(hWnd, szStrRcv, "Server", MB_OK); strcpy(szStr, "DATA\r\n"); send(s, szStr, strlen(szStr), 0); memset(szStrRcv, '\0', sizeof(szStrRcv)); recv(s, szStrRcv, sizeof(szStrRcv), 0); MessageBox(hWnd, szStrRcv, "Server", MB_OK); strcpy(szStr, "X-Mailer: Nekodemo_Wakaru-Mailer\r\n"); send(s, szStr, strlen(szStr), 0); if (strcmp(szReplyTo, "") != 0) { wsprintf(szStr, "Reply-To: %s\r\n", szReplyTo); send(s, szStr, strlen(szStr), 0); } MessageBox(hWnd, "件名を入力してください", "件名", MB_OK); DialogBox(hInst, "MYDLG", hWnd, (DLGPROC)MyDlgProc); strcpy(szSubject, szStr); wsprintf(szStr, "Subject: %s \r\n", szSubject); send(s, szStr, strlen(szStr), 0); //ここでもう一度「\r\n」を送信しておかないと //本文に日本語を使う場合1行目が文字化けします strcpy(szStr, "\r\n"); send(s, szStr, strlen(szStr), 0); while (1) { MessageBox(hWnd, "本文を1行ずつ記入して「OK」ボタンを押してください。\n" "記入が終わったら「キャンセル」ボタンを押してください。", "本文", MB_OK); iRtn = DialogBox(hInst, "MYDLG", hWnd, (DLGPROC)MyDlgProc); if (iRtn == IDCANCEL) break; strcat(szStr, "\r\n"); send(s, szStr, strlen(szStr), 0); } strcpy(szStr, ".\r\n"); send(s, szStr, strlen(szStr), 0); memset(szStrRcv, '\0', sizeof(szStrRcv)); recv(s, szStrRcv, sizeof(szStrRcv), 0); MessageBox(hWnd, szStrRcv, "Server", MB_OK); strcpy(szStr, "QUIT\r\n"); send(s, szStr, strlen(szStr), 0); memset(szStrRcv, '\0', sizeof(szStrRcv)); recv(s, szStrRcv, sizeof(szStrRcv), 0); MessageBox(hWnd, szStrRcv, "Server", MB_OK); closesocket(s); WSACleanup(); MessageBox(hWnd, "通信を終了しました", "OK", MB_OK); return; } LRESULT CALLBACK MyDlgProc(HWND hDlg, UINT msg, WPARAM wp, LPARAM lp) { static HWND hEdit; switch (msg) { case WM_INITDIALOG: hEdit = GetDlgItem(hDlg, IDC_EDIT1); return TRUE; case WM_COMMAND: switch (LOWORD(wp)) { case IDOK: Edit_GetText(hEdit, szStr, sizeof(szStr)); EndDialog(hDlg, IDOK); return TRUE; case IDCANCEL: EndDialog(hDlg, IDCANCEL); return TRUE; } return FALSE; } return FALSE; } LRESULT CALLBACK MySettingProc(HWND hDlg, UINT msg, WPARAM wp, LPARAM lp) { static HWND hServerName, hFrom, hReplyTo; switch (msg) { case WM_INITDIALOG: hServerName = GetDlgItem(hDlg, IDC_EDIT1); hFrom = GetDlgItem(hDlg, IDC_EDIT2); hReplyTo = GetDlgItem(hDlg, IDC_EDIT3); Edit_SetText(hServerName, szServerName); Edit_SetText(hFrom, szFrom); Edit_SetText(hReplyTo, szReplyTo); return TRUE; case WM_COMMAND: switch (LOWORD(wp)) { case IDOK: Edit_GetText(hServerName, szServerName, sizeof(szServerName)); Edit_GetText(hFrom, szFrom, sizeof(szFrom)); Edit_GetText(hReplyTo, szReplyTo, sizeof(szReplyTo)); if (strcmp(szReplyTo, "") == 0) strcpy(szReplyTo, szFrom); EndDialog(hDlg, IDOK); return TRUE; case IDCANCEL: EndDialog(hDlg, IDCANCEL); return TRUE; } return FALSE; } return FALSE; }
POP3設定のダイアログボックスのプロシージャです。特に説明はいりませんね。LRESULT CALLBACK MyPopSetProc(HWND hDlg, UINT msg, WPARAM wp, LPARAM lp) { static HWND hPopServer, hUserName, hPass; switch (msg) { case WM_INITDIALOG: hPopServer = GetDlgItem(hDlg, IDC_EDIT1); hUserName = GetDlgItem(hDlg, IDC_EDIT2); hPass = GetDlgItem(hDlg, IDC_EDIT3); Edit_SetText(hPopServer, szPopServer); Edit_SetText(hUserName, szUserName); Edit_SetText(hPass, szPass); return TRUE; case WM_COMMAND: switch (LOWORD(wp)) { case IDOK: Edit_GetText(hPopServer, szPopServer, sizeof(szPopServer)); Edit_GetText(hUserName, szUserName, sizeof(szUserName)); Edit_GetText(hPass, szPass, sizeof(szPass)); EndDialog(hDlg, IDOK); return TRUE; case IDCANCEL: EndDialog(hDlg, IDCANCEL); return TRUE; } return FALSE; } return FALSE; }
メール送信のときとほとんど同じ手順ですが、connectする時void MyRcv(HWND hWnd) { WSADATA wsaData; LPHOSTENT lpHost; SOCKET s; int iRtn, iNo, iMailSize, iGetSize; SOCKADDR_IN sockadd; if (WSAStartup(MAKEWORD(1, 1), &wsaData) != 0) { MessageBox(hWnd, "エラーです", "Error", MB_OK); return; } while (strcmp(szPopServer, "") == 0 || strcmp(szUserName, "") == 0 || strcmp(szPass, "") == 0) { MessageBox(hWnd, "POP3の設定を行ってください。", "注意", MB_OK); DialogBox(hInst, "MYPOPDLG", hWnd, (DLGPROC)MyPopSetProc); } lpHost = gethostbyname(szPopServer); if (lpHost == NULL) { wsprintf(szStr, "%sが見つかりません", szPopServer); MessageBox(hWnd, szStr, "Error", MB_OK); return; } s = socket(PF_INET, SOCK_STREAM, 0); if (s == INVALID_SOCKET) { MessageBox(hWnd, "ソケットをオープンできません", "Error", MB_OK); return; } sockadd.sin_family = AF_INET; sockadd.sin_port = htons(110);//POP3のポートは110番 sockadd.sin_addr = *((LPIN_ADDR)*lpHost->h_addr_list); if (connect(s, (PSOCKADDR)&sockadd, sizeof(sockadd))) { MessageBox(hWnd, "サーバーソケットに接続失敗", "Error", MB_OK); closesocket(s); WSACleanup(); return; } memset(szStrRcv, '\0', sizeof(szStrRcv)); recv(s, szStrRcv, sizeof(szStrRcv), 0); MessageBox(hWnd, szStrRcv, "Server", MB_OK); wsprintf(szStr, "USER %s\r\n", szUserName); send(s, szStr, strlen(szStr), 0); memset(szStrRcv, 0, sizeof(szStrRcv)); recv(s, szStrRcv, sizeof(szStrRcv), 0); MessageBox(hWnd, szStrRcv, "Server", MB_OK); wsprintf(szStr, "PASS %s\r\n", szPass); send(s, szStr, strlen(szStr), 0); memset(szStrRcv, 0, sizeof(szStrRcv)); recv(s, szStrRcv, sizeof(szStrRcv), 0); MessageBox(hWnd, szStrRcv, "Server", MB_OK); if (strstr(szStrRcv, "0 message") != NULL) { MessageBox(hWnd, "メッセージはありません", "No Message", MB_OK); strcpy(szStr, "QUIT\r\n"); send(s, szStr, strlen(szStr), 0); memset(szStrRcv, 0, sizeof(szStrRcv)); recv(s, szStrRcv, sizeof(szStrRcv), 0); MessageBox(hWnd, szStrRcv, "Server", MB_OK); closesocket(s); WSACleanup(); return; } while (1) { iRtn = MessageBox(hWnd, "メールを読みますか", "Down Load", MB_YESNO); if (iRtn == IDYES) { MessageBox(hWnd, "メールの番号を入力してください", "NO", MB_OK); DialogBox(hInst, "MYDLG", hWnd, (DLGPROC)MyDlgProc); iNo = atoi(szStr); wsprintf(szStr, "LIST %d\r\n", iNo); send(s, szStr, strlen(szStr), 0); memset(szStrRcv, '\0', sizeof(szStr)); recv(s, szStrRcv, sizeof(szStrRcv), 0); iMailSize = GetMailSize(szStrRcv); iGetSize = 0; wsprintf(szStr, "RETR %d\r\n", iNo); send(s, szStr, strlen(szStr), 0); memset(szStrRcv, '\0', sizeof(szStrRcv)); iGetSize += recv(s, szStrRcv, sizeof(szStrRcv), 0); MessageBox(hWnd, szStrRcv, "Server", MB_OK); while (iMailSize >= iGetSize) { memset(szStrRcv, '\0', sizeof(szStrRcv)); iGetSize += recv(s, szStrRcv, sizeof(szStrRcv), 0); MessageBox(hWnd, szStrRcv, "Server", MB_OK); } } else { break; } } closesocket(s); WSACleanup(); MessageBox(hWnd, "通信を終了しました", "OK", MB_OK); return; }
sockadd.sin_port = htons(110);
となることに注意してください。connectしたらUSEとPASSコマンドを 送ってください。この時点で何通メッセージが届いているか サーバーから連絡があります。
+OK **** has X messages (.....octes)
と言う形で来ますので、受け取った文字列に0 messageという文字列が 含まれていると、メールは届いていません。
メールを読む場合、メール番号を入力します。その番号を使って LISTコマンドを送り、読みたいメールのサイズを調べます。 戻ってきた文字列からメールサイズを調べるために、自作の GetMailSize関数を呼びます。あとはRETRコマンドを送信して、 メールサイズになるまで、recvで文字列を受け取りつづけます。 (メールを受け取るバッファサイズを動的に確保すれば一度ですみます。)
ま、こんなところです。文章で書くと簡単そうですが実際に作るのは 非常に大変でした。
文字列からメールのサイズを返す関数です。 strtok関数はは第78章で解説してあるので、参照してください。 あまり目立ちませんが便利な関数です。int GetMailSize(char *szMsg) { char *token; char seps[] = " "; //半角スペースをデリミタとして3番目のトークンを取得する //「+OK 番号 **** octes」という文字列から****を取得する token = strtok(szMsg, seps);//無駄読み1番目のトークン token = strtok(NULL, seps);//無駄読み2番目のトークン token = strtok(NULL, seps);//3番目のトークン return atoi(token); }
まだまだ、原始的なプログラムです。いろいろ改良を試みてください。
Update 27/Sep/1999 By Y.Kumei