源代码的下载地址(到下边的页面去下:
http://itboy.cn/product/index.asp
一、前言
foxmail新版中有一个《邮件特快专递》的功能。起先搞不懂如何用,后来知道要在 工具->系统选项 那边设置 本地DNS服务器的IP地址。
觉得这个新功能蛮好用的。不需要通过SMTP代理,可以直接通过本地往邮箱所在的邮件交换器发送邮件。在暑假一开始想在VC++中实现这个功能。用IRIS截包后,发现程序后 mx8.263.net发送邮箱,不知道这个是什么东西所以作罢。 后来才想到这个就是263.net的MX记录主机,原来特快专递的原理就是往这个主机上发送数据就行。
运行nslookup程序:
set type=mx
263.net
有了,有了,得到结果:
Non-authoritative answer:
263.net MX preference = 10, mail exchanger = mx06.263.net
263.net MX preference = 10, mail exchanger = mx08.263.net
263.net MX preference = 10, mail exchanger = mx09.263.net
263.net MX preference = 10, mail exchanger = mx11.263.net
263.net MX preference = 10, mail exchanger = mx12.263.net
263.net MX preference = 40, mail exchanger = mx03.263.net
263.net MX preference = 10, mail exchanger = mx01.263.net
没有错了。就是这个了。后来因为不知道怎么实现nslookup的功能,就放弃了,学了半个多月的C#。后来偶然在网上查找到了一些相关的文档。几次实验。把我的开发过程拿过来分享,我第一次写教程性文档。所以不规范之处,请大家包涵。本文涉及的域名、邮箱及IP均为真实的。
二、DNS协议原理
我认为,要想成为一个好的网络软件程序员,必须得读懂RFC文档。因为本文是面向大多广泛程序爱好者,所以我尽量从细节上写,如果高手的话,可以跳过此部分。
DNS协议的相关RFC文档:
RFC1034-《 DOMAIN NAMES - CONCEPTS AND FACILITIES》
RFC1035-《 DOMAIN NAMES - IMPLEMENTATION AND SPECIFICATION》
网上的计算机用形如 220.162.75.1 这样称为IP地址的数字串来标识一台计算机。而如果每次访问一台计算机都是通过输入这样的东东来访问,那不就太可怕了?以是出了DNS这样的好东东,用要指示其绑定的IP地址,当我们在浏览器内输入 http://zzsy.com 时,浏览器不知道网页该到哪里取,于是就向设定好的DNS服务器查询 zzsy.com这个域名。DNS服务器会先寻找自己的记录库,如果没有发现就转向上一级DNS服务器进行查询(转发请求)。把找到后的IP告知你的浏览器。这里边浏览器查询的记录类型是A记录。RFC1035文档第11页中定义有16种记录类型,而 常见的有A(地址)记录、CNAME(别名)记录、MX(邮件交换)记录。我们本篇要关心的是MX记录。
查询的过程一般是:客户向DNS服务器的53端口发送UDP报文,DNS服务器收到后进行处理,并把结果记录仍以UDP报文的形式返回过来。
此UDP报文的一般格式:
+---------------------+ | 报文头 | +---------------------+ | 问题 | 向服务器提出的查询部分 +---------------------+ | 回答 | 服务器回复的资源记录 +---------------------+ | 授权 | 权威的资源记录 +---------------------+ | 格外的 | 格外的资源记录 +---------------------+除了报文头是固定的12字节外,其他每一部分的长度均为不定字节数。
我们在这边关心的是报文头、问题、回答这三个部分。
其中 报文头的格式:
1 1 1 1 1 1 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ID | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ |QR| Opcode |AA|TC|RD|RA| Z | RCODE | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | QDCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ANCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | NSCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | ARCOUNT | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
好家伙,是什么鬼画符!
其中最上边是位的数字标识,0-15(注意,后边的10-15写成上下的形式了,一开始我楞没看懂)。
接下来是:
ID:占16位,2个字节。此报文的编号,由客户端指定。DNS回复时带上此标识,以指示处理的对应请应请求。
QR:占1位,1/8字节。0代表查询,1代表DNS回复
Opcode:占4位,1/2字节。指示查询种类:0:标准查询;1:反向查询;2:服务器状态查询;3-15:未使用。
AA:占1位,1/8字节。是否权威回复。
TC:占1位,1/8字节。因为一个UDP报文为512字节,所以该位指示是否截掉超过的部分。
RD:占1位,1/8字节。此位在查询中指定,回复时相同。设置为1指示服务器进行递归查询。
RA:占1位,1/8字节。由DNS回复返回指定,说明DNS服务器是否支持递归查询。
Z:占3位,3/8字节。保留字段,必须设置为0。
RCODE:占4位,1/2字节。由回复时指定的返回码:0:无差错;1:格式错;2:DNS出错;3:域名不存在;4:DNS不支持这类查询;5:DNS拒绝查询;6-15:保留字段。
QDCOUNT:占16位,2字节。一个无符号数指示查询记录的个数。
ANCOUNT:占16位,2字节。一个无符号数指明回复记录的个数。
NSCOUNT:占16位,2字节。一个无符号数指明权威记录的个数。
ARCOUNT:占16位,2字节。一个无符号数指明格外记录的个数。
其中每个查询的资源记录格式:
1 1 1 1 1 1 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | | / QNAME / / / +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | QTYPE | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | QCLASS | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+
QNAME:不定长,表示要查询的域名。(两边的方框用 / 来表示不定长)
QTYPE:2字节,根据RFC1035及nslookup的帮助文档,我定义以下枚举类型:
enum QueryType //查询的资源记录类型。
{
A=0x01, //指定计算机 IP 地址。
NS=0x02, //指定用于命名区域的 DNS 名称服务器。
MD=0x03, //指定邮件接收站(此类型已经过时了,使用MX代替)
MF=0x04, //指定邮件中转站(此类型已经过时了,使用MX代替)
CNAME=0x05, //指定用于别名的规范名称。
SOA=0x06, //指定用于 DNS 区域的“起始授权机构”。
MB=0x07, //指定邮箱域名。
MG=0x08, //指定邮件组成员。
MR=0x09, //指定邮件重命名域名。
NULL=0x0A, //指定空的资源记录
WKS=0x0B, //描述已知服务。
PTR=0x0C, //如果查询是 IP 地址,则指定计算机名;否则指定指向其它信息的指针。
HINFO=0x0D, //指定计算机 CPU 以及操作系统类型。
MINFO=0x0E, //指定邮箱或邮件列表信息。
MX=0x0F, //指定邮件交换器。
TXT=0x10, //指定文本信息。
UINFO=0x64, //指定用户信息。
UID=0x65, //指定用户标识符。
GID=0x66, //指定组名的组标识符。
ANY=0xFF //指定所有数据类型。
};
QTYPE:2字节。 根据RFC1035及nslookup的帮助文档,我定义以下枚举类型:
enum QueryClass //指定信息的协议组。
{
IN=0x01, //指定 Internet 类别。
CSNET=0x02, //指定 CSNET 类别。(已过时)
CHAOS=0x03, //指定 Chaos 类别。
HESIOD=0x04,//指定 MIT Athena Hesiod 类别。
ANY=0xFF //指定任何以前列出的通配符。
};
QTYPE中的A,MX,CNAME为常用,QCLASS中的IN为常用。
其中每个回复的记录格式:
1 1 1 1 1 1 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | | / / / NAME / | | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | TYPE | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | CLASS | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | TTL | | | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+ | RDLENGTH | +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--| / RDATA / / / +--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+--+NAME:回复查询的域名,不定长。
TYPE:回复的类型。2字节,与查询同义。指示RDATA中的资源记录类型。
CLASS:回复的类。2字节,与查询同义。指示RDATA中的资源记录类。
TTL:生存时间。4字节,指示RDATA中的资源记录在缓存的生存时间。
RDLENGTH:长度。2字节,指示RDATA块的长度。
RDATA:资源记录。不定义,依TYPE的不同,此记录的格示不同,通常一个MX记录是由一个2字节的指示该邮件交换器的优先级值及不定长的邮件交换器名组成的。
这边述说一下名称的组合形式。 名称由多个标识序列组成,每一个标识序列的首字节说明该标识符的长度,接着用是ASCII码表示字符,多个序列之后由字节0表示名字结束。其中某一个标识序列的首字符的长度若是0xC0的话,表示下一字节指示不是标识符序列,而是指示接下部分在本接收包内的偏移位置。
比如 bbs.zzsy.com 以.分开bbs、zzsy、com三个部分。每个部分的长度为3、4、3
在DNS报文中的形式就如 3 b b s 4 z z s y 3 c o m 0
假如在包内的第12个字节位置存在有 4 z z s y 3 c o m 0 这样的名称了。
那此时有可能为:3 b b s 4 z z s y 0xC0 0x0C 这样的形式。
三、DNS协议实例讲解
说了这么多理论屁话,可能头都有两个大了吧。还是用一个实例的方法来说明吧。
我选用著名的网络截包及协议分析工具IRIS 4.05,您可以从我的站点上下载:
http://itboy.cn/data/Iris405Full.rar
运行Iris,点击菜单的Filters 选 Port标签页 运用 53 端口后点确定。
点击Iris工具栏上的绿色运行图标进行监听。
在windows中运行nslookup程序。
输入以下命令:
set type=mx
然后返回nslookup程序。
再输入命令:
yahoo.com.cn
会得到
yahoo.com.cn MX preference = 20, mail exchanger = mx5.mail.yahoo.com
yahoo.com.cn MX preference = 10, mail exchanger = mta-v1.mail.vip.cnb.yahoo.com
这样的两个MX资源记录。此时的Iris截包如图:
当前的图示显示出的包是第二个报文,即从DNS回复过来的报文。第1个报文是查询报文,比回复报文简单多了。因此如果分析得懂回复报文,查询报文相信聪明如你一样可以轻松搞定。
图中显示的红色部分的数据为DNS报文包,上边的数据内容分别为MAC协义包、PPP协议包,IPv4协议包及UDP协议包。这些包不在本文范围内,不关注。
(插说一些废话:点选Iris左侧树中显示的条目,在右边的数据包中不一定正确反映,这点我一开始不知道,点中一个资源后按其错误的指示分析,分析了半天,也不知所言。我对包的分析是在此次写这个程序中学会的,以前大二时网络课没有好好去听我们学院那个少有的工程师兼教授的网络课,也怪我们系,咋不叫一个漂亮的MM过来教,那我会非常专心地听的:)。言归正传)
为了说明的方便,我把包中经色的DNS协议部分处理一下,如下图:
其中红色部分为包头,蓝色为查询部分,绿色为回复的第一条资源记录,金色为回复的第二条资源记录。其他的没有划起来的就权威记录、额外记录,不要分析考虑。
红色部分:
第0字节,第1字节 00 03 标识一个ID。
第2字节,第3字字 81 80 化成二进制形式 1000 0001 1000 0000
QR(0)为1表示是回复报文。
Opcode(2-5)为0表示标准查询。
AA(6)为0表示非权威查询。
TC(7)为0表示不超过512字节的包不截断。
RD(8)为1表示nslookup程序指示DNS进行递归查询。
RA(9)为1表示DNS支持递归查询。
Z(10-12)保留字段
RCODE(13-16)为0表示查询无查错
第4字节,第5字节 00 01 表示查询的资源记录数为1
第6字节,第7字节 00 02 表示回复的资源记录数为2
第8字节,第9字节 00 06 表示权威的资源记录数为6
第10字节,第11字节 00 04 表示额外的资源记录数为4
接着到达了蓝色部分,查询的资源记录部分:
一开始是查询的域名。
开始 05 表示后边的五个字节是序列字符,把 79 61 68 6F 6F 转为ASCII码为yahoo
到达 03 表示后边的三个字节是序列字符,把 63 6F 6D 转为ASCII码为com
到达 02 表示后边的二个字节是序列字符,把 63 6E 转为ASCII码为cn
到达 00 表示结束。
整个串起来,用.连接,即为:yahoo.com.cn
接下来的两个字节 00 0F 表示查询类型为15,即为MX记录查询。
再接下来两个字节 00 01 表示查询类为1,即为Internet连接。
Iris包中的第一个包中的DNS报文只包含红线及蓝线,分析方法相同。不再赘述。
绿色部分,回复的第一个资源记录部分:
一开始为查询的域名,
首字节C0表示压缩,接下来的位为偏移字节位。
接下来是0C表示跳到整个包中的第12个字节上,即为蓝色的第一个字节。
然后接着与上边的分析相同,得到域名为yahoo.com.cn
然后返回,到下一个字节
绿色包中的3-4字节 00 0F 表示查询类型为15,即为MX记录查询。
再接下来的5-6字节 00 01 表示查询类为1,即为Internet连接。
再接下来的7-10字节 00 00 05 95 化为十进制等于1429秒。即缓存时间为23分49秒。
接着11-12字节 00 16 指示本资源记录的数据部分为22字节。即剩下来的字节数。
从13字节开始的22字节为MX记录的数据部分。此部分的格式为两字节的邮件交换器优先级值,不定长的邮件交换器名。
第13-14字节 00 14 表示优先级值为20
接着从15开始是邮件交换器名部分,依蓝色的域名分析方法,得到mx5.mail.yahoo.com
(此段所说字节均是绿色块中的相对位置,而非整个DNS包的绝对位置)
金色那块的方析方法与绿色的大同小异,但是有一点不同的是,看金色包的邮件交换器名部分。
从包的绝对位置,第78位(图中标紫色78的那里)开始是邮件交换器名。我们进行分析,得到mta-v1.mail.vip.cnb此时接下来是0XC0表示压缩,再接下来的偏移是0x0C又跳到包的12字节上,得到yahoo.com.cn。整个合起来就是第二资源记录的邮件交换器名:mta-v1.mail.vip.cnb.yahoo.com
这样的结果与nslookup运行结果:
yahoo.com.cn MX preference = 20, mail exchanger = mx5.mail.yahoo.com
yahoo.com.cn MX preference = 10, mail exchanger = mta-v1.mail.vip.cnb.yahoo.com
相同。
希望此节能帮助网络爱好者学会包的分析方法。下边我们现行DNS中MX记录查询的编程要点分析。
四、DNS查询MX记录编程要点分析
foxmail 5.0 的邮件特快专递有一个很垃圾的地方是无法自动获得本地ISP的DNS服务器,还需要用户的手工输入,我想是因为API的局限或是foxmail开发组没有想到方法或是其他的不为我知的缘故吧。不管他,在C#中利用WMI,很容易的:
string[] dnses; ManagementClass mc = new ManagementClass("Win32_NetworkAdapterConfiguration"); ManagementObjectCollection moc = mc.GetInstances(); //枚举当前机子上的所有网卡 foreach(ManagementObject mo in moc) { if((bool)mo["ipEnabled"]) { dnses = (string[]) mo["DNSServerSearchOrder"]; if (dnses!=null) { dnsServer=dnses[0]; //使用第一个找到的DNS服务器。 } } }
把这个放在DnsQuery类的静态构造函数中。
还有一个自定义的MxRecord结构,用于存放一个MX资源记录的信息。
struct MxRecord { public string domain; //查询的域名 public QueryType queryType; //查询类型 public QueryClass queryClass; //查询类 public TimeSpan liveTime; //生存时间 public int dataLength; //资源部分的长度,即指示邮件服务器的优先级及名称的那部分资源的字节数。 public int preference; //优先级值,其值越小越优先. public string name; //邮件交换器名 }
其他的就不多说了,在程序的DnsQuery类中,我做了详细的代码注释,如果您有一些开发经验的话,应该很容易看得懂的,如果有疑问的话,欢迎联系我。我在精力及能力许可的范围内帮您解答。
五、分析Foxmail的特快专递发送数据。
1.运行ipconfig/all
把得到的Dns Servers的第一个IP地址记录下来。
2.还是运行Iris.设置Filters为SMTP,25端口。运行以便监听。
3.运行Foxmail5.0,在 工具 -> 系统设置 内的“邮件特快专递”标签页设定域名服务器1为刚刚的IP地址。
点“撰写”,用文本文件的格式编写。
收件人: dreamchild@263.net
抄送:
主题: 你好dreamchild,冒昧打饶
内容:
尊敬的dreamchild先生:
这是一封邮件。
附件:(添加 mm.gif 及 说明.txt 两个文件。为了保证此教程的完整性,把这两个额外的东东也放进包内了。)
4.完成后停止Iris,点其菜单栏的Decode 得到以下内容:
220 Welcome to coremail System(With Anti-Spam) 2.1 for 263(040326) HELO dreamchild 250 mta6.x263.net MAIL FROM: <> 250 Ok RCPT TO: 250 Ok Data 354 End data with . Date: Thu, 9 Sep 2004 01:00:35 +0800 From: "=?gb2312?B?w87Qobqi?=" < > To: "dreamchild" Subject: =?gb2312?B?xOO6w2RyZWFtY2hpbGSjrMOww8G08sjE?= X-mailer: Foxmail 5.0 [cn] Mime-Version: 1.0 Content-Type: multipart/mixed; boundary="=====001_Dragon788446150325_=====" This is a multi-part message in MIME format. --=====001_Dragon788446150325_===== Content-Type: text/plain; charset="gb2312" Content-Transfer-Encoding: base64 1/C+tLXEZHJlYW1jaGlsZM/Iyfqjug0KoaGhodXiysfSu7fi08q8/qGjDQo= --=====001_Dragon788446150325_===== Content-Type: image/gif; name="MM.GIF" Content-Transfer-Encoding: base64 Content-Disposition: attachment; filename="MM.GIF" R0lGODlheAB4AIddABwNCixIjJtjkLO8iZux0NK2xpVJT9XayJWNuDA0Vt5Xh+Sw7dLc7p6Fh3mD (这边省略去关于一陀 MM.gif 文件内容的Base64编码) w75UTdHFHwD/1Ex6cBSs0jHpKaySFXI4XCwAEhPkRsUSBQQAOw== --=====001_Dragon788446150325_===== Content-Type: application/octet-stream; name="=?gb2312?B?y7XD9y50eHQ=?=" Content-Transfer-Encoding: base64 Content-Disposition: attachment; filename="=?gb2312?B?y7XD9y50eHQ=?=" 1eK49k1Nv8mwrrK7v8mwrqGjDQo= --=====001_Dragon788446150325_=====-- . 250 Ok: queued as 7CDA613A26E QUIT 221 Bye
这边涉及到的就是SMTP协议了,其中文版的RFC821文档 http://itboy.cn/data/rfc821.doc
因为是中文版的,所以大家花些时间看,这边就不再赘述原理了。只列出状态标识
211 系统状态或系统帮助响应
214 帮助信息
220
服务就绪
221
服务关闭传输信道
250 要求的邮件操作完成
251 用户非本地,将转发向
354 开始邮件输入,以
.
结束
421
服务未就绪,关闭传输信道(当必须关闭时,此应答可以作为对任何命令的响应)
450 要求的邮件操作未完成,邮箱不可用(例如,邮箱忙)
451 放弃要求的操作;处理过程中出错
452 系统存储不足,要求的操作未执行
500 格式错误,命令不可识别(此错误也包括命令行过长)
501 参数格式错误
502 命令不可实现
503 错误的命令序列
504 命令参数不可实现
550 要求的邮件操作未完成,邮箱不可用(例如,邮箱未找到,或不可访问)
551 用户非本地,请尝试
552 过量的存储分配,要求的操作未执行
553 邮箱名不可用,要求的操作未执行(例如邮箱格式错误)
554 操作失败
可以从此看出,与一般的通过SMTP代理不同的是少了SMTP服务器的指定及其验证的用户名跟密码。
描述一下整个过程:
首先通过前述的方法得到263.net的一个邮件交换器,然后连到这个交换器上。然后连到此服务器的25端口上,
服务器返回220。
然后依次指示用户名,发送邮箱(人),接收邮箱(人)。接收写入邮件的数据。
数据分为邮件头及邮件的正文两部分。
邮件头包含:时间,发送邮箱(人),接收邮箱(人),主题,发信程序,MIME版本号,邮件内容的类型及分割符。
当中有一些用BASE64编码的字符串就是原来的中文汉字,其实,我们在制作无SMTP代理邮件发送程序时可以直接写成中文的。
这边就讲一下邮件内容的类型及分割符,其他的很容易理解的。
这边的邮件内容类型是
multipart/mixed; 说明是由多种格式混合成的。
分隔符,是用于分隔邮件内容部分与各个附件。用
boundary关键字及键值来定义。
比如本例用
=====001_Dragon788446150325_=====来表示,这边有一个细节问题,键值最好要用"引起来,并不要出现空格。举个例子,如果你用
boundary======001_Dragon788446150325_=====来表示的话,那FOXMAIl5.0将无法正确对邮件进行处理,邮件的内容部分被当成整个BASE64乱码文本,然而我登陆到263.net的网站去收信可以看到邮件被正常转化。
而邮件的内容部分是通过两个减号
--再连上分隔符来分隔各部分的。
邮件主体从第一个
--=====001_Dragon788446150325_=====开始,到第二个
--=====001_Dragon788446150325_=====为内容的第一部分
Content-Type: text/plain;
charset="gb2312" Content-Transfer-Encoding: base64
这两句说明了其类型及内容的字符集和编码。
在这边是指定的是base64,然后一个空行,再加上“尊敬的dreamchild先生:/r/n 这是一封邮件。”这个字符串的BASE64编码构成邮件的正文部分。
实际上,我们可以指定
Content-Transfer-Encoding:8bit然后就可以在正文部分用上原本表示了。
接下来是隔开的附件1部分,
多了一个
Content-Disposition: attachment;以说明这部分是附件,以及相关的文件名
filename="MM.GIF"。
附件内容部分是把文件读成一个字节数组,然后把字节数组转为base64编码的字符串。这边的是
mm.gif这个文件内容。
第三部分是附件2
测试.txt 文件,
测试.txt 又被foxmail处理成base64格式了,可以用原文表示的。
最后完了之后,用“回车换行加一个.号再一个回车换行”表示Data部分的结束。
如若正确过发送到达服务器,那就返回一个
250状态。
然后用
Quit命令跟服务器3166
我们先定义一个邮件结构,以描述邮件的各个属性。
public struct MailContent //邮件的内容 { public string to;//收件人地址 public string toname;//收件人姓名 public string from;//发件人地址 public string fromname;//发件人姓名 public string title;//主题 public string body;//文本内容 public bool useAttachment; //是否使用附件 public string [] attachmentList; //附件列表 }
其中取值过程中包括判断邮箱格式是否正确,我们用正则表达式来判断。如下:
Regex.IsMatch(邮箱字符串, @"^([/w-/.]+)@((/[[0-9]{1,3}/.[0-9]{1,3}/.[0-9]{1,3}/.)|(([/w-]+/.)+))([a-zA-Z]{2,4}|[0-9]{1,3})(/]?)$")
如果返回的值为假的话,就用错误提示器提示。
赋值完后,就开创一个工作线程进行邮件的发送。
线程一开始先通过当地DNS取得邮箱域名部分的邮件交换器列表。然后取得其最小优先级值的交换器用于以后的发送。 比如取得 dreamchild@263.net这个邮箱的域名 263.net的一个优先级值为10的MX记录 mx12.263.net
然后就是连到服务器发送命令及接收返回信息。以下列出一些代码:
关于创建Sock套接字:
TcpClient sock = new TcpClient(); NetworkStream netStream; sock.NoDelay = true; //不使用延时算法,以加快小数据包的发送。 sock.ReceiveTimeout = 10000; //接收超时为10秒。 sock.Connect(server,port); netStream = sock.GetStream();
关于Send方法:
byte [] sendArray = Encoding.Default.GetBytes(sendString); netStream.Write(sendArray,0,sendArray.Length);
关于Receive方法:
const int MaxReceiveSize = 1460; .... byte [] buffer=new byte[MaxReceiveSize]; length = netStream.Read(buffer, 0, MaxReceiveSize); if (length == 0) return null; .... receiveString = Encoding.Default.GetString(buffer, 0, length);
这边设置一次接收1460字节,但其实返回码用不了这么多字节,这个是以前我在写基于HTTP网络程序时写的接收函数一部分,保留了这个方法。
接收到后就再转回字符串返回。
send = "Date: {$time}/r/n" + "From: {$fromname} <{$from}>/r/n" + "To: {$toname} <{$to}>/r/n" + "Subject: {$title}/r/n" + "X-mailer: NoSmtpSender [蔡晓晖 制作]/r/n" + "MIME_Version:1.0/r/n" + "Content-type:multipart/mixed;Boundary=/"{$splitline}/"/r/n" + "/r/n" //头部结束,开始正文部分。 + "--{$splitline}/r/n" //内容部分。 + "Content-type:text/plain;Charset=gb2312/r/n" + "Content-Transfer-Encoding:8bit/r/n" + "/r/n" + "{$body}/r/n" + "/r/n"; attachment = ""; attachment += "--{$splitline}/r/n"; attachment += "Content-Type:application/octet-stream;Name={$filename}/r/n"; attachment += "Content-Disposition:attachment;FileName={$filename}/r/n"; attachment += "Content-Transfer-Encoding:Base64/r/n"; attachment += "/r/n"; attachment = attachment.Replace("{$splitline}",splitLine); //先进行替换,以防加上附件内容后替换耗时。 attachment = attachment.Replace("{$filename}",file.Name); attachment += Convert.ToBase64String(fileBytes,0,length); attachment += "/r/n/r/n"; send += attachment;
DateTime.Now.ToString("R").Replace("GMT","中国标准时间")
我不知道是不是VS2003的BUG,用"R"参数输出当前时间的RFC格式的格林威治时间,没有把本土化时间自动转换,比如现在的中国时间9点,它没有减去8小时。而只是直接在当前时间后边加上“GMT”。于是我们就把“GMT”改“中国标准时间”了以校时。
按一问一答的方式与邮件交换器“交流”,如果其间有错的话,就把错误的信息保存下来,然后 return false;
如果整个发送过程没有错的话就可以
netStream.Close();
sock.Close();
结束。
程序的界面截图:
七、最后的话
非常感谢您很有耐心地看完我的啰嗦。我花了四个通宵(其实是早上睡觉)时间编写软件及教程的辛苦值得了。这份软件算我暑假学习半个月C#后的一份比较好的成品吧。我是从大二开始学习真正的编程,用了九个月的VB及写了一年又三个月的C++/MFC程序。现在在学C#。感觉有C++的基础入门C#挺容易的。
本文件的邮件附件的发送方法参照罗前辈( http://www.luocong.com)的C++源码,不然可能又要多发上一天去思考了。在此感谢罗老师的无私。
写完这个东东后,再过四天,我的暑假就结束了,下个学期是大四了。很迷惘的一个学年,我想在毕业后能到上海找到一份好工作。我想请业内的前辈指引我一下,以我现在的水平要跨过上海大软件公司的门槛还要爬多高?我在这剩下一年内会更加努力去超越的的。
您可以任意地传播本软件、源码及相关文档,但请保留完整性。
如若用于商业用途需经我的同意。
我的联系方式:
通讯地址:漳州师范学院082信箱(363000)
姓名:蔡晓晖
电邮:
QQ:22415
希望我们能交个朋友。
如果一年内有问题需要我解答的话,请到http://bbs.zzsy.com上的【信息技术】版块发贴询问。
我会在精力及能力许可范围内帮您解答的。
[网络编程]用C#下的Raw Socket编程实现网络封包监视 2004-9-3 | |
谈起socket编程,大家也许会想起QQ和IE,没错。还有许多网络工具如P2P、NetMeeting等在应用层实现的应用程序,也是用socket来实现的。Socket是一个网络编程接口,实现于网络应用层,Windows Socket包括了一套系统组件,充分利用了Microsoft Windows 消息驱动的特点。Socket规范1.1版是在1993年1月发行的,并广泛用于此后出现的Windows9x操作系统中。Socket规范2.2版(其在Windows平台上的版本是Winsock2.2,也叫Winsock2)在 1996 年 5 月发行,Windows NT 5.0及以后版本的Windows系统支持Winsock2,在Winsock2中,支持多个传输协议的原始套接字,重叠I/O模型、服务质量控制等。 本文向大家介绍Windows Sockets的一些关于用C#实现的原始套接字(Raw Socket)的编程,以及在此基础上实现的网络封包监视技术。同Winsock1相比,Winsock2最明显的就是支持了Raw Socket套接字类型,使用Raw Socket,可把网卡设置成混杂模式,在这种模式下,我们可以收到网络上的IP包,当然包括目的不是本机的IP包,通过原始套接字,我们也可以更加自如地控制Windows下的多种协议,而且能够对网络底层的传输机制进行控制。 在本文例子中,我在nbyte.BasicClass命名空间实现了RawSocket类,它包含了我们实现数据包监视的核心技术。在实现这个类之前,需要先写一个IP头结构,来暂时存放一些有关网络封包的信息:
这样,当每一个封包到达时候,可以用强制类型转化把包中的数据流转化为一个个IPHeader对象。 这里的SIO_RCVALL是指示RawSocket接收所有的数据包,在以后的IOContrl函数中要用,在下面的构造函数中,实现了对一些变量参数的初始化: public RawSocket() //构造函数 下面的函数实现了创建RawSocket,并把它与终结点(IPEndPoint:本机IP和端口)绑定: if (SetSocketOption()==false) error_occurred=true; 第一个参数是设定地址族,MSDN上的描述是“指定 Socket 实例用来解析地址的寻址方案”,当要把套接字绑定到终结点(IPEndPoint)时,需要使用InterNetwork成员,即采用IP版本4的地址格式,这也是当今大多数套接字编程所采用一个寻址方案(AddressFamily)。 第二个参数设置的套接字类型就是我们使用的Raw类型了,SocketType是一个枚举数据类型,Raw套接字类型支持对基础传输协议的访问。通过使用 SocketType.Raw,你不光可以使用传输控制协议(Tcp)和用户数据报协议(Udp)进行通信,也可以使用网际消息控制协议 (Icmp) 和 Internet 组管理协议 (Igmp) 来进行通信。在发送时,您的应用程序必须提供完整的 IP 标头。所接收的数据报在返回时会保持其 IP 标头和选项不变。 第三个参数设置协议类型,Socket 类使用 ProtocolType 枚举数据类型向 Windows Socket API 通知所请求的协议。这里使用的是IP协议,所以要采用ProtocolType.IP参数。 在CreateAndBindSocket函数中有一个自定义的SetSocketOption函数,它和Socket类中的SetSocketOption不同,我们在这里定义的是具有IO控制功能的SetSocketOption,它的定义如下: private bool SetSocketOption() //设置raw socket 其中,设置套接字选项时必须使套接字包含IP包头,否则无法填充IPHeader结构,也无法获得数据包信息。 int WSAIoctl( C#的IOControl函数不像WSAIoctl函数那么复杂,其中只包括其中的控制操作码、输入字节流、输出字节流三个参数,不过这三个参数已经足够了。我们看到函数中定义了一个字节数组:byte []IN = new byte[4]{1, 0, 0, 0}实际上它是一个值为1的DWORD或是Int32,同样byte []OUT = new byte[4];也是,它整和了一个int,作为WSAIoctl函数中参数lpcbBytesReturned指向的值。 public bool ErrorOccurred 下面的函数实现的数据包的接收: //解析接收的数据包,形成PacketArrivedEventArgs事件数据类对象,并引发PacketArrival事件 fixed(byte *fixed_buf = buf) temp_version =(uint)(head->ip_verlen & 0xF0) >> 4;//提取IP协议版本 //以下语句提取出了PacketArrivedEventArgs对象中的其他参数 temp_srcport = *(short *)&fixed_buf[e.HeaderLength]; e.PacketLength =(uint)len; e.ReceiveBuffer=buf; 大家注意到了,在上面的函数中,我们使用了指针这种所谓的不安全代码,可见在C#中指针和移位运算这些原始操作也可以给程序员带来编程上的便利。在函数中声明PacketArrivedEventArgs类对象,以便通过OnPacketArrival(e)函数通过事件把数据包信息传递出去。其中PacketArrivedEventArgs类是RawSocket类中的嵌套类,它继承了系统事件(Event)类,封装了数据包的IP、端口、协议等其他数据包头中包含的信息。在启动接收数据包的函数中,我们使用了异步操作的方法,以下函数开启了异步监听的接口: public void Run() //开始监听 Socket.BeginReceive函数返回了一个异步操作的接口,并在此接口的生成函数BeginReceive中声明了异步回调函数CallReceive,并把接收到的网络数据流传给receive_buf_bytes,这样就可用一个带有异步操作的接口参数的异步回调函数不断地接收数据包: private void CallReceive(IAsyncResult ar)//异步回调 此函数当挂起或结束异步读取后去接收一个新的数据包,这样能保证让每一个数据包都能够被程序探测到。 public delegate void PacketArrivedEventHandler(Object sender, PacketArrivedEventArgs args); 这样就可以实现对数据包信息的获取,采用异步回调函数,可以提高接收数据包的效率,并通过代理事件把封包信息传递到外界。既然能把所有的封包信息传递出去,就可以实现对数据包的分析了:)不过RawSocket的任务还没有完,最后不要望了关闭套接字啊: public void Shutdown() //关闭raw socket 以上介绍了RawSocket类通过构造IP头获取了包中的信息,并通过异步回调函数实现了数据包的接收,并使用时间代理句柄和自定义的数据包信息事件类把数据包信息发送出去,从而实现了网络数据包的监视,这样我们就可以在外部添加一些函数对数据包进行分析了。 |
[网络编程]MX记录获取组件的编写 2004-9-4 | ||||||
一、MX记录的应用
点击这里下载源代码>> |
[网络编程]Windows 2000下的Raw Socket编程 2004-9-3 | |
Windows2000在TCP/IP协议组件上做了很多改进,功能也有增强。比如在协议栈上的调整,增大了默认窗口大小,以及高延迟链接新算法。同时在安全性上,可应用IPSec加强安全性,比NT下有不少的改进。 Microsoft TCP/IP 组件包含“核心协议”、“服务”及两者之间的“接口”。传输驱动程序接口 (TDI) 与网络设备接口规范 (NDIS) 是公用的。 此外,还有许多用户模型应用程序的更高级接口。最常用的接口是 Windows Sockets、远程过程调用 (RPC) 和 NetBIOS。 Windows Sockets 是一个编程接口,它是在加州大学伯克利分校开发的套接字接口的基础上定义的。它包括了一组扩展件,以充分利用 Microsoft Windows 消息驱动的特点。规范的 1.1 版是在 1993 年 1 月发行的,2.2.0 版在 1996 年 5 月发行。Windows 2000 支持 Winsock 2.2 版。在Winsock2中,支持多个传输协议的原始套接字,重叠I/O模型、服务质量控制等。 这里介绍Windows Sockets的一些关于原始套接字(Raw Socket)的编程。同Winsock1相比,最明显的就是支持了Raw Socket套接字类型,通过原始套接字,我们可以更加自如地控制Windows下的多种协议,而且能够对网络底层的传输机制进行控制。 1、创建一个原始套接字,并设置IP头选项。 SOCKET sock; sock = socket(AF_INET,SOCK_RAW,IPPROTO_IP); 或者: s = WSASoccket(AF_INET,SOCK_RAW,IPPROTO_IP,NULL,0,WSA_FLAG_OVERLAPPED); 这里,我们设置了SOCK_RAW标志,表示我们声明的是一个原始套接字类型。创建原始套接字后,IP头就会包含在接收的数据中,如果我们设定 IP_HDRINCL 选项,那么,就需要自己来构造IP头。注意,如果设置IP_HDRINCL 选项,那么必须具有 administrator权限,要不就必须修改注册表: HKEY_LOCAL_MACHINE/System/CurrentControlSet/Services/Afd/Parameter/ 修改键:DisableRawSecurity(类型为DWORD),把值修改为 1。如果没有,就添加。 BOOL blnFlag=TRUE; setsockopt(sock, IPPROTO_IP, IP_HDRINCL, (char *)&blnFlag, sizeof(blnFlag); 对于原始套接字在接收数据报的时候,要注意这么几点: 1、如果接收的数据报中协议类型和定义的原始套接字匹配,那么,接收的所有数据就拷贝到套接字中。 2、如果绑定了本地地址,那么只有接收数据IP头中对应的远端地址匹配,接收的数据就拷贝到套接字中。 3、如果定义的是外部地址,比如使用connect(),那么,只有接收数据IP头中对应的源地址匹配,接收的数据就拷贝到套接字中。 2、构造IP头和TCP头 这里,提供IP头和TCP头的结构: // Standard TCP flags #define URG 0x20 #define ACK 0x10 #define PSH 0x08 #define RST 0x04 #define SYN 0x02 #define FIN 0x01 typedef struct _iphdr //定义IP首部 { unsigned char h_lenver; //4位首部长度+4位IP版本号 unsigned char tos; //8位服务类型TOS unsigned short total_len; //16位总长度(字节) unsigned short ident; //16位标识 unsigned short frag_and_flags; //3位标志位 unsigned char ttl; //8位生存时间 TTL unsigned char proto; //8位协议 (TCP, UDP 或其他) unsigned short checksum; //16位IP首部校验和 unsigned int sourceIP; //32位源IP地址 unsigned int destIP; //32位目的IP地址 }IP_HEADER; typedef struct psd_hdr //定义TCP伪首部 { unsigned long saddr; //源地址 unsigned long daddr; //目的地址 char mbz; char ptcl; //协议类型 unsigned short tcpl; //TCP长度 }PSD_HEADER; typedef struct _tcphdr //定义TCP首部 { USHORT th_sport; //16位源端口 USHORT th_dport; //16位目的端口 unsigned int th_seq; //32位序列号 unsigned int th_ack; //32位确认号 unsigned char th_lenres; //4位首部长度/6位保留字 unsigned char th_flag; //6位标志位 USHORT th_win; //16位窗口大小 USHORT th_sum; //16位校验和 USHORT th_urp; //16位紧急数据偏移量 }TCP_HEADER; TCP伪首部并不是真正存在的,只是用于计算检验和。校验和函数: USHORT checksum(USHORT *buffer, int size) { unsigned long cksum=0; while (size > 1) { cksum += *buffer++; size -= sizeof(USHORT); } if (size) { cksum += *(UCHAR*)buffer; } cksum = (cksum >> 16) + (cksum & 0xffff); cksum += (cksum >>16); return (USHORT)(~cksum); } 当需要自己填充IP头部和TCP头部的时候,就同时需要自己计算他们的检验和。 3、发送原始套接字数据报 填充这些头部稍微麻烦点,发送就相对简单多了。只需要使用sendto()就OK。 sendto(sock, (char*)&tcpHeader, sizeof(tcpHeader), 0, (sockaddr*)&addr_in,sizeof(addr_in)); 下面是一个示例程序,可以作为SYN扫描的一部分。 #include #include #include #define SOURCE_PORT 7234 #define MAX_RECEIVEBYTE 255 typedef struct ip_hdr //定义IP首部 { unsigned char h_verlen; //4位首部长度,4位IP版本号 unsigned char tos; //8位服务类型TOS unsigned short total_len; //16位总长度(字节) unsigned short ident; //16位标识 unsigned short frag_and_flags; //3位标志位 unsigned char ttl; //8位生存时间 TTL unsigned char proto; //8位协议 (TCP, UDP 或其他) unsigned short checksum; //16位IP首部校验和 unsigned int sourceIP; //32位源IP地址 unsigned int destIP; //32位目的IP地址 }IPHEADER; typedef struct tsd_hdr //定义TCP伪首部 { unsigned long saddr; //源地址 unsigned long daddr; //目的地址 char mbz; char ptcl; //协议类型 unsigned short tcpl; //TCP长度 }PSDHEADER; typedef struct tcp_hdr //定义TCP首部 { USHORT th_sport; //16位源端口 USHORT th_dport; //16位目的端口 unsigned int th_seq; //32位序列号 unsigned int th_ack; //32位确认号 unsigned char th_lenres; //4位首部长度/6位保留字 unsigned char th_flag; //6位标志位 USHORT th_win; //16位窗口大小 USHORT th_sum; //16位校验和 USHORT th_urp; //16位紧急数据偏移量 }TCPHEADER; //CheckSum:计算校验和的子函数 USHORT checksum(USHORT *buffer, int size) { unsigned long cksum=0; while(size >1) { cksum+=*buffer++; size -=sizeof(USHORT); } if(size ) { cksum += *(UCHAR*)buffer; } cksum = (cksum >> 16) + (cksum & 0xffff); cksum += (cksum >>16); return (USHORT)(~cksum); } void useage() { printf("******************************************/n"); printf("TCPPing/n"); printf("/t Written by Refdom/n"); printf("/t Email: refdom@263.net/n"); printf("Useage: TCPPing.exe Target_ip Target_port /n"); printf("*******************************************/n"); } int main(int argc, char* argv[]) { WSADATA WSAData; SOCKET sock; SOCKADDR_IN addr_in; IPHEADER ipHeader; TCPHEADER tcpHeader; PSDHEADER psdHeader; char szSendBuf[60]={0}; BOOL flag; int rect,nTimeOver; useage(); if (argc!= 3) { return false; } if (WSAStartup(MAKEWORD(2,2), &WSAData)!=0) { printf("WSAStartup Error!/n"); return false; } if ((sock=WSASocket(AF_INET,SOCK_RAW,IPPROTO_RAW,NULL,0,WSA_FLAG_OVERLAPPED))==INVALID_SOCKET) { printf("Socket Setup Error!/n"); return false; } flag=true; if (setsockopt(sock,IPPROTO_IP, IP_HDRINCL,(char *)&flag,sizeof(flag))==SOCKET_ERROR) { printf("setsockopt IP_HDRINCL error!/n"); return false; } nTimeOver=1000; if (setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, (char*)&nTimeOver, sizeof(nTimeOver))==SOCKET_ERROR) { printf("setsockopt SO_SNDTIMEO error!/n"); return false; } addr_in.sin_family=AF_INET; addr_in.sin_port=htons(atoi(argv[2])); addr_in.sin_addr.S_un.S_addr=inet_addr(argv[1]); // // //填充IP首部 ipHeader.h_verlen=(4<<4 | sizeof(ipHeader)/sizeof(unsigned long)); // ipHeader.tos=0; ipHeader.total_len=htons(sizeof(ipHeader)+sizeof(tcpHeader)); ipHeader.ident=1; ipHeader.frag_and_flags=0; ipHeader.ttl=128; ipHeader.proto=IPPROTO_TCP; ipHeader.checksum=0; ipHeader.sourceIP=inet_addr("本地地址"); ipHeader.destIP=inet_addr(argv[1]); //填充TCP首部 tcpHeader.th_dport=htons(atoi(argv[2])); tcpHeader.th_sport=htons(SOURCE_PORT); //源端口号 tcpHeader.th_seq=htonl(0x12345678); tcpHeader.th_ack=0; tcpHeader.th_lenres=(sizeof(tcpHeader)/4<<4|0); tcpHeader.th_flag=2; //修改这里来实现不同的标志位探测,2是SYN,1是FIN,16是ACK探测 等等 tcpHeader.th_win=htons(512); tcpHeader.th_urp=0; tcpHeader.th_sum=0; psdHeader.saddr=ipHeader.sourceIP; psdHeader.daddr=ipHeader.destIP; psdHeader.mbz=0; psdHeader.ptcl=IPPROTO_TCP; psdHeader.tcpl=htons(sizeof(tcpHeader)); //计算校验和 memcpy(szSendBuf, &psdHeader, sizeof(psdHeader)); memcpy(szSendBuf+sizeof(psdHeader), &tcpHeader, sizeof(tcpHeader)); tcpHeader.th_sum=checksum((USHORT *)szSendBuf,sizeof(psdHeader)+sizeof(tcpHeader)); memcpy(szSendBuf, &ipHeader, sizeof(ipHeader)); memcpy(szSendBuf+sizeof(ipHeader), &tcpHeader, sizeof(tcpHeader)); memset(szSendBuf+sizeof(ipHeader)+sizeof(tcpHeader), 0, 4); ipHeader.checksum=checksum((USHORT *)szSendBuf, sizeof(ipHeader)+sizeof(tcpHeader)); memcpy(szSendBuf, &ipHeader, sizeof(ipHeader)); rect=sendto(sock, szSendBuf, sizeof(ipHeader)+sizeof(tcpHeader), 0, (struct sockaddr*)&addr_in, sizeof(addr_in)); if (rect==SOCKET_ERROR) { printf("send error!:%d/n",WSAGetLastError()); return false; } else printf("send ok!/n"); closesocket(sock); WSACleanup(); return 0; } 4、接收数据 和发送原始套接字数据相比,接收就比较麻烦了。因为在WIN我们不能用recv()来接收raw socket上的数据,这是因为,所有的IP包都是先递交给系统核心,然后再传输到用户程序,当发送一个raws socket包的时候(比如syn),核心并不知道,也没有这个数据被发送或者连接建立的记录,因此,当远端主机回应的时候,系统核心就把这些包都全部丢掉,从而到不了应用程序上。所以,就不能简单地使用接收函数来接收这些数据报。 要达到接收数据的目的,就必须采用嗅探,接收所有通过的数据包,然后进行筛选,留下符合我们需要的。可以再定义一个原始套接字,用来完成接收数据的任务,需要设置SIO_RCVALL,表示接收所有的数据。 SOCKET sniffersock; sniffsock = WSASocket(AF_INET, SOCK_RAW, IPPROTO_IP, NULL, 0, WSA_FLAG_OVERLAPPED); DWORD lpvBuffer = 1; DWORD lpcbBytesReturned = 0 ; WSAIoctl(sniffersock, SIO_RCVALL, &lpvBuffer, sizeof(lpvBuffer), NULL, 0, & lpcbBytesReturned, NULL, NULL); 创建一个用于接收数据的原始套接字,我们可以用接收函数来接收数据包了。然后在使用一个过滤函数达到筛选的目的,接收我们需要的数据包。 在 http://www.xici.net/board/doc.asp?id=5891453&sub=15 提供了一个完整的发送和接收的程序,可以参考。 | |
如何把一个*.CS文件编译成一个DLL文件啊?(谢谢)
http://community.csdn.net/Expert/topic/3367/3367953.xml?temp=.1050531
作 者: lyzhong79 (雪狼湖)
等 级:
信 誉 值: 100
所属社区: .NET技术 C#
问题点数: 10
回复次数: 4
发表时间: 2004-09-13 16:29:34
比如我在一个项目中,添加了一个*.CS文件,当我编译这个项目的时候这个*.CS文件并没有被编译成*.DLL文件.是在一个项目中添加的*.CS文件,不是新建CLASS项目,请问大家,要怎么才能编译成*.DLL文件呢?
谢谢了
回复人: zyhjolly(zyhjolly) ( ) 信誉:100 2004-09-13 16:43:00 得分: 0
如果想把这个cs文件编译成dll,新建“类库”项目,把cs文件加进去再编译
Top
回复人: designonline(meetweb) ( ) 信誉:100 2004-09-13 16:43:00 得分: 0
项目中便编译的话,只有一个dll
如果需要单独编译
命令行示例
编译 File.cs 以产生 File.exe:
csc File.cs
编译 File.cs 以产生 File.dll:
csc /target:library File.cs
编译 File.cs 并创建 My.exe:
csc /out:My.exe File.cs
通过使用优化和定义 DEBUG 符号,编译当前目录中所有的 C# 文件。输出为 File2.exe:
csc /define:DEBUG /optimize /out:File2.exe *.cs
编译当前目录中所有的 C# 文件,以产生 File2.dll 的调试版本。不显示任何徽标和警告:
csc /target:library /out:File2.dll /warn:0 /nologo /debug *.cs
将当前目录中所有的 C# 文件编译为 Something.xyz(一个 DLL):
csc /target:library /out:Something.xyz *.cs
Top
回复人: laodeng72586(只是路过) ( ) 信誉:100 2004-09-13 16:49:00 得分: 0
例子:
启动VS.net的命令行工具;
csc /t:library /r:System.dll,System.Web.Services.dll,System.Xml.dll Service1.cs
说明:
csc: C#编译器如果是 VB.net应该为vbc
/t:library 编译结果为dll文件
/r: 为引用的命名空间
Service1.cs 为要编译的代码文件
Top
回复人: lyzhong79(雪狼湖) ( ) 信誉:100 2004-09-13 16:58:00 得分: 0
不能用VS自动编译吗?这样做很麻烦的
一楼说的方法,需要新建个项目-类库(这方法主要是不方便调试,如果编译出来了DLL文件,添加进项目了,如果有错,还的再打开类库这个项目来检查,重新编译,重新添加)
二楼的办法和一楼差不多(不方便啊)
建类库,在类库里可以建控件类,再生成就可以了
新建一个项目1,项目菜单-->(项目文件名)-->输出类型,选类库.
新建一个项目2,在 新项目2的解决方案资源管理里添加应用,点浏览,找到生成的类库,确定
在项目2的源代码里添加using 项目一名称的命名空间.
这样毫无问题.
下面代码总是用在vb.net中,用c#写花了我不少时间。
public static MainForm FormDefInstance;
public static MainForm DefInstance
{
get
{
if (FormDefInstance == null || FormDefInstance.IsDisposed)
{
FormDefInstance = new MainForm();
}
return FormDefInstance;
}
set
{
FormDefInstance = value;
}
}
static void Main()
{
Application.EnableVisualStyles();
Application.DoEvents(); //处理完当前的消息队列。
Application.Run( MainForm.DefInstance);
}
http://www.vbcity.com/forums/topic.asp?tid=32927&highlight=regex%7Cemail
Private Function IsValidEmail(ByVal UncheckedEmailAddr As String) As Boolean
Const RegExpr As String = "^([a-zA-Z0-9_/-/.]+)@((/[[0-9]{1,3}/.[0-9]{1,3}/.[0-9]{1,3}/.)/(([a-zA-Z0-9/-]+/.)+))([a-zA-Z]{2,4}/[0-9]{1,3})(/]?)$"
Dim InputEmail As String = UncheckedEmailAddr.Trim
Try
Dim r As New Regex(RegExpr)
If r.IsMatch(InputEmail) Then
Return True
Else
Return False
End If
Catch ex As Exception
DebugWriter("Source of Error : " & ex.Source & vbCrLf & _
"Error Message : " & ex.Message)
End Try
End Function
/*
* 模块名:操作INI文件。
* 模块来源:英特网,
* 备注:等有空把这个模块的功能完善一下。
* 时间:2004年9月9日
*/
using System;
using System.Text;
using System.IO;
using System.Runtime.InteropServices;
namespace cxh_mk
{
/// <summary>
/// Ini文件操作的类
/// </summary>
public class IniFile
{
private string filename; //INI完整文件名
//声明读写INI文件的API函数
[DllImport("kernel32")]
private static extern long WritePrivateProfileString(string section,string key,string val,string filePath);
[DllImport("kernel32")]
private static extern int GetPrivateProfileString(string section,string key,string def,StringBuilder retVal,int size,string filePath);
/// <summary>
/// 类的构造函数
/// </summary>
/// <param name="INIPath">INI文件名</param>
public IniFile(string iniFilename)
{
if (iniFilename.IndexOf(@"://") == -1)
{
filename = System.Windows.Forms.Application.StartupPath + "//" + iniFilename;
}
else
{
filename = iniFilename;
}
}
/// <summary>
/// 写INI文件
/// </summary>
/// <param name="Section">节</param>
/// <param name="Key">关键字</param>
/// <param name="Value">值</param>
public void WriteValue(string Section,string Key,string Value)
{
WritePrivateProfileString(Section,Key,Value,this.filename);
}
/// <summary>
/// 写INI文件
/// </summary>
/// <param name="Section">节</param>
/// <param name="Key">关键字</param>
/// <param name="Value">值</param>
/// <param name="filename">ini文件所以路径</param>
public static void WriteValue(string Section,string Key,string Value,string filename)
{
WritePrivateProfileString(Section,Key,Value,filename);
}
/// <summary>
/// 读取INI文件指定
/// </summary>
/// <param name="Section">节</param>
/// <param name="Key">关键字</param>
/// <returns>指定节-关键字的值</returns>
public string ReadValue(string Section,string Key)
{
StringBuilder temp = new StringBuilder(255);
int i = GetPrivateProfileString(Section,Key,"",temp,255,this.filename);
return temp.ToString();
}
/// <summary>
/// 读取INI文件指定
/// </summary>
/// <param name="Section">节</param>
/// <param name="Key">关键字</param>
/// <returns>指定节-关键字的值</returns>
/// <param name="filename">ini文件所在路径</param>
public string ReadValue(string Section,string Key,string filename)
{
StringBuilder temp = new StringBuilder(255);
int i = GetPrivateProfileString(Section,Key,"",temp,255,filename);
return temp.ToString();
}
}
}
// IniFile ini = new IniFile("user.ini");
// txtTo.Text=ini.ReadValue("mailinfo","to");
// txtToname.Text=ini.ReadValue("mailinfo","toname");
// txtFrom.Text=ini.ReadValue("mailinfo","from");
// txtFromname.Text=ini.ReadValue("mailinfo","fromname");
// txtTitle.Text=ini.ReadValue("mailinfo","title");
// if (ini.ReadValue("mailinfo","dns") == "")
// {
// txtDns.Text=DnsQuery.dnsServer;
//
// }
// else
// {
//
// txtDns.Text=ini.ReadValue("mailinfo","dns");
// }
// txtBody.Text=ini.ReadValue("mailinfo","body");
//IniFile ini = new IniFile("user.ini");
//ini.WriteValue("mailinfo","to",mail.to);
//ini.WriteValue("mailinfo","toname",mail.toname);
//ini.WriteValue("mailinfo","from",mail.from);
//ini.WriteValue("mailinfo","fromname",mail.fromname);
//ini.WriteValue("mailinfo","title",mail.title);
//ini.WriteValue("mailinfo","dns",DnsQuery.dnsServer);
//ini.WriteValue("mailinfo","body",mail.body);
//mail.attachmentList = new string[lstFile.Items.Count];
//if (lstFile.Items.Count!=0)
//{
// lstFile.Items.CopyTo(mail.attachmentList,0);
//}
// Thread th = new Thread(new ThreadStart(this.Run));
// th.Start();