邮箱的设计基于SMTP(Simple Mail Transfer Protocol)协议,即简单邮件传输协议,邮件服务器用SMTP发送、接受邮件,但是邮件客户端只能用SMTP发送邮件,接收邮件一般用IMAP或者POP3。
邮箱发送客户端程序主要处理对邮件的发送过程,采用的是SMTP协议,其原理是本客户端程序,使用SMTP协议,将邮件发送到SMTP服务器上,此服务器会作为客户端,使用SMTP协议将邮件发送到接收邮件的服务端上,接收方程序即可使用IMAP或POP3协议,对接受到的邮件进行处理,即转为用户可以读懂的邮件内容(也就是发送方想要接收方读的内容)。
其中具体传送邮件的步骤对用户是“透明”的,这也是邮箱发送客户端所要实现的一项功能,对用户隐藏那些不友好的传输手段,为用户提供方便友好的具体接口,提供更好的交互手段。用户看不到SMTP协议的具体内容和如何实现对邮件的转换过程,用户看得到的是自己的邮件能够准确、快捷和完整的发送到收件人手里。
在具体实现过程中,由于SMTP在传输报文时,只能传输7位的ASCII格式的报文,不支持不适用7位ASCII格式的语种和语言、视频数据的传输,因此我们需要辅助性协议帮忙传输报文,即MIME协议。
MIME邮件由邮件头和邮件体组成。
其中,邮件头有发件人、收件人、主题、时间、MIME版本、邮件内容的类型等信息。邮件体有段体的类型、段体的传输编码方式、段体的安排方式、段体的ID、段体的位置(路径)等信息。其中multipart类型,是MIME邮件的精髓之处。由于邮件体被分为了很多个段,这些段又各自分为了段头和段体两部分。各个段也需要用multipart类型分隔开。如下图。
邮件内如果要嵌入各个内容,需要用multipart类型分隔开。
以上为邮件体MIME的实现方式。
接下来是SMTP的工作方式。
首先,邮箱客户端程序,会发起建立一个运行到接收端邮箱服务器主机上的SMTP服务器端口号25之间的TCP连接,SMTP服务器向该客户回答应答码220,并且为客户端提供服务器的域名。
客户端当收到应答码后,想服务端发送HELO命令,启动客户端和服务器之间的SMTP的会话。服务端会回应应答码250,通知客户端已经建立服务会话。
接着,在客户与服务器之间的连接建立之后,发信的用户就可以与一个或多个收信人交换邮件报文了。
客户用“MAIL FROM”向服务器报告发信人的邮箱与域名,服务器向客户回应应答码“250”,代表请求命令完成;
客户用“RCPT TO”命令向服务器报告收信人的邮箱与域名,服务器向客户回应应答码“250”,代表请求命令完成;
客户用“DTAT”命令对报文的传送进行初始化,服务器回应“354”,表示可以进行邮件输入了;
客户用连续的行向服务器传送报文的内容,每行以两字符的行结束标识(CR与LF,即换行和回车)终止;
当发送结束时,报文以只有一个“.”的行结束,服务器向客户回应应答码“250”,代表请求命令完成。邮件体的内容就发送完成。
最后连接终止,客户端发送“QUIT”命令到服务器,服务器收到命令后,回到回应应答码“221”,并结束会话。
以上,就是SMTP客户端发送的全过程,也是基于这种方式,实现的邮箱客户端。
1.用户在登录界面输入用户名(邮箱号)和密码(授权码),点击登录按钮,跳转到邮箱主体部分。
2. 在主体部分,由用户输入发件人、收件人、主题,选择附件并输入正文内容,点击发送邮箱按钮。
3. 从输入框内读取信息,首先与服务其建立连接,想服务器打招呼并说明请求登录,发送邮箱用户名和密码,发送发件人和收件人,发送数据(包括邮件日期、发件人、收件人、主题、使用邮箱、正文和附件内容),结束发送(发送‘.’)。
4. 若发送成功,则弹出消息框发送成功告知用户;否则弹出发送失败。
邮箱登录,在此处读取用户的邮箱账号和密码,保存在数据类中,以便在主界面进行使用该数据。
在此界面内,为了使界面看起来更美观,将Label标签背景隐藏成透明,并将窗口固定。
在用户点击登录按钮后,会获取密码,并跳转到主界面,之后才能隐藏该窗口。但是需要在主窗口关闭时,才能关闭登录窗口,隐藏需要在创建主窗口时,传入该窗口的“this”对象。
在此窗口内,用户可以输入发件人、收件人(默认可以直接修改全部)、主题、附件和正文。
在用户输入全部信息后,点击发送按钮,由此开始程序内部的发送步骤。
程序首先声明套接字,会根据用户登录时使用的邮箱,检测是否为qq邮箱或者网易邮箱,由此选择不同的SMTP服务器,使用端口号25,进行连接。
建立连接后,会先从服务器接收回应,然后打招呼并请求登录,成功后,会随机发送用户名(邮箱)和密码(授权码)。
说明发送人和收件人,随后说明发送数据。
此为邮箱头,包括日期、发件人、收件人、主题和使用邮箱。
发送正文内容,其中要用外部界限和内部界限。
随后是发送附件内容,如果文件路径不为空的话,会读取附件路径,得到目标文件名,对其编码然后发送段头,随后,打开文件内部数据,同样需要对其转换成编码,然后发送附件内容。
当选择附件内容后会找到其路径,然后写入文本框附件路径。
随后结束,发送连接结束,根据是否发送成功与否,会弹出消息框提示。
即为发送全部过程。
此为接受服务端返回消息,并打印出的接收函数。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace tcp_ip
{
public partial class Firstform : Form
{
public Firstform()
{
InitializeComponent();
// this.FormBorderStyle = FormBorderStyle.None;
this.ShowInTaskbar = false;
//标签透明
label1.BackColor = Color.Transparent;
label2.BackColor = Color.Transparent;
label3.BackColor = Color.Transparent;
label4.BackColor = Color.Transparent;
//固定窗口
this.MaximizeBox = false;
this.MinimizeBox = false;
this.StartPosition = FormStartPosition.CenterScreen;
// label1.Parent = Firstform;
}
private void pictureBox1_Click(object sender, EventArgs e)//鲨鱼图案
{
pictureBox1.Image = Image.FromFile("E:\\Tcpip\\big_work\\content\\first.png");
}
private void button1_Click(object sender, EventArgs e)//登录按钮
{
Data.clientname = textBox2.Text;
Data.password = textBox1.Text;//获取密码
Mainform mainform=new Mainform(this);
mainform.Show();
this.Hide();//主窗口内关闭
}
}
}
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.IO;
namespace tcp_ip
{
public partial class Mainform : Form
{
public static Socket socket;
public static string textname;//附件名字
Thread thread;
public static int flag=0;
Form enter_form=null;//登录窗口
public Mainform(Form firstf)
{
enter_form = firstf;
InitializeComponent();
this.ShowInTaskbar = false;
panel1.BackColor = Color.Transparent;
label5.BackColor = Color.Transparent;
label2.BackColor = Color.Transparent;
label3.BackColor = Color.Transparent;
label4.BackColor = Color.Transparent;
label6.BackColor = Color.Transparent;
label1.BackColor = Color.Transparent;
this.MaximizeBox = false;
this.MinimizeBox = false;
this.StartPosition = FormStartPosition.CenterScreen;
}
public static string rece()
{
int len ;//receive长度
byte[] buffer = new byte[1024];//接收传来的信息
len= socket.Receive(buffer);//接收信息
byte[] buffer2 = new byte[len];
Array.Copy(buffer, buffer2, len);
string s= Encoding.UTF8.GetString(buffer2);
Console.WriteLine(s);//打印出
return s;
}
private void myclose(object sender, FormClosedEventArgs e)//关闭主窗口
{
enter_form.Close();//关闭登陆窗口
}
private void button_send_Click(object sender, EventArgs e)
{
socket=new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);//声明通信套接字
IPAddress iRAddress=null;
if (Data.clientname.Substring(Data.clientname.Length - 6) == "qq.com")//检测是否为QQ邮箱
iRAddress = Dns.GetHostAddresses("smtp.qq.com")[0];
else if (Data.clientname.Substring(Data.clientname.Length - 7) .Equals("163.com"))
iRAddress = Dns.GetHostAddresses("SMTP.163.com")[0];
int port = int.Parse("25");
IPEndPoint ep = new IPEndPoint(iRAddress, port);
try
{
socket.Connect(ep);
flag = 1;
}
catch
{
}
if(flag == 1)
{
rece();
//打招呼
socket.Send(Encoding.ASCII.GetBytes("ehlo hlp\r\n"));//向服务器打招呼
rece();
//登录
socket.Send(Encoding.ASCII.GetBytes("auth login\r\n"));//请求登录
rece();
//发送用户名
byte[] da1=Encoding.ASCII.GetBytes(Data.clientname);//转码
socket.Send(Encoding.ASCII.GetBytes(Convert.ToBase64String(da1)+"\r\n"));//发送用户名
rece();
//发送密码
da1 = Encoding.ASCII.GetBytes(Data.password);//转码
socket.Send(Encoding.ASCII.GetBytes(Convert.ToBase64String(da1)+"\r\n"));//发送密码
rece();
//说明发件人
socket.Send(Encoding.ASCII.GetBytes("mail from:<"+Data.clientname+">\r\n"));//说明发件人
rece();
//说明收件人
socket.Send(Encoding.ASCII.GetBytes("RCPT TO:<" + accept_textBox.Text + ">\r\n"));//发送收件人
rece();
//说明发送数据
socket.Send(Encoding.ASCII.GetBytes("DATA\r\n"));//说明发送数据
rece();
//发送数据
socket.Send(Encoding.ASCII.GetBytes("Date:" + DateTime.Now.ToString("yyyy-MM-dd-HH-mm-ss")+"\r\n"));//日期
socket.Send(Encoding.UTF8.GetBytes("From:" + "\""+textBox1.Text +"\""+ "<" + Data.clientname + ">\r\n"));//发件人
socket.Send(Encoding.ASCII.GetBytes("To:<" + accept_textBox.Text + ">\r\n"));//收件人
socket.Send(Encoding.UTF8.GetBytes("Subject:"+subject_textBox.Text +"\r\n"));//主题
socket.Send(Encoding.ASCII.GetBytes("X-mailer:shark 邮箱\r\n"));//使用邮箱
socket.Send(Encoding.ASCII.GetBytes("Content-Type:multipart/mixed;boundary=\"*shark_out*\"\r\n"));//外部界限
socket.Send(Encoding.ASCII.GetBytes("--*shark_out*\r\n"));
socket.Send(Encoding.ASCII.GetBytes("Content-Type:multipart/alternative;boundary=\"*shark_inside*\"\r\n"));//内部界限
socket.Send(Encoding.ASCII.GetBytes("--*shark_inside*\r\n"));//开始正文
socket.Send(Encoding.ASCII.GetBytes("Content-Type:text/plain;charset=utf-8\r\n"));
socket.Send(Encoding.ASCII.GetBytes("Content-Transfer-Encoding:7bit\r\n"));
//正文内容
socket.Send(Encoding.UTF8.GetBytes(main_textBox.Text+"\r\n"));
//附件
socket.Send(Encoding.UTF8.GetBytes("\r\n"));
if(richTextBox1.Text!="")
{
socket.Send(Encoding.ASCII.GetBytes("--*shark_inside*\r\n"));
string alli = richTextBox1.Text;//读取附件路径
string[] name = alli.Split('\\');//分割'/'
string namelast = name[name.Length - 1];
string base64alli = "";
base64alli = Convert.ToBase64String(Encoding.UTF8.GetBytes(namelast));//先编码
socket.Send(Encoding.UTF8.GetBytes("Content-Type:application/octet-stream;name=\"" + namelast + "\"\r\n"));
socket.Send(Encoding.UTF8.GetBytes("Content-Transfer-Encoding:base64\r\n"));
socket.Send(Encoding.UTF8.GetBytes("Content-Disposition:attachment; filename=\"=?utf-8?B?" + base64alli + "?=\"\r\n"));
FileStream fileStream = new FileStream(alli, FileMode.Open);//打开文件内部数据
string base64Str = "";
fileStream.Seek(0, SeekOrigin.Begin);
byte[] bufferxx = new byte[fileStream.Length];
fileStream.Read(bufferxx, 0, Convert.ToInt32(fileStream.Length));
base64Str = Convert.ToBase64String(bufferxx);//转换数据编码
socket.Send(Encoding.UTF8.GetBytes(base64Str + "\r\n"));//发送数据内容
fileStream.Close();
}
socket.Send(Encoding.ASCII.GetBytes(".\r\n"));//结束发送
string lp = rece(); //读取回送信息
// socket.Send(Encoding.UTF8.GetBytes("quit\r\n"));//连接结束
if(!lp.Equals("") && lp[0]=='2')//查看是否发送成功
{
MessageBox.Show("发送成功");
}
else
{
MessageBox.Show("发送失败");
}
}
}
private void button1_Click(object sender, EventArgs e)//选择附件文件
{
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.InitialDirectory = "c:\\";//打开C
openFileDialog.Filter = "文本文件|*.*|C#文件|*.cs|所有文件|*.*";
openFileDialog.ReadOnlyChecked = true;
openFileDialog.FilterIndex = 1;
if(openFileDialog.ShowDialog() == DialogResult.OK)
{
richTextBox1.Text= openFileDialog.FileName;//写入附件路径
}
}
private void textBox1_MouseDown(object sender, MouseEventArgs e)
{
textBox1.SelectAll();
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace tcp_ip
{
public class Data
{
public static string clientname;//用户名
public static string password;//密码
}
}