精华内容
下载资源
问答
  • 前言大家一定熟悉Foxmail中的“特快专递”,它能直接将电子邮件发送到对方的...有时我们用Outlook发送一封邮件,第二天对方都没收到,可我这边确实已经发送成功了,只好让对方多收几次,了第三天SMTP服务器回信说

    前言

    大家一定熟悉Foxmail中的“特快专递”,它能直接将电子邮件发送到对方的邮件服务器中,而不需要经过SMTP服务器中转,这样做有什么好处?第一:发送速度比较快,不需要等SMTP服务器对邮件进行查毒、派发、验证;第二:你可以及时掌握邮件是否发送成功的信息。有时我们用Outlook发送一封邮件,到第二天对方都没收到,可我这边确实已经发送成功了,只好让对方多收几次,到了第三天SMTP服务器回信说“不好意思,你发往XXX的邮件因为XXX原因未能送达……”,原来邮件被打回来了,尤其最近163邮箱非常离谱,我发出去的10封邮件,至少有3封会被无故打回来,说什么“网络连接失败”所以被打回,莫名其妙,可能我是免费邮箱的缘故吧,没办法只好再申请多几个邮箱,我现在已经有“chrys@21cn.comchrys.xie@gmail.comhwxie@ust.hk ……”好多邮箱了,就是为了防止给别人发邮件时被无故退回……撤远了,不好意思。第三:我们有时需要在程序里将某些敏感信息发送至公司邮箱,例如:计算注册码时我们需要用户操作我们的软件将申请注册的信息发送回我们的售后服务邮箱,由我们的工作人员处理来这些邮件。

    大家一定会想用SMTPSimple Mail Transfer Protocol)借助SMTP服务器也能通过程序实现邮件发送,但是有一个很大问题就是安全问题,很多著名的邮件服务器运营商对于用软件方式通过SMTP协议频繁提交邮件转发的申请是不欢迎的,我的163邮箱就曾经深受其害,我那次是在写SMTP客户端发送邮件的程序,顺手就用了163SMTP服务器,我刚发到第5封邮件时就发送失败了,我再登录163网站一查,原来我的账号被封了,原因就是我用软件发送邮件太多了(天啦,才5封而已啊),后来我花了近两个月时间跟新浪公司又赔礼又道歉,还把身份证传真过去了我的账号才被恢复。

    剖析邮件传送过程

    废话说太多请别介意,现在言归正传,要直接将邮件送到对方(POPIMAP)服务器上,而不经过SMTP邮件服务器转交,其实也不难,你只要改用Unix/Linux操作系统,直接SendMail命令就能完成,但在Windows下想要实现这个功能恐怕得花一点心思了。我们首先要从协议RFC821 - Simple Mail Transfer Protocol入手来分析。

    首先我们看一下Email的递送过程:

    邮件原文 编码 SMTP客户端 SMTP转交服务器 远程SMTP服务器(对方邮局)。

    “特快专递”的实现思路

    邮件编码后被递送到一个SMTP转交服务器上,该服务器对信件分检(到同一邮局的被放在一起)后,根据优先级以及信件的先后次序被发送到远程邮局的SMTP服务器上。换句话说,只要我们知道了SMTP转交服务器是如何确定远程邮局SMTP服务器的地址的,就可以直接递送到远程邮局服务器。SMTP转交服务器又是知道远程邮局的地址呢?这就是域名解析所完成的工作了,就好比我们在IE浏览器输入“www.viction.net”这个域名,IE浏览器又如何知道目标服务器的IP地址呢?也是域名解析服务器的功劳。

    电子邮件地址由两部分组成,例如:chrys@163.com,这里的chrys是邮箱名(即用户名,一个用户对应一个邮箱),163.com是邮箱服务器地址,邮箱名和邮箱服务器地址之间以“@”作为分隔。

    我们只要向域名服务器发送查询“163.com”的远程邮局服务器地址便可找到远程邮局SMTP服务器的IP 地址,该查询指令被称作MX(Mail Exchange)邮件交换服务器的地址查询。远程邮局SMTP服务器的地址可能不止一个,这时,你可根据信件优先级的不同来选择对应的远程邮局,我为了安全起见会对每一个远程邮局服务器按照等级高低逐一尝试,只要将邮件成功地发送到其中一个邮局我们的任务就完成了。

    我们要完成几项编程工作:本机DNS的获取、与DNS服务器通信实现MX指令查询、SMTP邮件提交,下面我们一一阐述。

    获取本机DNS

    代码中我封装了一个类CnetAdapterInfo,该类可以获取本机网卡的系列信息,包括本机IP地址、子网掩码、DNSWins、网卡MAC地址等相关信息。

    首先我们需要调用IPHelpAPI 库中的GetAdaptersInfo()函数来获取系统中所有网卡信息。

    DWORD GetAdaptersInfo (

      __out    PIP_ADAPTER_INFO pAdapterInfo,

      __inout  PULONG pOutBufLen

    );

    该函数有两个参数,pAdapterInfo是一个指针,指向一个用户定义的结构体,一般是用HeapAlloc()申请的内存空间,pOutBufLen传入pAdapterInfo所指空间的大小,传出实际需要的缓冲大小,第一次调用该函数时pOutBufLen传入0,函数将返回 ERROR_BUFFER_OVERFLOW 表示需要更多的缓冲,并将实际需要的缓冲长度返回,我们根据实际长度用HeapAlloc()函数申请空间再次调用该函数,以下代码是枚举所有网卡并将信息保存到数组 m_Ary_NetAdapterInfo 中:

    #define MALLOC( bytes ) ::HeapAlloc( ::GetProcessHeap(), HEAP_ZERO_MEMORY, (bytes) )

    #define FREE( ptr )       if( ptr ) ::HeapFree( ::GetProcessHeap(), 0, ptr )

    #define REMALLOC( ptr, bytes ) ::HeapReAlloc( ::GetProcessHeap(), HEAP_ZERO_MEMORY, ptr, bytes )

    //

    // 枚举网络适配器

    // return : ------------------------------------------------------------

    //     -1    -      失败

    //     >=0 -      网络适配器数量

    //

    int CNetAdapterInfo::EnumNetworkAdapters ()

    {

           DeleteAllNetAdapterInfo ();

     

           IP_ADAPTER_INFO* pAdptInfo = NULL;

           IP_ADAPTER_INFO* pNextAd   = NULL;

           ULONG ulLen                             = 0;

           int nCnt                               = 0;

          

           DWORD dwError = ::GetAdaptersInfo ( pAdptInfo, &ulLen );

           if( dwError != ERROR_BUFFER_OVERFLOW ) return -1;

           pAdptInfo = ( IP_ADAPTER_INFO* )MALLOC ( ulLen );

           dwError = ::GetAdaptersInfo( pAdptInfo, &ulLen );

           if ( dwError != ERROR_SUCCESS ) return -1;

          

           pNextAd = pAdptInfo;

           while( pNextAd )

           {

                  COneNetAdapterInfo *pOneNetAdapterInfo = new COneNetAdapterInfo ( pNextAd );

                  if ( pOneNetAdapterInfo )

                  {

                         m_Ary_NetAdapterInfo.Add ( pOneNetAdapterInfo );

                  }

                  nCnt ++;

                  pNextAd = pNextAd->Next;

           }

          

           // free any memory we allocated from the heap before

           // exit.  we wouldn't wanna leave memory leaks now would we? ;p

           FREE( pAdptInfo );             

          

           return nCnt;

    }

    针对每个网卡信息,我们需要调用 GetPerAdapterInfo()函数来获取指定网卡的DNS信息,使用方法和GetAdaptersInfo()类似。以下代码获取网卡基本信息:

    //

    // 根据传入的 pAdptInfo 信息来获取指定网卡的基本信息

    //

    BOOL COneNetAdapterInfo::Init ()

    {

           IP_ADDR_STRING* pNext                = NULL;

           IP_PER_ADAPTER_INFO* pPerAdapt       = NULL;

           ULONG ulLen                                    = 0;

           DWORD dwErr = ERROR_SUCCESS;

           ASSERT ( m_AdptInfo.AddressLength > 0 );

           t_IPINFO iphold;

     

           // 将变量清空

           m_bInitOk = FALSE;

           m_csName.Empty ();

           m_csDesc.Empty ();

           m_CurIPInfo.csIP.Empty ();

           m_CurIPInfo.csSubnet.Empty ();

           m_Ary_IP.RemoveAll ();

           m_Ary_DNS.RemoveAll ();

           m_Ary_Gateway.RemoveAll ();

          

    #ifndef _UNICODE

           m_csName                   = m_AdptInfo.AdapterName;

           m_csDesc                    = m_AdptInfo.Description;

    #else

           USES_CONVERSION;

           m_csName                   = A2W ( m_AdptInfo.AdapterName );

           m_csDesc                    = A2W ( m_AdptInfo.Description );

    #endif

          

           // 获取当前正在使用的IP地址

           if ( m_AdptInfo.CurrentIpAddress )

           {

                  m_CurIPInfo.csIP         = m_AdptInfo.CurrentIpAddress->IpAddress.String;

                  m_CurIPInfo.csSubnet  = m_AdptInfo.CurrentIpAddress->IpMask.String;

           }

           else

           {

                  m_CurIPInfo.csIP         = _T("0.0.0.0");

                  m_CurIPInfo.csSubnet  = _T("0.0.0.0");

           }

          

           // 获取本网卡中所有的IP地址

           pNext = &( m_AdptInfo.IpAddressList );

           while ( pNext )

           {

                  iphold.csIP            = pNext->IpAddress.String;

                  iphold.csSubnet      = pNext->IpMask.String;

                  m_Ary_IP.Add ( iphold );

                  pNext = pNext->Next;

           }

          

           // 获取本网卡中所有的网关信息

           pNext = &( m_AdptInfo.GatewayList );

           while ( pNext )

           {

                  m_Ary_Gateway.Add ( pNext->IpAddress.String );

                  pNext = pNext->Next;

           }

          

           // 获取本网卡中所有的 DNS

           dwErr = ::GetPerAdapterInfo ( m_AdptInfo.Index, pPerAdapt, &ulLen );

           if( dwErr == ERROR_BUFFER_OVERFLOW )

           {

                  pPerAdapt = ( IP_PER_ADAPTER_INFO* ) MALLOC( ulLen );

                  dwErr = ::GetPerAdapterInfo( m_AdptInfo.Index, pPerAdapt, &ulLen );

                 

                  // if we succeed than we need to drop into our loop

                  // and fill the dns array will all available IP

                  // addresses.

                  if( dwErr == ERROR_SUCCESS )

                  {

                         pNext = &( pPerAdapt->DnsServerList );

                         while( pNext )

                         {

                                m_Ary_DNS.Add( pNext->IpAddress.String );

                                pNext = pNext->Next;

                         }                         

                         m_bInitOk = TRUE;

                  }

     

                  // this is done outside the dwErr == ERROR_SUCCES just in case. the macro

                  // uses NULL pointer checking so it is ok if pPerAdapt was never allocated.

                  FREE( pPerAdapt );

           }

     

           return m_bInitOk;

    }

    至此我们已经获取到系统中所有DNS服务器地址了。

    MX指令查询获取远程邮局地址

    DNS服务器通信其实就是一个简单的UDP网络通信,端口号为53,通信的数据格式如下:

     

    所有的DNS消息基本上都是相同的数据结构,但DNS RR是采用了其他的数据结构。

    QNAME是一个表示域长度的变量,表示每一节有多少字节,例如:www.sockets.com将表示为:

     

    最后的“Additional”通常包含了查询服务器期望被发送的纪录以减少通信量,例如,回应MX查询时通常在“Additional”中包含‘A’纪录。

    具体的MX查询过程请参加源代码,以下代码实现了获取本机所有DNS,然后逐一尝试MX查询的方法:

    //

    // 尝试所有的DNS来查询邮局服务器地址

    //

    BOOL GetMX (

           char *pszQuery,                                               // 要查询的域名

           OUT t_Ary_MXHostInfos &Ary_MXHostInfos   // 输出 Mail Exchange 主机名

                         )

    {

           CNetAdapterInfo m_NetAdapterInfo;

           m_NetAdapterInfo.Refresh ();

           int nNetAdapterCount = m_NetAdapterInfo.GetNetCardCount();

           for ( int i=0; i<nNetAdapterCount; i++ )

           {

                  COneNetAdapterInfo *pOneNetAdapterInfo = m_NetAdapterInfo.Get_OneNetAdapterInfo ( i );

                  if ( pOneNetAdapterInfo )

                  {

                         int nDNSCount = pOneNetAdapterInfo->Get_DNSCount ();

                         for ( int j=0; j<nDNSCount; j++ )

                         {

                                CString csDNS = pOneNetAdapterInfo->Get_DNSAddr ( j );

                                if ( GetMX ( pszQuery, csDNS.GetBuffer(0), Ary_MXHostInfos ) )

                                       return TRUE;

                         }

                  }

           }

           return FALSE;

    }

    如果查询“gmail.com”的邮局服务器地址,将得到如下的结果:

    gsmtp147.google.com 50

    gsmtp183.google.com 50

    gmail-smtp-in.l.google.com       5

    alt1.gmail-smtp-in.l.google.com 10

    alt2.gmail-smtp-in.l.google.com 10

    SMTP协议给远程邮局直接发送邮件

    SMTP是一个简单邮件传输协议,通过TCP连接服务器的25端口号即可进行数据通信,以下是我用telnet手工发送邮件的过程:

     

    其中红色矩形框起来的是服务器回应的数据,绿色矩形框起来的是我手工输入的数据,这里发送的邮件内容为“我是手工发送的电子邮件”,邮件被直接发送到chrys.xie@gmail.com邮箱中,不需要讨厌的SMTP服务器中转,当然,因为这是手工发送的邮件,其内容未经过任何MIME编码,这封邮件可以被FoxmailOutlook收到,但可能被判为垃圾邮件,因为这封邮件连标题都没有,是无头苍蝇,肯定是垃圾,呵呵……关于邮件内容的编码请参考其他相关资料,我有一本书,名叫《Visual C++ 网络通信协议分析与应用实》,这本书有详细的电子邮件编码介绍,可以下载电子文档看看。

    当我们知道了SMTP通信的全过程,再编写一个TCP网络通信程序处理与SMTP服务器请求就不是难事了。本代码中的CHwSMTP类已经封装了整个通信过程,可以发送普通的电子邮件,还可以发送带附件的电子邮件,配合DNS查找,远程邮局地址MX查询便可实现任意邮件直接发送到对方邮箱的功能。

    软件操作界面介绍

    程序执行后界面如下:

     

    From中可以输入一个虚假的邮箱地址,也可以输入真实的邮箱地址(如:21cn的邮箱),但不能输入163的邮箱,否则发送会失败,163邮箱不太欢迎大家用软件方式进行邮件收发,人家要靠这个吃饭嘛。

    注意事项

    看到这里是否已经很兴奋了,想着要自己写一个SMTP服务器,甚至想要写一个邮局服务器程序,但我觉得恐怕还没那么容易,我用这个软件给我的google邮箱(gmail)发送邮件,很快就能成功收到了,可我尝试过用这种方式给163邮箱和21cn邮箱发送邮件时却失败了,发给163时服务器说你的IP不被允许,提示信息如下:

    550-5.7.1 [116.25.186.155] The IP you're using to send mail is not authorized

    550-5.7.1 to send email directly to our servers. Please use the SMTP

    看来163邮箱只接收大牌邮件服务器发过来的邮件,难怪我们这些免费的163用户发送邮件时常会被退回,原来163服务器还认牌子的,faint

    到底要怎么样做才可以直接给163等著名的邮局发邮件呢?不知道用IP欺骗方式能否成功,请有高手知道解决这个问题的一定要告诉我啊,我的邮箱是:chrys@163.com,先谢过了!

    电子邮件在目前的Internet上被广泛地使用,为了安全很多邮局服务器做了安全认证等诸多限制,我们要想让自己的SMTP服务器能向所有的邮局发邮件,恐怕还得做更多的努力。

    结束语

    知识就是力量,知识共享将具有推动时代进步的力量。希望我能为中国的软件行业尽一份薄力。

    你可以任意修改复制本代码,但请保留版权信息文字不要修改。

    由于水平有限,错误再所难免,请知情者原谅并告知,多谢!

     

    源代码下载

    CSDN 下载:http://download.csdn.net/source/802519

     

     

    展开全文
  • 无须SMTP服务器中转直接将电子邮件发送到对方邮箱(附源码)【版 本】1.0.0【操作系统】Windows 桌面操作系统【作 者】谢红伟·chrys ·chrys@163.com ·http://www.viction.net【开发日期】2008-11-23 3:54【文档...

    无须SMTP服务器中转直接将电子邮件发送到对方邮箱(附源码)

    【版   本】

    1.0.0

    【操作系统】

    Windows 桌面操作系统

    【作   者】

    谢红伟·chrys ·chrys@163.com ·http://www.viction.net

    【开发日期】

    2008-11-23 3:54

    【文档日期】

    2008-11-23 3:54

     

    前言

    大家一定熟悉Foxmail中的“特快专递”,它能直接将电子邮件发送到对方的邮件服务器中,而不需要经过SMTP服务器中转,这样做有什么好处?第一:发送速度比较快,不需要等SMTP服务器对邮件进行查毒、派发、验证;第二:你可以及时掌握邮件是否发送成功的信息。有时我们用Outlook发送一封邮件,到第二天对方都没收到,可我这边确实已经发送成功了,只好让对方多收几次,到了第三天SMTP服务器回信说“不好意思,你发往XXX的邮件因为XXX原因未能送达……”,原来邮件被打回来了,尤其最近163邮箱非常离谱,我发出去的10封邮件,至少有3封会被无故打回来,说什么“网络连接失败”所以被打回,莫名其妙,可能我是免费邮箱的缘故吧,没办法只好再申请多几个邮箱,我现在已经有“chrys@21cn.comchrys.xie@gmail.comhwxie@ust.hk ……”好多邮箱了,就是为了防止给别人发邮件时被无故退回……撤远了,不好意思。第三:我们有时需要在程序里将某些敏感信息发送至公司邮箱,例如:计算注册码时我们需要用户操作我们的软件将申请注册的信息发送回我们的售后服务邮箱,由我们的工作人员处理来这些邮件。

    大家一定会想用SMTPSimple Mail Transfer Protocol)借助SMTP服务器也能通过程序实现邮件发送,但是有一个很大问题就是安全问题,很多著名的邮件服务器运营商对于用软件方式通过SMTP协议频繁提交邮件转发的申请是不欢迎的,我的163邮箱就曾经深受其害,我那次是在写SMTP客户端发送邮件的程序,顺手就用了163SMTP服务器,我刚发到第5封邮件时就发送失败了,我再登录163网站一查,原来我的账号被封了,原因就是我用软件发送邮件太多了(天啦,才5封而已啊),后来我花了近两个月时间跟新浪公司又赔礼又道歉,还把身份证传真过去了我的账号才被恢复。

    剖析邮件传送过程

    废话说太多请别介意,现在言归正传,要直接将邮件送到对方(POPIMAP)服务器上,而不经过SMTP邮件服务器转交,其实也不难,你只要改用Unix/Linux操作系统,直接SendMail命令就能完成,但在Windows下想要实现这个功能恐怕得花一点心思了。我们首先要从协议RFC821 - Simple Mail Transfer Protocol入手来分析。

    首先我们看一下Email的递送过程:

    邮件原文 编码 SMTP客户端 SMTP转交服务器 远程SMTP服务器(对方邮局)。

    “特快专递”的实现思路

    邮件编码后被递送到一个SMTP转交服务器上,该服务器对信件分检(到同一邮局的被放在一起)后,根据优先级以及信件的先后次序被发送到远程邮局的SMTP服务器上。换句话说,只要我们知道了SMTP转交服务器是如何确定远程邮局SMTP服务器的地址的,就可以直接递送到远程邮局服务器。SMTP转交服务器又是知道远程邮局的地址呢?这就是域名解析所完成的工作了,就好比我们在IE浏览器输入“www.viction.net”这个域名,IE浏览器又如何知道目标服务器的IP地址呢?也是域名解析服务器的功劳。

    电子邮件地址由两部分组成,例如:chrys@163.com,这里的chrys是邮箱名(即用户名,一个用户对应一个邮箱),163.com是邮箱服务器地址,邮箱名和邮箱服务器地址之间以“@”作为分隔。

    我们只要向域名服务器发送查询“163.com”的远程邮局服务器地址便可找到远程邮局SMTP服务器的IP 地址,该查询指令被称作MX(Mail Exchange)邮件交换服务器的地址查询。远程邮局SMTP服务器的地址可能不止一个,这时,你可根据信件优先级的不同来选择对应的远程邮局,我为了安全起见会对每一个远程邮局服务器按照等级高低逐一尝试,只要将邮件成功地发送到其中一个邮局我们的任务就完成了。

    我们要完成几项编程工作:本机DNS的获取、与DNS服务器通信实现MX指令查询、SMTP邮件提交,下面我们一一阐述。

    获取本机DNS

    代码中我封装了一个类CnetAdapterInfo,该类可以获取本机网卡的系列信息,包括本机IP地址、子网掩码、DNSWins、网卡MAC地址等相关信息。

    首先我们需要调用IPHelpAPI 库中的GetAdaptersInfo()函数来获取系统中所有网卡信息。

    DWORD GetAdaptersInfo (

      __out    PIP_ADAPTER_INFO pAdapterInfo,

      __inout  PULONG pOutBufLen

    );

    该函数有两个参数,pAdapterInfo是一个指针,指向一个用户定义的结构体,一般是用HeapAlloc()申请的内存空间,pOutBufLen传入pAdapterInfo所指空间的大小,传出实际需要的缓冲大小,第一次调用该函数时pOutBufLen传入0,函数将返回 ERROR_BUFFER_OVERFLOW 表示需要更多的缓冲,并将实际需要的缓冲长度返回,我们根据实际长度用HeapAlloc()函数申请空间再次调用该函数,以下代码是枚举所有网卡并将信息保存到数组 m_Ary_NetAdapterInfo 中:

    #define MALLOC( bytes ) ::HeapAlloc( ::GetProcessHeap(), HEAP_ZERO_MEMORY, (bytes) )

    #define FREE( ptr )       if( ptr ) ::HeapFree( ::GetProcessHeap(), 0, ptr )

    #define REMALLOC( ptr, bytes ) ::HeapReAlloc( ::GetProcessHeap(), HEAP_ZERO_MEMORY, ptr, bytes )

    //

    // 枚举网络适配器

    // return : ------------------------------------------------------------

    //     -1    -      失败

    //     >=0 -      网络适配器数量

    //

    int CNetAdapterInfo::EnumNetworkAdapters ()

    {

           DeleteAllNetAdapterInfo ();

     

           IP_ADAPTER_INFO* pAdptInfo = NULL;

           IP_ADAPTER_INFO* pNextAd   = NULL;

           ULONG ulLen                             = 0;

           int nCnt                               = 0;

          

           DWORD dwError = ::GetAdaptersInfo ( pAdptInfo, &ulLen );

           if( dwError != ERROR_BUFFER_OVERFLOW ) return -1;

           pAdptInfo = ( IP_ADAPTER_INFO* )MALLOC ( ulLen );

           dwError = ::GetAdaptersInfo( pAdptInfo, &ulLen );

           if ( dwError != ERROR_SUCCESS ) return -1;

          

           pNextAd = pAdptInfo;

           while( pNextAd )

           {

                  COneNetAdapterInfo *pOneNetAdapterInfo = new COneNetAdapterInfo ( pNextAd );

                  if ( pOneNetAdapterInfo )

                  {

                         m_Ary_NetAdapterInfo.Add ( pOneNetAdapterInfo );

                  }

                  nCnt ++;

                  pNextAd = pNextAd->Next;

           }

          

           // free any memory we allocated from the heap before

           // exit.  we wouldn't wanna leave memory leaks now would we? ;p

           FREE( pAdptInfo );             

          

           return nCnt;

    }

    针对每个网卡信息,我们需要调用 GetPerAdapterInfo()函数来获取指定网卡的DNS信息,使用方法和GetAdaptersInfo()类似。以下代码获取网卡基本信息:

    //

    // 根据传入的 pAdptInfo 信息来获取指定网卡的基本信息

    //

    BOOL COneNetAdapterInfo::Init ()

    {

           IP_ADDR_STRING* pNext                = NULL;

           IP_PER_ADAPTER_INFO* pPerAdapt       = NULL;

           ULONG ulLen                                    = 0;

           DWORD dwErr = ERROR_SUCCESS;

           ASSERT ( m_AdptInfo.AddressLength > 0 );

           t_IPINFO iphold;

     

           // 将变量清空

           m_bInitOk = FALSE;

           m_csName.Empty ();

           m_csDesc.Empty ();

           m_CurIPInfo.csIP.Empty ();

           m_CurIPInfo.csSubnet.Empty ();

           m_Ary_IP.RemoveAll ();

           m_Ary_DNS.RemoveAll ();

           m_Ary_Gateway.RemoveAll ();

          

    #ifndef _UNICODE

           m_csName                   = m_AdptInfo.AdapterName;

           m_csDesc                    = m_AdptInfo.Description;

    #else

           USES_CONVERSION;

           m_csName                   = A2W ( m_AdptInfo.AdapterName );

           m_csDesc                    = A2W ( m_AdptInfo.Description );

    #endif

          

           // 获取当前正在使用的IP地址

           if ( m_AdptInfo.CurrentIpAddress )

           {

                  m_CurIPInfo.csIP         = m_AdptInfo.CurrentIpAddress->IpAddress.String;

                  m_CurIPInfo.csSubnet  = m_AdptInfo.CurrentIpAddress->IpMask.String;

           }

           else

           {

                  m_CurIPInfo.csIP         = _T(" 0.0.0 .0");

                  m_CurIPInfo.csSubnet  = _T(" 0.0.0 .0");

           }

          

           // 获取本网卡中所有的IP地址

           pNext = &( m_AdptInfo.IpAddressList );

           while ( pNext )

           {

                  iphold.csIP            = pNext->IpAddress.String;

                  iphold.csSubnet      = pNext->IpMask.String;

                  m_Ary_IP.Add ( iphold );

                  pNext = pNext->Next;

           }

          

           // 获取本网卡中所有的网关信息

           pNext = &( m_AdptInfo.GatewayList );

           while ( pNext )

           {

                  m_Ary_Gateway.Add ( pNext->IpAddress.String );

                  pNext = pNext->Next;

           }

          

           // 获取本网卡中所有的 DNS

           dwErr = ::GetPerAdapterInfo ( m_AdptInfo.Index, pPerAdapt, &ulLen );

           if( dwErr == ERROR_BUFFER_OVERFLOW )

           {

                  pPerAdapt = ( IP_PER_ADAPTER_INFO* ) MALLOC( ulLen );

                  dwErr = ::GetPerAdapterInfo( m_AdptInfo.Index, pPerAdapt, &ulLen );

                 

                  // if we succeed than we need to drop into our loop

                  // and fill the dns array will all available IP

                  // addresses.

                  if( dwErr == ERROR_SUCCESS )

                  {

                         pNext = &( pPerAdapt->DnsServerList );

                         while( pNext )

                         {

                                m_Ary_DNS.Add( pNext->IpAddress.String );

                                pNext = pNext->Next;

                         }                         

                         m_bInitOk = TRUE;

                  }

     

                  // this is done outside the dwErr == ERROR_SUCCES just in case. the macro

                  // uses NULL pointer checking so it is ok if pPerAdapt was never allocated.

                  FREE( pPerAdapt );

           }

     

           return m_bInitOk;

    }

    至此我们已经获取到系统中所有DNS服务器地址了。

    MX指令查询获取远程邮局地址

    DNS服务器通信其实就是一个简单的UDP网络通信,端口号为53,通信的数据格式如下:

     

    所有的DNS消息基本上都是相同的数据结构,但DNS RR是采用了其他的数据结构。

    QNAME是一个表示域长度的变量,表示每一节有多少字节,例如:www.sockets.com将表示为:

     

    最后的“Additional”通常包含了查询服务器期望被发送的纪录以减少通信量,例如,回应MX查询时通常在“Additional”中包含‘A’纪录。

    具体的MX查询过程请参加源代码,以下代码实现了获取本机所有DNS,然后逐一尝试MX查询的方法:

    //

    // 尝试所有的DNS来查询邮局服务器地址

    //

    BOOL GetMX (

           char *pszQuery,                                               // 要查询的域名

           OUT t_Ary_MXHostInfos &Ary_MXHostInfos   // 输出 Mail Exchange 主机名

                         )

    {

           CNetAdapterInfo m_NetAdapterInfo;

           m_NetAdapterInfo.Refresh ();

           int nNetAdapterCount = m_NetAdapterInfo.GetNetCardCount();

           for ( int i=0; i<nNetAdapterCount; i++ )

           {

                  COneNetAdapterInfo *pOneNetAdapterInfo = m_NetAdapterInfo.Get_OneNetAdapterInfo ( i );

                  if ( pOneNetAdapterInfo )

                  {

                         int nDNSCount = pOneNetAdapterInfo->Get_DNSCount ();

                         for ( int j=0; j<nDNSCount; j++ )

                         {

                                CString csDNS = pOneNetAdapterInfo->Get_DNSAddr ( j );

                                if ( GetMX ( pszQuery, csDNS.GetBuffer(0), Ary_MXHostInfos ) )

                                       return TRUE;

                         }

                  }

           }

           return FALSE;

    }

    如果查询“gmail.com”的邮局服务器地址,将得到如下的结果:

    gsmtp147.google.com 50

    gsmtp183.google.com 50

    gmail-smtp-in.l.google.com       5

    alt1.gmail-smtp-in.l.google.com 10

    alt2.gmail-smtp-in.l.google.com 10

    SMTP协议给远程邮局直接发送邮件

    SMTP是一个简单邮件传输协议,通过TCP连接服务器的25端口号即可进行数据通信,以下是我用telnet手工发送邮件的过程:

     

    其中红色矩形框起来的是服务器回应的数据,绿色矩形框起来的是我手工输入的数据,这里发送的邮件内容为“我是手工发送的电子邮件”,邮件被直接发送到chrys.xie@gmail.com邮箱中,不需要讨厌的SMTP服务器中转,当然,因为这是手工发送的邮件,其内容未经过任何MIME编码,这封邮件可以被FoxmailOutlook收到,但可能被判为垃圾邮件,因为这封邮件连标题都没有,是无头苍蝇,肯定是垃圾,呵呵……关于邮件内容的编码请参考其他相关资料,我有一本书,名叫《Visual C++ 网络通信协议分析与应用实》,这本书有详细的电子邮件编码介绍,可以下载电子文档看看。

    当我们知道了SMTP通信的全过程,再编写一个TCP网络通信程序处理与SMTP服务器请求就不是难事了。本代码中的CHwSMTP类已经封装了整个通信过程,可以发送普通的电子邮件,还可以发送带附件的电子邮件,配合DNS查找,远程邮局地址MX查询便可实现任意邮件直接发送到对方邮箱的功能。

    软件操作界面介绍

    程序执行后界面如下:

     

    From中可以输入一个虚假的邮箱地址,也可以输入真实的邮箱地址(如:21cn的邮箱),但不能输入163的邮箱,否则发送会失败,163邮箱不太欢迎大家用软件方式进行邮件收发,人家要靠这个吃饭嘛。

    注意事项

    看到这里是否已经很兴奋了,想着要自己写一个SMTP服务器,甚至想要写一个邮局服务器程序,但我觉得恐怕还没那么容易,我用这个软件给我的google邮箱(gmail)发送邮件,很快就能成功收到了,可我尝试过用这种方式给163邮箱和21cn邮箱发送邮件时却失败了,发给163时服务器说你的IP不被允许,提示信息如下:

    550- 5.7.1 [116.25.186.155] The IP you're using to send mail is not authorized

    550- 5.7.1 to send email directly to our servers. Please use the SMTP

    看来163邮箱只接收大牌邮件服务器发过来的邮件,难怪我们这些免费的163用户发送邮件时常会被退回,原来163服务器还认牌子的,faint

    到底要怎么样做才可以直接给163等著名的邮局发邮件呢?不知道用IP欺骗方式能否成功,请有高手知道解决这个问题的一定要告诉我啊,我的邮箱是:chrys@163.com,先谢过了!

    电子邮件在目前的Internet上被广泛地使用,为了安全很多邮局服务器做了安全认证等诸多限制,我们要想让自己的SMTP服务器能向所有的邮局发邮件,恐怕还得做更多的努力。

    结束语

    知识就是力量,知识共享将具有推动时代进步的力量。希望我能为中国的软件行业尽一份薄力。

    你可以任意修改复制本代码,但请保留版权信息文字不要修改。

    由于水平有限,错误再所难免,请知情者原谅并告知,多谢!

     

    源代码下载

    CSDN 下载:http://download.csdn.net/source/802519

     

    ·谢红

    展开全文
  • 项目中使用PHPmailer,发送邮件客户的邮件服务器,$mail对象返回发送结果为true,结果客户那边的邮箱总是收不邮件。 设置$mail->SMTPDebug = 2,把客户端和服务器端的错误都返回echo出来,结果在chrome的...

    项目中使用PHPmailer,发送邮件到客户的邮件服务器,$mail对象返回发送结果为true,结果客户那边的邮箱总是收不到邮件。


    设置$mail->SMTPDebug = 2,把客户端和服务器端的错误都返回到echo出来,结果在chrome的network里,怎么看都没有发现任何错误的信息。


    试着把收件人的邮箱换为我自己的QQ邮箱,发现可以收到邮件。于是怀疑对方的邮件服务器过滤或者拒收了我发来的邮件。


    接着,我把发件箱换成一个163邮箱去发送,结果对方在垃圾箱里收到了邮件,问题得以解决。

    应该是对方的邮件服务器拒绝了部分发件箱地址导致的问题,解除过滤应该也可以解决。


    在此记录一下这个问题,免得以后忘记

    展开全文
  • 目录1.状态码:100~2.状态码:200~3.状态码:300~4....在爬虫开发过程中,可以根据服务器返回的响应状态码来判断服务器时候成功返回了你想要的内容,如返回(200),还是请求出错,页面找不(404)...


    响应状态码表示服务器的响应状态,在爬虫开发过程中,向服务器发送一个请求,服务器会根据用户的请求返回相应的响应,响应分为三类:响应状态码、响应头和响应体。
    在爬虫开发过程中,可以根据服务器返回的响应状态码来判断服务器时候成功返回了你想要的内容,如返回(200),还是请求出错,页面找不到(404),又或者是服务器识破了你的爬虫,封禁了你的爬虫(500)。爬虫开发者可以根据这些返回的响应状态码来快速纠错,提高纠错能力。

    其实不管是在爬虫开发过程中,还是日常的网页开发,客户端开发,包括运维,知道特定状态码所代表的含义,对排除bug、故障都有很大的帮助。

    (ps:大神请忽略此段废话)


    ~~ 华丽的分割线 ~~


    下面几张表给出了常见的100、200、300、400、500系的响应状态码所代表的含义

    1.状态码:100~

    状态码说明详情
    100继续请求者应当继续提出请求,服务器已经收到请求的一部分,正在等待其余部分
    101切换协议请求者已要求服务器切换协议,服务器已确认并准备切换

    2.状态码:200~

    状态码说明详情
    200成功服务器已经成功处理了请求
    201已创建请求成功并且服务器创建了新的资源
    202已接受服务器已接受请求,但尚未处理
    203非授权信息服务器已经成功处理了请求,但返回的信息可能来自另一个源
    204无内容服务器成功处理了请求,但没有返回任何内容
    205重置内容服务器成功处理了请求,内容被重置
    206部分内容服务器成功处理了部分请求

    3.状态码:300~

    状态码说明详情
    300客户请求的文档可以再多个位置找到
    301客户请求的文档在其他地方新的URL在Location头中给出
    302类似301
    303同上
    304客户端有缓冲的文档并发送一个条件性请求
    305客户请求的文档应该通过Location头所指明的代理服务器提取
    307同302

    4.状态码:400~

    状态码说明详情
    400请求出现语法错误
    401客户视图未经授权访问受密码保护的页面
    403资源不可用服务器get到了你发送的请求,但是拒绝处理
    404无法找打页面也就是无法找到指定位置的资源,一般是URL输入错误导致
    405请求方法不适用如GET、POST等
    406Not Acceptable指定的资源已经找到,但它的MIME类型和客户在Accept头中所指定的不兼容
    407类似401
    408请求过期在服务器许可的等待时间内,客户端一直没有发出任何请求
    409请求冲突
    410请求的资源已经不可用
    411服务器不能处理该请求
    412请求头中指定的一些前提条件失败
    413目标文档大小超过服务器允许的大小
    414URL过长
    415服务器无法处理请求附带的媒体格式
    416服务器不能满足客户在请求中指定的Range头

    5.状态码:500~

    状态码说明详情
    500服务器遇到情况,满足请求
    501服务器无法支持发送请求所需要的功能
    502Bad Gateway作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应。
    503服务器因为维护或者负载过重未能应答
    504Gateway Time-out作为网关或者代理工作的服务器尝试执行请求时,未能及时从上游服务器(URI标识出的服务器,例如HTTP、FTP、LDAP)或者辅助服务器(例如DNS)收到响应。
    505服务器不支持请求中指明的HTTP版本

    参考博客:
    https://www.cnblogs.com/lvchunhao/p/3852908.html
    https://blog.csdn.net/qq_24802115/article/details/53898565

    展开全文
  • 通过http post方法,使用代理向目标服务器发送请求,第一次对方能正常收到数据,第二次 的时候对方显示能接到请求但是收不数据,连接改为直连后就没问题了。自己的代理服务器 通过抓包查询都是正常的,每次发...
  • 下载源代码 前言大家一定熟悉Foxmail中的“特快专递”,它能直接将电子邮件发送到对方的邮件服务器中,而不需要经过SMTP服务器中转,这样做有什么好处?第一:发送速度比较快,不需要等SMTP服务器对邮件进行查毒、...
  • 发送邮件程序,突然某一天遇到不能发送邮件。... Java报告 “无法连接到服务器” 换命令telnet 目标机器->>失败 检查防火墙->>防火墙状态OK 关闭防火墙->>问题依旧 换smtp服务器->>同样失败 换机器->>居然telnet可行,
  • 大家好,我是时间财富网智能客服时间君,上述问题将由我为大家进行解答。soul聊天状态异常,消息发送失败的原因及解决方法如下:1、...3、消息显示发送成功,但是对方表示收不消息,这种情况需要截图反映给客服,...
  • 大家好,我是时间财富网智能客服时间君,上述问题将由我为大家进行解答。soul聊天状态异常,消息发送失败的原因及解决方法如下:1、...3、消息显示发送成功,但是对方表示收不消息,这种情况需要截图反映给客服,...
  • 最近公司有需求,使用netty服务器来操作设备,这个设备是应答模式,就是上位机发送指令后,下位机会应答(应答才表示指令发送成功),开始的时候我使用的是发送心跳包(心跳包是一个指令,设备会应答)的方式来确定...
  • 阿里云ECS服务器无法使用25端口发送

    千次阅读 2017-01-22 11:45:34
    在部署项目阿里的ECS服务器上的时候,发现邮件无法发送,报出异常javax.mail.MessagingException: Could not connect to SMTP host: smtp.163.com, port: 25;
  • 同时,你可能需要安装一个简单的CS聊天工具,来测试你的代码是否成功的在openfire服务器上建立会话链接,并成功的向在线用户发送聊天消息。 必须了解:...
  • foxmail邮件发送成功

    千次阅读 热门讨论 2014-01-14 09:42:35
     通过查阅相关资料发现集中解决方案如下:翻译过来是: 请检查电子邮件服务器的回复:550 数字550是错误代码 服务器返回“550: Invalid User”、“550:local user only”或者“551 delivery not allowed to
  • 阿里云服务器发送邮件(一)

    千次阅读 2018-03-30 10:12:10
    mailx采用指令mail进行发送邮件的编辑,通过sendmail进行发送; 1、 检查服务状况 service sendmail status 问题:没有结果的话,则重新安装sendmail邮件服务。本项目采用suse企业版系统。 卸载指令:zypper ...
  • 其实javamial只是给我们提供了链接设置的某个邮箱的服务器上,具体的发邮件还是通过服务器发送的。下面是代码() 1.sendEmailUtil(连接邮箱服务器,验证权限,填充发送内容,我用的是163邮箱smtp协议,也可以使用...
  • 直接nslookup,看了一下该域名的A记录,Telnet测试连接该A记录指向的服务器25端口,成功! 查询发现,如果发邮件过程中.查询不该域名的MX记录,会再次查询A记录,如果有A记录,向A记录指向的地址投递信件.
  • 将代码部署服务器,每日早上定时获取天气数据,并发送邮箱。 也可以说是一个小人工智障。 思路可以运用在不同地方,主要介绍的是思路。
  • 本篇只是配置php+postfix 实现发送邮件功能 一.环境条件(我这里使用阿里云的linux系统) 1.yum 命令可用 2.sendmail确定卸载 3.php环境安装好的 二.步骤 在安装之前有一个解析域名的步骤(这一步是转载...
  • Sender -> Senders SMTP server -> Receivers SMTP server -> Receiver 正常流程,SMTP是这样应用的,需要自己的服务器中转。 其实可以直接Sender - > Receivers SMTP server ->Receiver. Foxmail里面其实也...
  • 现在结和我所学,我想总结一下客户端到服务器端的通信过程。只有明白了原理,我们才会明白当我们程序开发过程中错误的问题会出现在那,才会更好的解决问题。 我们首先要了解一个概念性的词汇:Socket socket的英文...
  • 基于JavaMail的Java邮件发送:简单邮件发送

    万次阅读 多人点赞 2016-06-14 21:56:35
    电子邮件的应用非常广泛,例如在某网站注册了一个账户,自动发送一封欢迎邮件,通过邮件找回密码,自动批量发送活动信息等。...本文将简单介绍如何通过 Java 代码来创建电子邮件,并连接邮件服务器发...
  • iOS开发中,一般都是通过AFN搭建一个简易的网络模块来进行与服务器的通信,这一模块要优化好没那么简单,需要花费很多时间与精力,仅仅根据这几年来的填坑经验,总结下这一块的需要注意的地方,也是给自己梳理下知识...
  • 被问懵逼了:TCP 握手没成功怎么办?

    万次阅读 多人点赞 2021-08-19 19:32:42
    第一次握手,如果客户端发送的SYN一直都传不服务器,那么客户端是一直重发SYN永久吗?客户端停止重发SYN的时机是什么? 第三次握手,如果服务器永远不会收到ACK,服务器就永远都留在 Syn-Recv 状态了吗?...
  • java socket编程服务器接收不数据

    万次阅读 2015-12-04 13:15:25
    本人在socket编程的时候,连接上服务器后直接发送一个字符串过去,但服务器始终没有接收数据。查了很多资料也没有找到,...这种方式是可以成功传送到服务器。 二、如果想在线程中不断的接收和发送数据(socket一直
  • http请求从浏览器到服务器过程

    万次阅读 2018-01-25 14:37:07
    前些天刚看完《How Tomcat Works》,虽然书比较老,但是看完后收获还是挺多的,懂得了tomcat的内部...FIN:对方终止发送数据(Finally)(对方没有数据再发送给你啦) 好相信知道这么多,再面http请求应该不慌了。
  • TCP服务器

    千次阅读 2018-06-28 16:44:58
    (1)创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器) int socket(int domain, int type, int protocol); socket()打开⼀个网络通讯端口,如果成功的话就像open()⼀样返回⼀个文件描述符 应⽤程序可以像读写...
  • web服务器

    千次阅读 多人点赞 2021-03-24 00:34:17
    web服务器 服务器的相关概念 服务器与客户端 服务器的概念:提供网络服务的一台机器,通过在自己的电脑上安装特殊的软件(或者是运行某段特殊的代码)来提供服务 服务器 = 电脑 + 可以给其他电脑提供服务的软件 ...
  • 近来工作上接收一项任务,实现c++后台服务器程序,要求它能承载千万级别的DAU读写请求。目前实现千万级高并发海量数据...假设客户端要上传一张图片,它会将图片数据发送给API服务器程序,后者从数据库服务器集群中选
  • 暂时假象的2种场景,一种是android实时录制音频,通过编码rtp打包到服务器服务器接收并转发,客户端能够实时播放。这个是单向音频传输的想法,与spydroid处理音频类似,但由于spydroid本身的音频在我小米1s上测试...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 84,737
精华内容 33,894
关键字:

成功发送到对方服务器