有时候需要在家里连接公司的服务器,但是它的外网IP会经常变换。当然可以用花生壳之类来解决,不过作为程序员,还是尝试用程序的方法解决吧。
接口定义
Windows里在外网IP改变时好像没有现成的事件,所以定义了这样一个接口IPublicIPMonitor:
public interface IPublicIPMonitor
{
event EventHandler<PublicIpChangedEventArgs> PublicIpChanged;
void Start();
void Stop();
}
当监测到IP改变时会触发一个事件PublicIpChanged,有两个方法分别用来开始和停止监测。
事件参数PublicIpChangedEventArgs的定义如下,只有一个叫IP的属性:
[Serializable]
public class PublicIpChangedEventArgs : EventArgs
{
public string IP { get; private set; }
public PublicIpChangedEventArgs(string ip)
{
IP = ip;
}
}
还有一个IPublicIPNotifyReceiver的接口用来对IP改变事件进行响应:
public interface IPublicIPNotifyReceiver
{
void Listen(IPublicIPMonitor monitor);
void OnPublicIPChanged(PublicIpChangedEventArgs arg);
}
它可以监听某个IPublicIPMonitor并在OnPublicIPChanged里响应。
IPublicIPMonitor实现
实现思路是定时去获取当前IP,如果和之前保存的IP不一样的话则触发PublicIpChanged事件:
public class PublicIPMonitor : IPublicIPMonitor
{
System.Timers.Timer timer;
string lastIp;
static Regex rgx = new Regex("((2[0-4]\\d|25[0-5]|[01]?\\d\\d?)\\.){3}(2[0-4]\\d|25[0-5]|[01]?\\d\\d?)", RegexOptions.Compiled);
public event EventHandler<PublicIpChangedEventArgs> PublicIpChanged;
protected virtual void OnPublicIpChanged(object o, PublicIpChangedEventArgs e)
{
if (PublicIpChanged != null) PublicIpChanged(o, e);
}
public PublicIPMonitor()
: this(TimeSpan.FromMinutes(30))
{
}
public PublicIPMonitor(TimeSpan span)
{
timer = new System.Timers.Timer(span.TotalMilliseconds);
timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed);
}
public void Start()
{
timer.Enabled = true;
timer_Elapsed(null, null);
timer.Start();
}
public void Stop()
{
timer.Stop();
}
void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
var ip = GetPublicIp();
if (ip == lastIp) { return; }
lastIp = ip;
OnPublicIpChanged(this, new PublicIpChangedEventArgs(ip));
}
public static string GetPublicIp()
{
try
{
string strUrl = "http://www.ip138.com/ip2city.asp";
Uri uri = new Uri(strUrl);
System.Net.WebRequest wr = System.Net.WebRequest.Create(uri);
System.IO.Stream s = wr.GetResponse().GetResponseStream();
System.IO.StreamReader sr = new System.IO.StreamReader(s, Encoding.ASCII);
string all = sr.ReadToEnd();
var match=rgx.Match(all);
if(!match.Success)
return null;
var ip = match.Value;
return ip;
}
catch (System.Net.WebException)
{
}
return null;
}
}
IPublicIPNotifyReceiver实现
可以用短信、邮件等等需要的方式响应IP改变事件,这里用邮件做例子:
public class EmailNotifyReceiver : PublicIPNotifyReceiver
{
static string receiveEmailAddress;
public EmailNotifyReceiver() {
receiveEmailAddress = File.ReadAllText(System.AppDomain.CurrentDomain.BaseDirectory + "\\EmailNotifyReceiverAddress.config");
}
public override void OnPublicIPChanged(PublicIpChangedEventArgs e)
{
var tries = 3;
var success = false;
while (tries > 0 && !success)
{
try
{
Send("smtp.qq.com", "***@qq.com", "***",
"***@qq.com", receiveEmailAddress, "ip", e.IP);
success = true;
}
catch
{
System.Threading.Thread.Sleep(1000 * 60 * 5);
}
}
}
void Send(string smtpHost, string user, string password, string mailFrom, string mailTo, string subject, string body)
{
MailAddress from = new MailAddress(mailFrom); //邮件的发件人
//MailAddress to = new MailAddress(mailTo); //邮件的收件人
MailMessage mail = new MailMessage();
//设置邮件的标题
mail.Subject = subject;
//设置邮件的发件人
//Pass:如果不想显示自己的邮箱地址,这里可以填符合mail格式的任意名称,真正发mail的用户不在这里设定,这个仅仅只做显示用
mail.From = from;
//设置邮件的收件人
mail.To.Add(mailTo);
//设置邮件的内容
mail.Body = body;
//设置邮件的格式
mail.BodyEncoding = System.Text.Encoding.UTF8;
mail.IsBodyHtml = true;
//设置邮件的发送级别
mail.Priority = MailPriority.Normal;
mail.DeliveryNotificationOptions = DeliveryNotificationOptions.OnSuccess;
SmtpClient client = new SmtpClient();
//设置用于 SMTP 事务的主机的名称,填IP地址也可以了
client.Host = smtpHost;
//设置用于 SMTP 事务的端口,默认的是 25
//client.Port = 465;
//client.EnableSsl = true;
client.UseDefaultCredentials = false;
//这里才是真正的邮箱登陆名和密码,比如我的邮箱地址是 hbgx@hotmail, 我的用户名为 hbgx ,我的密码是 xgbh
client.Credentials = new System.Net.NetworkCredential(user, password);
client.DeliveryMethod = SmtpDeliveryMethod.Network;
//都定义完了,正式发送了,很是简单吧!
//client.Timeout = 30;
client.Send(mail);
}
}
在EmailNotifyReceiver的构造函数里,程序从EmailNotifyReceiverAddress.config里读取需要接收通知的邮件地址(用,隔开)。然后在OnPublicIPChanged里尝试给他们发邮件。
程序入口
现在已经可以实现我们需要的功能了,我们可以把IPublicIPMonitor的实现放在一个Windows服务里,然后在其OnStart方法里添加下面代码:
protected override void OnStart(string[] args)
{
var monitor = new PublicIPMonitor();
var receiverList = ImplementsLoader.LoadImplementsOf<IPublicIPNotifyReceiver>();
foreach (var receiver in receiverList)
{
receiver.Listen(monitor);
}
monitor.Start();
}
ImplementsLoader类
在上面的代码里有这样一个方法ImplementsLoader.LoadImplementsOf<IPublicIPNotifyReceiver>()。它的作用是获取程序所在目录所有的IPublicIPNotifyReceiver的实现。这样当我们添加一个新的接收者的时候不需要改现有代码,只要实现IPublicIPNotifyReceiver接口,然后把这个dll复制到程序目录下就可以了。
public class ImplementsLoader
{
public static T[] LoadImplementsOf<T>() where T : class
{
var files = System.IO.Directory.GetFiles(System.AppDomain.CurrentDomain.BaseDirectory, "*.dll");
var list = new List<T>();
foreach (var f in files)
{
list.AddRange(LoadFromAssembly<T>(Assembly.LoadFile(f)));
}
return list.ToArray();
}
static T[] LoadFromAssembly<T>(Assembly ass) where T : class
{
return ass.GetTypes().Where(t => !t.IsInterface && !t.IsAbstract && typeof(T).IsAssignableFrom(t)).Select(t => Activator.CreateInstance(t) as T).ToArray();
}
}
一些改进想法
在程序入口里创建IPublicIPMonitor实例的时候,可以用ioc或者读取配置文件等方式。还有EmailNotifyReceiver在代码里写发送者的邮箱密码也不太好,可以放到配置文件里并做加密。。。
浙公网安备 33010602011771号