蛙蛙推荐:写程序给IIS的SMTP服务加上邮件组功能
摘要:windows自带的smtp服务不支持mail group功能,难道只能用exchange了吗?不,我们可以编程实现。昨天发了一个配置邮件服务器的帖子,被DUDU老大移到其它技术区了,这个应该不会被移除首页了吧。
上回文和大家分享了用windows2003自带的服务架设一个完整的邮件服务器的过程,但是作为一个企业,肯定有不同的组织,我们希望发送给Developer@fetionmm.com的邮件,所有开发人员都能收到,发给Tester@fetionmm.com的邮件,所有测试人员都能收到,这本是ex的Features,但今天我们来自己实现。
思路:比如我们新建了ms-onlytiancai的域,那么所有的用户的收件箱的邮件都默认放到C:\Inetpub\mailroot\Mailbox\ms-onlytiancai\目录的用户名子目录下,我们新建一个all用户作为一个邮件组,然后用c#的FileSystemWatch监控这个用户的收件箱目录是否有新邮件,如果有新邮件那么复制到所有其他用户的收件箱目录下,基本原理就是这些。其它就是把邮件组的成员设置成可配置的,在拷贝邮件的时候考虑邮件被其它进程占用的情况的容错和补偿处理。
没看上篇帖子的,先看上篇帖子
蛙蛙推荐:自己架设一个邮件服务器
http://www.cnblogs.com/onlytiancai/archive/2008/09/16/1292089.html
通过本文你可以学习到如下:
1、.net 2.0的自定义配置
2、拷贝文件如何折中考虑多进程并发问题
3、怎样最简单的制作windows服务
4、怎样让IIS自带的smtp服务支持mail group功能
先定义邮件组配置,如下
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="MailGroup" type="WawaSoft.MailGroup.Config,MailGroupLib"/>
</configSections>
<MailGroup>
<Groups>
<add Dir="C:\Inetpub\mailroot\Mailbox\ms-onlytiancai\P3_all.mbx">
<Members>
<add Dir="C:\Inetpub\mailroot\Mailbox\ms-onlytiancai\P3_huhao.mbx"></add>
</Members>
</add>
</Groups>
</MailGroup>
</configuration>
其中Groups\add是可重复的,意思就是可以建立多个组,Members\add也是可重复的,意思是一个组可以有多个成员,dir属性就是邮件组或成员的收件箱目录,这段自定义配置的处理代码如下
using System.Configuration;

namespace WawaSoft.MailGroup


{
public class Config : ConfigurationSection

{

单件实现#region 单件实现

private static Config _config;

public static Config Instance

{
get

{
if (_config == null)

{
lock (typeof (Config))

{
if (_config == null)
_config = ConfigurationManager.GetSection("MailGroup") as Config;
}
}
return _config;
}
}

#endregion


MailGroup#region MailGroup

[ConfigurationProperty("Groups")]
internal GroupCollection Groups

{

get
{ return (GroupCollection) this["Groups"]; }
}

#endregion


Groups#region Groups

internal class GroupCollection : ConfigurationElementCollection

{
public Group this[int index]

{

get
{ return (Group) BaseGet(index); }
}

public new Group this[string dir]

{

get
{ return (Group) BaseGet(dir); }
}

public override ConfigurationElementCollectionType CollectionType

{

get
{ return ConfigurationElementCollectionType.AddRemoveClearMap; }
}

protected override ConfigurationElement CreateNewElement()

{
return new Group();
}

protected override object GetElementKey(ConfigurationElement element)

{
return ((Group) element).Dir;
}
}

#endregion


Group#region Group

internal class Group : ConfigurationElement

{
[ConfigurationProperty("Dir")]
public string Dir

{

get
{ return (string) this["Dir"]; }
}

[ConfigurationProperty("Members")]
public MemberCollection Members

{

get
{ return (MemberCollection) this["Members"]; }
}
}

#endregion


Members#region Members

internal class MemberCollection : ConfigurationElementCollection

{
public Member this[int index]

{

get
{ return (Member) BaseGet(index); }
}

public new Member this[string dir]

{

get
{ return (Member) BaseGet(dir); }
}

protected override ConfigurationElement CreateNewElement()

{
return new Member();
}

protected override object GetElementKey(ConfigurationElement element)

{
return ((Member) element).Dir;
}
}

#endregion


Member#region Member

internal class Member : ConfigurationElement

{
[ConfigurationProperty("Dir")]
public string Dir

{

get
{ return (string) this["Dir"]; }
}
}

#endregion
}
}


然后就是读取配置,监控邮件组收件箱,复制到成员收件箱,如下
using System;
using System.IO;
using WawaSoft.Common;
using System.Threading;

namespace WawaSoft.MailGroup


{
public class MailWatcher

{
private static readonly ITracer _tracer = TracerFactory.GetTrace(typeof (MailWatcher));

public static void Start()

{
_tracer.Info("启动成功");
Config.GroupCollection collection = Config.Instance.Groups;
foreach (Config.Group group in collection)

{
if (!Directory.Exists(group.Dir))

{
_tracer.Info("{0}不存在\r\n", group.Dir);
continue;
}
FileSystemWatcher fileSystemWatcher =
new FileSystemWatcher(group.Dir, "*.eml");
_tracer.Info("监听{0}", group.Dir);
Config.Group group1 = group;
fileSystemWatcher.Created +=
delegate(object sender, FileSystemEventArgs e)

{
try

{
Config.MemberCollection members = group1.Members;
foreach (Config.Member member in members)

{
try

{
string target = string.Format("{0}\\{1}",
member.Dir, e.Name);
ReliableCopy(e.FullPath, target, 0);
_tracer.Info("copy {0} to {1}",
e.FullPath, target);
}
catch (Exception ex)

{
_tracer.Error(ex, "复制邮件出错");
}
}
File.Delete(e.FullPath);
}
catch (Exception ex2)

{
_tracer.Error(ex2, "执行出错");
}
};
fileSystemWatcher.EnableRaisingEvents = true;
}
}

private static void ReliableCopy(string path, string target, int retryCount)

{
if(retryCount>0)
_tracer.Warn("正在执行第{0}次重试:{1}",retryCount,target);
if (retryCount > 5)
throw new ApplicationException(string.Format(
"重试次数超过限制:{0}", target));
try

{
File.Copy(path, target, true);
}
catch (IOException) //捕获IO异常

{
Thread.Sleep(5000);
ReliableCopy(path,target, ++retryCount);
}
//其它异常直接抛给调用者
}
}
}


代码很简单,但又时候邮件特别大的话,邮件组的收件箱接受邮件需要一定的时间,而文件刚创建就会触发FileSystemWatch的事件,就会开始尝试拷贝复制文件,而要复制的文件,正在接受新的字节,写入到自身,这种情况下进行File.Copy会报该文件被其它进程使用,所以我们拷贝的时候如果遇到这个错,可以重试5次,每次重试Sleep 5秒,这也是一个折中的办法,当然用try catch性能会查一些,你可以可以考虑用win32 api来判断文件状态,再做个循环检查直到可以复制,当然是有这样的API的话。
然后创建一个Service类,如下
using System.ServiceProcess;

namespace WawaSoft.MailGroup


{
internal partial class Service1 : ServiceBase

{
public Service1()

{
InitializeComponent();
}

protected override void OnStart(string[] args)

{
MailWatcher.Start();
}
}
}


并在属性面板里给服务设置ServiceName属性,点这个服务属性下面的“添加安装程序”按钮,会添加一个ProjectInstaller.cs类,选中serviceProcessInstaller1,设置Accout为local system,选中serviceInstaller1,设置DispayName和ServiceName为MailGroupService,把StartType设置为automatic,就是自动启动就行了,这样就是一个windows服务了。
安装的使用用以下命令安装服务
C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\installutil MailGroupLib.exe
pause
最后在services.msc里启用服务就行了。
测试:用telnet给all@ms-onlytiancai发邮件,看huhao@ms-onlytiancai能否收到