计算机网络——电子邮件客户端程序设计与实现

一、实验目的

参照教材6.5节原理,设计一个电子邮件客户端程序。

二、总体设计

1. 基本原理

STMP客户通过命令向SMTP服务器发送操作请求,而服务器则通过3位的数字对响应的请求作出响应。STMP规定了14条命令和21条应答信息,每条命令由4个字母组成,而每一种应答一般只有一行信息,又一个3位数字的代码开始。
POP3采用C/S的工作方式。在接收邮件的用户PC机中的用户代理UA必须运行POP3客户程序,而在收件人所连接的ISP的邮件服务器中则运行POP的服务器程序。POP服务器只有在用户鉴别信息(用户名+密码)后,才运行对方对邮箱进行读取。

2. 设计步骤

(1)发送邮件

①加载套接字,创建套接字库;
②得到有关于域名信息,链接到QQ邮箱服务器;
连接端口25,端口25对应简单邮件传输服务器SMTP。
③向服务器发送请求,用于建立客户端与服务器之间的连接;
不论是客户还是服务器应用程序都用recv()函数从TCP连接的另一端接收数据,recv()函数返回其实际copy的值接受,如果recv()在copy时出错,那么它返回SOCKET_ERROR;如果recv在等待协议接收数据时网络中断了,那么它返回0。
④登录邮箱服务器:进行用户身份认证,由于使用EHLO,需要发送base64加密的用户名和密码;
ehol可以支持用户认证,客户端发送ehlo命令,用来把客户的域名通知服务器,在TCP连接建立阶段,发送方和接收方都是通过他们的IP地址来告诉对方的。
⑤客户发送MAIL FORM报文介绍报文的发送者;
MAIL FORM报文包括发送人的邮件地址,可以给服务器在返回差错或报文时的返回邮件地址。
⑥客户发送RCPT(收件人)报文,包括收件人的邮件地址;
RCPT命令的作用是:先弄清接收方系统是否已经准备好接受邮件的准备,然后才发送邮件这样做是为了避免浪费通信资源,不至于发送了很长的邮件之后才知道是因地址错误。
⑦客户发送DATA报文对报文的传送进行初始化;
DATA命令表示要开始传送邮件的内容了。
⑧发送邮件内容;
⑨在报文传送成功后,客户就终止连接。
客户发送QUIT命令,服务器响应221(服务关闭)或其他代码,在连接终止阶段,TCP连接必须关闭。

(2)查看邮件

①加载套接字,创建套接字库;
②得到有关于域名信息,链接到qq邮箱服务器;
POP是邮件读取协议,端口110是为POP3服务开放的,使用电子邮件客户端程序的时候,会要求输入POP3服务器地址,默认情况下使用的就是110端口。
③向服务器发送请求,用于建立客户端与服务器之间的连接;
④向服务器发送账号和授权码登录到邮箱服务器;
⑤通过stat命令查看总共多少封信件,多少字节;
⑥通过list命令列出每封信件的字节数;
⑦通过retr n命令读取第n封信;
⑧客户终止连接。

三、详细设计

1. 程序流程图

(1)发送邮件

在这里插入图片描述

(2)查看邮件

在这里插入图片描述

2. 实验代码

#include <windows.h>
#include <iostream>
#include <string>
#include <WinSock2.h>
#include <stdio.h>
#include <unistd.h>
#include <sstream>
using namespace std;
#pragma comment(lib, "ws2_32.lib") /*链接ws2_32.lib动态链接库*/

int main()
{
    char buff[5000]; //收到recv函数返回的结果
    string message;
    string info;//邮件内容
    string subject;//邮件主题
    WSADATA wsaData;//存储被WSAStartup函数调用后返回的Windows Sockets数据
    WORD wVersionRequested = MAKEWORD(2, 1);//创建一个无符号16位整型数
    //WSAStarup,即WSA(Windows SocKNDs Asynchronous,Windows套接字异步)的启动命令
    //使用Socket的程序在使用Socket之前必须调用WSAStartup函数,以后应用程序就可以调用所请求的Socket库中的其他Socket函数了
    int err = WSAStartup(wVersionRequested, &wsaData);//进行相应的socket库绑定
    SOCKADDR_IN addrServer; //服务端地址
    HOSTENT *pHostent;//hostent是host entry的缩写,该结构记录主机的信息,包括主机名、别名、地址类型、地址长度和地址列表
    SOCKET sockClient; //客户端的套接字
    /*
    使用 MAIL 命令指定发送者
    使用 RCPT 命令指定接收者,可以重复使用RCPT指定多个接收者
    */
    cout << "发送邮件请按“1”,查看邮件请按“2”:\n";
    int choose;
    cin >> choose;
    if (choose == 1)
    {
        sockClient = socket(AF_INET, SOCK_STREAM, 0); //建立socket对象
        //stmp为电子邮件发送协议
        pHostent = gethostbyname("smtp.qq.com"); //得到有关于域名的信息,链接到qq邮箱服务器
        addrServer.sin_addr.S_un.S_addr = *((DWORD *) pHostent->h_addr_list[0]); //得到smtp服务器的网络字节序的ip地址
        addrServer.sin_family = AF_INET;//TCP/IP-IPv4
        addrServer.sin_port = htons(25); //连接端口25,端口25对应简单邮件传输服务器SMTP
        //int connect (SOCKET s , const struct sockaddr FAR *name , int namelen ); //函数原型
        //向服务器发送请求,用于建立客户端与服务器之间的连接
        err = connect(sockClient, (SOCKADDR *) &addrServer, sizeof(SOCKADDR));
        //不论是客户还是服务器应用程序都用recv函数从TCP连接的另一端接收数据
        //该函数的第一个参数指定接收端套接字描述符
        //recv()函数返回其实际copy的值接受,如果recv在copy时出错,那么它返回SOCKET_ERROR;如果recv在等待协议接收数据时网络中断了,那么它返回0
        buff[recv(sockClient, buff, 500, 0)] = '\0';//buff指明一个缓冲区,存放recv函数接收到的数据,500指明缓冲区的长度
        /*
        登录邮件服务器
        */
        message = "ehlo qq.com\r\n";//ehol可以支持用户认证
        //send()向一个已经连接的socket发送数据,如果无错误,返回值为所发送数据的总数,否则返回SOCKET_ERROR
        //c_str()把string对象转换成c中的字符串样式
        //客户端发送ehlo命令,用来把客户的域名通知服务器
        //在TCP连接建立阶段,发送方和接收方都是通过他们的IP地址来告诉对方的
        send(sockClient, message.c_str(), message.length(), 0);
        buff[recv(sockClient, buff, 500, 0)] = '\0';   //接收返回值
        //cout <<"1" <<  buff << endl;

        message = "auth login\r\n";//进行用户身份认证
        send(sockClient, message.c_str(), message.length(), 0);
        buff[recv(sockClient, buff, 500, 0)] = '\0';
        // cout <<"2" <<  buff << endl;
        /*
        由于使用EHLO,需要发送base64加密的用户名、密码
        */
        message = "########\r\n";//base64加密的用户名
        send(sockClient, message.c_str(), message.length(), 0);
        buff[recv(sockClient, buff, 500, 0)] = '\0';
        // cout <<"3" <<  buff << endl;

        message = "###########\r\n";//base64加密的密码
        send(sockClient, message.c_str(), message.length(), 0);
        buff[recv(sockClient, buff, 500, 0)] = '\0';
        // cout <<"4" <<  buff << endl;
        string mail;
        cout << "请输入收件人邮箱:";
        cin >> mail;

        /*
        客户发送MAIL FROM报文介绍报文的发送者
        它包括发送人的邮件地址
        可以给服务器在返回差错或报文时的返回邮件地址
        */
        /*
        客户发送RCPT(收件人)报文,包括收件人的邮件地址
        RCPT命令的作用是:先弄清接收方系统是否已经准备好接受邮件的准备,然后才发送邮件
        这样做是为了避免浪费通信资源,不至于发送了很长的邮件之后才知道是因地址错误
        */
        message = "MAIL FROM:<########@qq.com> \r\nRCPT TO:<";
        message.append(mail);
        message.append("> \r\n");
        send(sockClient, message.c_str(), message.length(), 0);
        buff[recv(sockClient, buff, 500, 0)] = '\0';
        // cout <<"5" <<  buff << endl;
        /*
        客户发送DATA报文对报文的传送进行初始化
        DATA命令表示要开始传送邮件的内容了
        */
        message = "DATA\r\n";
        send(sockClient, message.c_str(), message.length(), 0);
        buff[recv(sockClient, buff, 500, 0)] = '\0';
        // cout <<"6" <<  buff << endl;
        message = "From: #########@qq.com\r\nTo: " + mail + "\r\nsubject:";
        cout << "主题:";
        cin >> subject;
        message.append(subject);
        message.append("\r\n\r\n");
        cout << "内容:";
        cin >> info;
        message.append(info);
        message.append("\r\n.\r\n");

        send(sockClient, message.c_str(), message.length(), 0);//发送邮件内容
        // cout <<"7" <<  buff << endl;
        /*
        在报文传送成功后,客户就终止连接,包括如下步骤:
        1.客户发送QUIT命令
        2.服务器响应221(服务关闭)或其他代码
        在连接终止阶段,TCP连接必须关闭
        */
        message = "QUIT\r\n";
        send(sockClient, message.c_str(), message.length(), 0);
        buff[recv(sockClient, buff, 500, 0)] = '\0';
        // cout <<"8" <<  buff << endl;
        cout << "发送成功!" << endl;
    }else if (choose == 2)
    {
        sockClient = socket(AF_INET, SOCK_STREAM, 0); //建立socket对象
        //pop是邮件读取协议
        //const char *host_id = "pop3.126.com";//const定义只读变量的关键字
        pHostent = gethostbyname("pop.qq.com");//得到有关于域名的信息,链接到qq邮箱服务器
        int port = 110;//端口110是为pop3服务开放的。使用电子邮件客户端程序的时候,会要求输入POP3服务器地址,默认情况下使用的就是110端口
        addrServer.sin_addr.S_un.S_addr = *((DWORD *) pHostent->h_addr_list[0]); //得到smtp服务器的网络字节序的ip地址
        addrServer.sin_family = AF_INET;//TCP/IP-IPv4
        addrServer.sin_port = htons(port); //连接端口110
        //向服务器发送请求,用于建立客户端与服务器之间的连接
        err = connect(sockClient, (SOCKADDR *) &addrServer, sizeof(SOCKADDR)); //向服务器发送请求
        //不论是客户还是服务器应用程序都用recv函数从TCP连接的另一端接收数据
        //该函数的第一个参数指定接收端套接字描述符
        //recv()函数返回其实际copy的只接受,如果recv在copy时出错,那么它返回SOCKET_ERROR;如果recv在等待协议接收数据时网络中断了,那么它返回0
        buff[recv(sockClient, buff, 500, 0)] = '\0';
        //cout <<"1" <<  buff << endl;
        message = "user ########@qq.com\r\n";
        send(sockClient, message.c_str(), message.length(), 0); //发送账号
        buff[recv(sockClient, buff, 500, 0)] = '\0';   //接收返回值
        //std::cout << "Client : send name \nServer:"<< buff << std::endl;

        message = "pass ######\r\n";//授权码
        send(sockClient, message.c_str(), message.length(), 0); //发送授权码
        buff[recv(sockClient, buff, 500, 0)] = '\0';   //接收返回值
        //std::cout << "Client : send authoriza code\nServer:"<< buff << std::endl;

        message = "stat\r\n";//查看总共多少封信件,多少字节
        send(sockClient, message.c_str(), message.length(), 0); //发送状态
        buff[recv(sockClient, buff, 500, 0)] = '\0';   //接收返回值
        Sleep(1);
        std::cout << "Client : send stat \nServer : "
                  << buff << std::endl;

        message = "list\r\n";//列出每封信件的字节数
        send(sockClient, message.c_str(), message.length(), 0); //发送状态
        buff[recv(sockClient, buff, 5000, 0)] = '\0';   //接收返回值
        Sleep(1);
        std::cout << "Client : send list \nServer :"
                  << buff << std::endl;
        int number;
        std::cout << "请输入要查看的邮件的序号"
                  << std::endl;
        cin >> number;
        stringstream ss;
        ss<<number;
        string str_number = ss.str();//将数字number转换为字符串str
        //retr str_number 读取第str_number封信
        message = "retr " + str_number + "\r\n";
        send(sockClient, message.c_str(), message.length(), 0); //发送状态
        Sleep(1);
        std::cout << "Client : send retr (...) \n";

        memset(buff,0,500);
        buff[recv(sockClient, buff, 500, 0)] = '\0';   //接收返回值
        Sleep(1);
        std::cout << "Server :" << buff << std::endl;
        message = "quit\r\n";
        send(sockClient, message.c_str(), message.length(), 0);
        //buff[recv(sockClient, buff, 500, 0)] = '\0';
        //std::cout << "Client : send quit \nServer :"<< buff << std::endl;

        cout << "查询成功!" << endl;

    }else{
        cout << "输入错误!" << endl;
    }
}


四、实验结果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值