|
#include <stdio.h> int main() { char c1,c2,c3,c4,c5; c1='C';c2='h';c3='i';c4='n';c5='a'; c1+=4; c2+=4; c3+=4; c4+=4; c5+=4; printf("%c%c%c%c%c",c1,c2,c3,c4,c5); return 0; }
原来是China,译完后是Glmre
这是一个用C写的解二阶行列式的程序,减少了自己手动写的麻烦。决定将《线性代数》中的一些算法都以程序的方式写出来,既提高程序水平,也加强对线性代数的了解,同时也加深对这门课的兴趣。
#include <stdio.h> int main() { int a1,a2,a3,a4,a5,a6; int m,n,p; printf("求解方程组-二阶行列式/n"); printf("请输入第一个方程式的前三个元素,中间以空格分开:/n"); scanf("%d%d%d",&a1,&a2,&a3); printf("请输入第二个方程式的前三个元素,中间以空格分开:/n"); scanf("%d%d%d",&a4,&a5,&a6); m=a1*a5-a2*a4; n=a3*a5-a2*a6; p=a1*a6-a3*a4; printf("分析数据:/n"); printf("D=%d/n",m); printf("D1=%d/n",n); printf("D2=%d/n",p); printf("最终结果:/n"); printf("x1=%d/n",n/m); printf("x2=%d/n",p/m); return 0; }
摘要: 1990年,山东省某市中学生齐某参加中专考试,被一学校录取为90级财会专业委培生。但是,齐所在的中学既未将考试成绩告知齐,也未将录取通知书送给齐本人,而是送给了与齐同一届的另一名学生陈某。陈即以齐的名义读完中专,被分配到金融单位工作,其人事档案中也一直使用齐某的姓名。 此事被掩盖多年后终于东窗事发。1999年1月29日,齐某以陈某和她的父亲以及原所在学校等数家单位侵害其姓名权和受教育权为由诉至法院,请求责令被告停止侵害、赔礼道歉并赔偿经济损失16万元和精神损失40万元。 这起看似简单的民事案件,却给司法机关提出了一个“棘手”的问题— ——侵犯姓名权问题在民法通则中有详细的规定,无须赘述;侵害受教育权却在民法中没有规定——换句话说,受教育权属于公民的宪法权利,而不是民事权利。但是,我国各级审判机关在审理具体案件时,惯例是不能直接引用宪法。因此,在一般情况下,这一诉求可能会以“没有法律依据为由”而不予受理。 据此,最高人民法院根据山东省高级人民法院的请示,于2001年8月13日作出[2001]法释25号《关于以侵犯姓名权的手段侵害宪法保护的公民受教育的基本权利是否应当承担民事责任的批复》,指出“陈××侵犯姓名权的手段,侵犯了齐××依据宪法所享有的公民受教育的基本权利,并造成了具体损害,应承担相应的民事责任”。 最高司法机关对公民因宪法规定享有的基本权利受到侵害而产生纠纷的法律适用问题进行司法解释,这在我国尚属首例。 关键字:宪法司法化,宪法 正文: 本案属公民提起的受教育权诉讼,在现有的《民法通则》中虽然对公民的姓名权有规定,但对公民的受教育权却仅在我国的宪法的第40条规定公民有受教育的权利和义务,而在《民法通则》及其它部门法当中并没有具体的体现。这就涉及到宪法是否可以像其它法律法规一样,直接进入司法程序加以引用并做出相应的裁判,也即所谓的“宪法司法化”。此案倍受各界关注,最高人民法院于就本案所作的《批复》中称:“根据本案事实,陈晓琪等以侵犯姓名权的手段,侵犯了齐玉玲依据宪法规定所享有的受教育的基本权利,并造成了具体的损害后果,应承担相应的民事责任”。随后,有关法院根据上述批复做出了判决,此案即被称之为“宪法第一案”。 在该案件之前,因为宪法所具有的抽象性,一直不作为各项诉讼所直接参考的依据。但是,在一些基本法律没有体现到的地方,宪法却给出我们基本的精神。也就是从这个案子开始,在一些诉讼上面宪法也正式被司法化,成为司法中依据的条例。 一、宪法司法化内涵及其发展首先,宪法司化这一话语在纯理论意义上具有两个维度:一是当没有具体法律将公民的宪法基本权利落实时,司法机关能否直接适用或引用宪法条文作为判决依据?在这种意义上,宪法司化法意味着宪法司法化适用性。这个命题建立在公民基本权利之充分保障的宪政理论之上,即认为宪法是公民权利的保障书,如果宪法权利没有得到具体法律落实,司法机关又不适用,宪法条文作为判决依据,无疑权利保障成为一纸空文。因此,宪法的司法适用有最后屏障之功效。宪法司法化的第二层面是:在司法机关对个案审理过程中,能否对有违宪疑义的法律规范的合宪性问题进行审查并作出判断。这涉及到司法机关是否有违宪审查权问题。这无疑已经不是一个技术性命题,它涉及到一个国家的宪政理论和政治制度的基本构架,甚至包括历史传统和文化观念等层面。虽然司法审查这种制度在现代受到广泛的推崇,但它本来并非一条不证身明的公理。实际上,司法审查制度始终受到本身两个方面的严峻挑战:因此宪法司法化内涵主要是指宪法可以像其它法律一样严格地进入司法程序,作为裁判案件的法律依据,并依照宪法进行司法审查的一种制度。 二、我国宪法实施中存在的问题宪法是国家的根本大法,具有最高的法律效力,从1949年9月制定起临时宪法作用的《共同纲领》至今,我国宪法制度的发展已走过了风风雨雨50年,有些人认为,我国宪法被视为“闲法”,人民法院判案不得引用宪法条文;人们意识中也有“宁可违宪,不可违法”的思想,在民意调查中,公民认为与切身有最大利益关系的法律是民法、刑法等等,根本找不到宪法的影子。还有些人认为,宪法是“镜中花,雾中月”好看不中用,但随着人们权利意识和法治观念的日益增强,将宪法请下“神坛”,使其真正发挥根本大法的实际效力,在概叹人们宪法意识谈谟的同时,应该对我国的宪法制度及司法实践进行反思。第一,宪法的频繁变更削弱了宪法的稳定性和权威性。从1949年9月至今,我国先后制定了一部临时宪法(即《共同纲领》)、四部宪法,并颁布了三次宪法修正案,无论是全身手术还是局部手术,所修改及确定的内容皆是当时历史阶段党要完成的任务及实现的目标的政策。修宪就是将党的政策法律化的过程。宪法的频繁变更和修改,严重削弱了宪法的稳定性和权威性,这似乎不能完全归咎于立宪者的短视,症结所在是宪法在我国政治生活中所扮演的角色在一定程度上是给执政党的政策披上一层法律的外衣,使其上升为国家的意志,人民的意志,因为“党是代表广大人民群众的根本利益”。“法律必须是稳定的,但不可一成不变”。在我国每一次党的代表大会召开伴随着一个时期内方针政策的修改又带来了一次宪法的修改,宪法经常性地被其政策而温柔地改变,那么,就意味着整个国家的权力秩序已纳入到宪法的管辖之内,“政治权力的宪法化”就很难充分地得以实现,宪政秩序也就失去了必要的基础。这也就难怪人们对宪法的认识还不如看一下党的红头文件来的容易,也难免让人心悦诚服地维护宪法的权威性呢?第二,“法治”与“人大至上性”的矛盾,使宪法司法化在现行体制上不能完全行使。——所谓“人大至上”就是说,人民代表大会具有至高无上的权力或者说具有“决定一切职权的职权”;法治的最低标准就是保持国家法律在宪法框架内的统一,就是保证法律的合宪性,就是所谓的“治法”,如果法治排除了“治法”的硬核,那么法治的最终含义的就是在于“治人”了;一旦统治者打着法治的旗号而行“治人”之时,人也就变成了奴隶,“法治”也就走向了它的反面。在我国,一方面全国人大有立法权,可以制定他“认为合适”的法律;另一方面全国人大有权对宪法进行修改,全国人大常委会有权解释宪法。假如有人指控全国人大立法有违宪之嫌,全国人大常委会就可能通过解释宪法而不是修改法律来“自圆其说”,以保证“宪法”的合法性,而不是法律的合宪性;即使不能“自圆其说”,“人大”还可使用"修宪"的杀手锏来保证其所制定的法律“合宪性”。在这种体制下,除非“人大”自觉的进行其立法的合宪性监督,否则,法律违宪问题是断然不可能存在的。第三,宪法的不直接适用性削弱了宪法的权威性。造成这一现象的原因是多方面的。首先,宪法本身具有高度的抽象性。宪法规定的是国家的根本制度和根本任务,是对国家政治结构、经济制度,社会制度以及公民的基本权利与义务的规定。宪法规范具有原则性、概括性,其假定、处理、制裁三个方面的区分并不完全,造成其惩罚性,制裁性不强,因此,宪法规范本身缺乏可诉性和可操作性。在司法实践中,法院一般将依据宪法制定的普遍法律作为法律适用的依据,而不将宪法直接引入诉讼程序。其次,人们对宪法认识的观念问题。长期以来,人们对宪法性质主要着眼于政治性。往往和国家的大政方针联系在一起,因而很久以来,我们一直没有树立宪法为法的观念,让根本大法降格去解决刑事、民事等小问题在绝大多数人看来实在是荒唐之举。最后,最高人民法院以往的司法解释捆住了自己的手脚。其一是1955年ii最高人民法院在给新疆自治区高级人民法院的批复中认为,在刑事诉讼中不宜援引宪法作定罪科刑的依据。其二是1986年最高人民法院在给江苏省高院的批复中对是否引用宪法条文进行裁判采取了回避的态度。第四,违宪事件经常发生削弱了宪法的根本大法地位,影响了社会稳定。《宪法》第五条规定:“一切国家机关……都必须遵守宪法和法律。一切违反宪法和法律的行为,必须予以追究。”“任何组织或者个人都不得有超越宪法和法律的特权。”这是对国家机关依法行使职权原则的具体规定。国家机关违宪情况大致包括以下几种:首先,不履行宪法职责,职务行为违反法律规定。包括违反实体法和程序法的规定,如滥用权力等。其次,职务行为没有法律根据,认定这类行为违法,是法治原则的必然要求,这类行为若给相对人一方科以义务使其因此而遭受了损失,国家要负赔偿责任。最后,国家制定的法规,规章等抽象行政行为违宪,引起社会广泛关注的湖北青年孙志刚在广州收容致死一案让人反思,1991年国务院发出48号文将收容对象扩大到“无合法证件,无固定住所,无稳定经济来源”的三无人员,而在执行中,“三无”往往变成无身份证、暂住证、务工证,“三证”缺一不可。也就是说最初制度设计上,收容制度是一种救济制度,但在特定历史条件下,它演变成了一项限制公民基本宪法权利的制度。《中华人民共和国立法法》第8条规定:对公民政治权利剥夺、限制人身自己的强制措施和处罚只能制定法律。《中华人民共和国宪法》第37条规定中华人民共和国公民的人身自由不受侵犯……禁止非法拘禁和以其他方法非法剥夺或者限制公民的人身自由。可见,《收容遣送办法》与《立法法》相矛盾,同时也违背了宪法。 三、宪法司法化不同类型之比较世界上现存的宪法司法化大致分为两种类型——美国模式与欧陆模式;表现在审查主体上是分权的与集权的,表现在审查时机上是事后审查的与预防审查的,表现在审查方法上是附带审查的与主要审查的,表现在审查结果上是个案效力与普及效力的等等①。以下分别对两大类型的主要构成进行简单比较、说明和分析。 美国模式承认各级法院都有权进行合宪性审查。但这种审查只针对已经生效的法律,只能在处理各类普通诉讼案件的程序当中采取所谓的“附带审查”(即宪法问题只能作为具体争议内容的一部分而不能作为主要争议提出来)的方式。法院仅仅解决具体的问题而不作抽象性判断,因此审查结果的效力只限于本案当事人。这样做的目的是要尽量避免由法官来制定法律的事态。当然,遵循先例原则使判决的效力有机会涉及其他同类案件,实际上合宪性审查的结果还是有普遍性的,法律的安定也不会因而遭到破坏。然而,这种普及效力在形式上还是仍然局限于具体案件的当事人之间的具体争议。 与美国模式不同,在欧洲大陆法系各国中,合宪性审查职能被限定在单一司法性机关如宪法法院,宪法评议委员会等集中履行,普遍的各级法院以及最高法院则无权过问。宪法诉讼在多数场合是由国家机关(包括政府部门,国会议员以及受理具体诉讼案件的普通法院)。按照特别程序来提起,因此合宪性审查与具体诉讼案件的审理是分别进行的。以合宪性审查的政治性为前提,宪法法院的构成以及人事选任方法都必须反映政治势力的分布状况,审查内容也往往包括政治问题和统治行为。另外,尚未生效的法律,条约也被列入审查范围之内。二战后,英美和欧陆这两种不同的模式在司法审查制度出现了趋同化的发展①。其中最典型实例是属于大陆法系的日本。根据1947年宪法第81条的规定,日本导入了美式附带合宪性审查制度。尽管如此,由于社会和制度的背景不同,日本的实际做法最终表现出明显的特色,例如分权化的合宪性审查到1975年就名存实亡,最高法院实际上一直发挥宪法法院的作用,但却没有采取抽象性审查的方式,而是通过具体诉讼案件的判决进行部门问题的审查,另外,审查的重点从立法转移到防止行政权力对人仅的侵犯方面,在整体上的倾向于司法消级主义等等,似乎介于美国模式和战后德国模式之间。 四、我国宪法司法化的重要意义及其适用范围 首先,宪法司法化有助于保障人权。现实中,宪法规定的公民所享有的基本权利往往因为缺乏普通法律、法规的具体化,量化而长期处于休眠状态,无法得到实现。由于宪法具有高度的原则性和概括性,一般能够适应社会关系不断发展变化的要求,因此宪法司法化能够弥补普通法律、法规的缺陷和漏洞,使宪法规范从静态走向动态,将宪法规定权利落到实处。其次,宪法司法化有助于实现法治。宪法规定了国家政治生活和社会生活等具有全局意义的问题,在整个法律体系中处于母法地位,具有最高法律效力和权威。因此,实现法治、依法治国首先是依宪治国,树立法律权威首先是树立宪法的权威。而依宪治国树立宪法权威不能停留在纸面上,对于违宪事件和违宪争议,宪法不应沉默,而应将其纳入司法轨道。最后,宪法司法化有助于推动宪政。长期以来,现实生活中违宪现象可司空见惯,而由于我国长期以来形成的宪法不能作裁判依据的司法惯例与思维定势,有关国家机关对此只能束手无策,如果实行宪法司法化,那么就能激活纸面上的宪法,在司法过程中凸显宪法的最高法律效力和权威,使宪法确定的公民的基本权利再无具体法律法规规定或规定不明确的情况下变成现实,对国家机关、组织或者个人的违宪行为进行有效的追究与纠正。只有这样,徒具口号意义的宪政才能转变为活生生的现实。 虽然,宪法司法化在我国的实现有着重大意义,但是适用范围是有限的。如果不对宪法司法化的范围,进行合理架构,那么会导致宪法的滥诉现象,果真如此的话宪法的根本大法就会降格。我国必须坚持普通的民事审判的私法领域不能直接适用宪法权利条款。宪法权利仅直接适用在公法领域中的,适用在反映公民与国家权力关系的领域中。“宪法中的权利条款仅仅保护其不受“国家行为”的侵犯,而将私人对他人的权利的侵犯留给侵权法”①。刑事审判程序是确定公民是否犯罪以及对犯罪行为人处以何种刑罚的程序,即定罪量刑的程序,刑事审判所处理的案件在性质上属于公法领域案件,但是,由于刑事审判属于定罪量刑性质,依据罪行法定原则,应当直接适用刑法规范,而不宜直接以宪法规范作为其法律依据。在非刑事审判的其他公法领域,也并非都需要直接适用,如果立法符合宪法立法体现了权利的价值,通过立法构建的法律秩序促进了基本权利的实现,或者说一般法律权利已是基本权利的具体化则可以直接适用一般法律权利。在执法尤其行政执法领域,基本权利受到公权力的侵害,可以直接适用一般权利规范进行救济,在穷尽这种救济之后,再适用宪法权利规范进行救济。 五、对我国宪法司法化的设想 随着我国法治化的进程,扩大公民宪法权利的适用性也是当务之急。鉴于中国法治环境不断改善。在现阶段实现宪法司法化的条件已经日趋成熟。 首先,必须改变对宪法的认识观念,宪法不是“神法”,也不是“闲法”。宪法是我国的根本大法,宪法的主要任务在于规定国家机构的设置,权限运作以及公民的基本权利。为了维护宪法的稳定性和权威性,对于宪法中要规定的基本国策要有条件限制,只有那些带有根本性的国家理念和基本国策,才有必要在宪法中作出规定。 其次,司法审查制度可以分几步走。第一步,在现行体制下,全国人大常务委员会应当切实履行宪法赋予的职责,维护宪法的权威及尊严,我国是社会主义国家,全国人民代表大会是我国最高权力机关,由于全国人民代表大会由省、自治区、直辖市和军队选出的代表组成,每年举行一次会议,所以本人认为应当强化它的常设机关全国人民代表大会常务员会职权。委员应当具备较高的政治、经济、法律素质,委员应当实行全职化、年轻化。修改《全国人民代表大会常务委员会议事规则》这样才能履行宪法67条规定:解释宪法,监督宪法的实施;撤销国务院制定的同宪法法律相抵触的行政法规、决定和命令。宪法37条规定:中华人民共和国公民的人身自由不受侵犯。孙志刚案的发生使人们不得不思考宪法的权威。公民要求司法救济权利的呼声越来越高。而现在公民只能对具体行政行为违法提起行政诉讼,对于抽象行政行为违法问题还是解决不了。收容制度审查就是一个抽象行政行为。人们迫切希望宪法司法化时代到来,如果没有一个最终的司法救济渠道,宪法赋予公民合法的人身权利及其它权利就得不到根本的保障。 第二步借鉴美国模式和欧陆模式,根据我国现今社会制度、历史文化传统及法官素质不高等客观原因,在重新修改《中华人民共和国宪法》、《中华人民共和国立法法》、《中华人民共和国行政诉讼法》、《最高人民法院组织法》的基础上,实行分级违宪审查的制度模式。即在人大常委会内设立宪政委员会,在最高人民法院、省级人民法院内设宪法审判庭,宪政委员会由13名知名法律家和政治家组成,最高法院审判庭由9名宪法大法官组成,这些组成人员由国家主席在与全国人大常委会委员长,最高人民法院院长协商提名,由人国人大选举产生,对宪法和人大负责。由宪政委会员重点审查法律、法规的合宪性以及国家领导人的违宪诉讼案,宪政委员会履行宪法第六十七条第一款、第七款、第八款职权,法律在提交全国人民代表大会及常务委员会表决之前,宪政委员会可以提前预防审查。《收容遣送办法》行政法规违背宪法时,宪政委员会可直接撤销上述办法,也可以提请启动特别调查程序、组织特定的问题调查委员会议案,调查委员会由全国人大代表担任,调查委员会可聘请专家参加调查工作,调查委员会的组成要遵循回避原则。对有关国家机关调查处理孙志刚案的情况,可以在司法机关依照法定程序办结以后,调取案卷审查,发现疑点时召开听证会,听取有关国家机关汇报,并进行必要的询问和质询,在调查过程中,调查委员会视具体情况决定是否公布调查情况和材料,在调查过程以后,调查委员会应向全国人大常委会报告调查结果,并向全国人民公开。由宪法审判庭重点审查规章及其他规范性文件的合宪性,以及侵犯公民宪法权利的案件,但要明确两种审查机关之间以及与权力机关之间的关系。还应合理划分最高法院与宪政委员会的管辖权限,并建立相应宪法诉讼制度。 第三步,在具备条件和重新修宪的基础上,设立宪法委员会或者宪法法院,统一行使原来由全国人大享有改变或撤销常务委员会的决定和权限,由全国人大常委会享有的撤销行政法规和地方法规以及进行宪法解释和立法解释权限,宪法委员会由13名或15名资深望重的法学家和政治家作为委员组成,宪法委员会对我国的宪法负责。与此同时,通过司法改革进一步落实审判独立原则,以提高职业法官群体的社会威性减少法官的数量,提高法官素质。宪法委员会大法官们按照宪法规定的权限和程序对一切已经生效的基本法律、决议、行政法规、地方法规自治条例,单行条例,司法解释以及各种规章进行抽象性审查并能够直接否定违宪规范效力,也可以批准并宣告已经生效法院判决的撤销。除有关国家机关外,任何公民也都可以由普通法院向宪法委员会或宪法法院提起宪法诉讼,在特殊情况下宪法委员会或宪法法院还可以直接受理已经穷尽一切普通司法救济手段的个人的控诉或申诉。 最后,随着国际交往的日益频繁,我国已加入了WTO,我国每年在国际上缔结的各种类型条约有二三百个,我们不能排除会出现国务院缔结的条约与全国人大常委会制定的法律相冲突,从国家主权原则出发,从宪法是国家根本法的要求出发,宪法应当对条约的适用问题作出规定,对抽象行政行为也要具有司法审查权。 主要参考书目: (1) 汤维建:《美国民事司法制度与民事诉讼程序》。 (2) 张志铭:《也谈宪法的司法化》。 (3) 周菁 王超:《宪法司法化散论-从我国宪法司法第一案谈起》。 (4) 刘武俊:《以诉讼激活弥合宪法鸿沟》。
Visual Assist X 10
增强Microsoft开发环境下的编辑能力。
Visual Assist X具有强大的编辑特色,提高了生产效率,可以完全集成到您的Microsoft开发环境中,升级了您的IDE,在不改变编程习惯的同时就可以感受到Visual Assist X为您带来的好处。
Visual Assist X是一款非常好的Visual Studio .NET 2003、2002插件,支持C/C++,C#,Visual Basic等语言,能自动识别各种关键字、系统函数、成员变量、自动给出输入提示、自动更正大小写错误、自动标示错误等,有助于提高开发过程的自动化和开发效率。
结构体系
从Microsoft Visual C/C++ 6.0开始,所有的Microsoft IDE都为您的编辑环境提供了轻巧的剖析器,为您的代码编写提供方便,他们不生成目标码,这种默认帮助形式称为Intellisense。其特性包括成员列表框、完成列表框、停驻工具提示、自动参数信息。您可以在IDE对话框选项内设定每个组件的工作状态。
Visual Assist X去除了默认Intellisense的几个不足之处,采用一系列新的特性对其进行了补充,提高了IDE的可用性。其中,帮助的数量决定于IDE、所用编程语言和您设定的选项。
如同默认Intellisense,Visual Assist X也包括剖析器,运行于您的编辑过程中。该培训器同默认剖析器的不同之处在于,他们不装载预先设置的符号数据库,因此扩大了Intellisense的范围。Visual Assist X只收集您工程中的头文件和对象信息,这些头文件和对象可以包含在系统、第三方库、MFC或ATL/WTL/STL中。这意味着Intellisence的活动更加频繁,结果也更加准确。
不止是C/C++,对于所有编程语言,Visual Assist X都含有可执行的轻载默认剖析器。对于C/C++,您可以选择Visual Assist X专有Intellisense,也可以首选默认剖析器,只有当其不可用时,再采用Visual Assist X,这些工作方式都可以通过专门设置来实现。
Visual Assist X作为一种低优先度的后台线程插件,不影响您的IDE环境性能。该线程在idle期间活动,当您开始输入代码时,又回复为静默状态。并且它的剖析器不生成目标码,因此,同传统剖析器相比,占用更少的资源。
Visual Assist X提高了工作的自动化,简化了浏览,并且显示您开发过程的关键信息。其功能完全集成到IDE中,实现了二者的充分融合。为Microsoft Visual Studio .NET 2003和2002的所有编程语言提供编程助手,包括C/C++, C#, ASP, Visual Basic, Java 和 HTML,另外,还为Microsoft Visual C++ 6.0和 5.0的C/C++用户提供编程帮助。
产品特点
改进了Intellisense:成员和完成列表框的出现更加频繁、迅速,并且结果更加准确。参数信息更加完善,并带有注释。含有所有符号的停驻工具提示。
代码输入更迅速:输入时观察suggestion列表框,其中将根据您的输入提供相应的备选字符。为了更加方便的选择字符,还可以提前定义Atuotext和代码模板。
错误自动校正:监控您的IDE,对那些简单但耗时的错误进行即时校正。
信息获取更加快速:更加迅速了解代码信息,在新的VA View中观察当前的停驻类浏览器,可以获得当前符号的更多信息。除此,资源窗口中还添加了小的内容和定义项,可以获取信息快照。
增加了色彩和格式:采用了更多的色彩和格式选项,代码解译更加迅速。增强了IDE的基本语法色彩,在您输入代码的同时,突出匹配和不匹配条目。另外,还添加了column indicator和print in color,将RTF置于剪切版内。
简化了查找和浏览:查找和浏览更加轻松。通过内容查找可以快速跳到相同名称的符号处,在您工作台的任何地方都可以找到符号定义,还可以转入您代码中的符号执行处。选择您文件的列表方式,锁定头文件和相应的cpp文件。从您的工作台文件列表中打开文件。含有最近行为列表,可以在代码的活动部分之间相互转换。Move scope可以到达下一个方法,还包含往返浏览。
拼写检查:在您输入代码的同时进行检查,并且可以看到同Microsoft Word相似的红色下划线。含有Spell check comments and strings,另外,Spell check code可以检查错误的输入符号。
拓展了基本编辑:对编辑器进行了增强,编辑代码更加迅速。含有Surround selections,multiple clipboards. Sort lines。
适合您个人风格的配置特色:细化选项对话框,定义Visual Assist X特性以适应您的编程习惯。内容菜单中含多个命令,设置快捷方式可以加快访问您所偏好的命令。可以禁止或允许Visual Assist X,或者强制其重新剖析从而更加智能化。
版本新特色
支持多种编程语言:Microsoft Visual Studio .NET的所有编程语言,包括C#, ASP, Java, Javascript, Basic 和 VBScript等。
单一安装,适合所有IDE:去除了针对不同IDE的单个产品,下载后,Visual Assist X可以安装到您的全部Microsoft IDE中,如果又安装了新的IDE,只需重新安装一次Visual Assist X即可。 紧密集成:Visual Assist X更加紧密的集成到了您的IDE中,利用Microsoft的Add-In和VSIP接口,无需离开IDE或改变编码习惯就可以运行Visual Assist X。为了更好的集成,还包含新的菜单条目、更多的键捆绑,并去除了冗余选项。
更多的特色:包含建议列表框(Suggestion listboxes)、停驻类浏览器(hovering class browser)、语法上色(syntax coloring)、访问最近应用的文件和符号等。 更好的文档:产品含有优秀的文档说明,选项对话框中包含信息工具提示,另外,在我们的网站上也含有关于Visual Assist X的充分信息。
系统需求
Visual Assist X可以工作于任何Microsoft IDE和操作系统。
开发环境:Visual Assist X兼容于以下开发环境,当您改变或新增了IDE时,需要重新安装Visual Assist X: Microsoft Visual Studio .NET 2003 Microsoft Visual Studio .NET 2002 Microsoft Visual C++ .NET 2003 Microsoft Visual C++ .NET
Microsoft Visual C++ 6.0 Microsoft Visual Studio 6.0 Microsoft Visual C++ 5.0 Microsoft Visual Studio 97 Microsoft eMbedded Visual Tools 4.0 Microsoft eMbedded Visual Tools 3.0
操作系统:
Windows XP Server 2003 Windows XP Professional Windows XP Home Edition Windows 2000 Professional Windows 2000 Server Windows NT 4.0
硬盘空间:依赖于您的工程大小。建议最小为50MB,大的工程需要100MB以上。
下载地址:http://www.ttdown.com/SoftView/SoftView_21625.html (破解版)
虚函数联系到多态,多态联系到继承。所以本文中都是在继承层次上做文章。没了继承,什么都没得谈。
下面是小弟对C++的虚函数这玩意儿的理解。
一, 什么是虚函数(如果不知道虚函数为何物,但有急切的想知道,那你就应该从这里开始)
简单地说,那些被virtual关键字修饰的成员函数,就是虚函数。虚函数的作用,用专业术语来解释就是实现多态性(Polymorphism),多态性是将接口与实现进行分离;用形象的语言来解释就是实现以共同的方法,但因个体差异而采用不同的策略。下面来看一段简单的代码
class A{
public:
void print(){ cout<<”This is A”<<endl;}
};
class B:public A{
public:
void print(){ cout<<”This is B”<<endl;}
};
int main(){ //为了在以后便于区分,我这段main()代码叫做main1
A a;
B b;
a.print();
b.print();
}
通过class A和class B的print()这个接口,可以看出这两个class因个体的差异而采用了不同的策略,输出的结果也是我们预料中的,分别是This is A和This is B。但这是否真正做到了多态性呢?No,多态还有个关键之处就是一切用指向基类的指针或引用来操作对象。那现在就把main()处的代码改一改。
int main(){ //main2
A a;
B b;
A* p1=&a;
A* p2=&b;
p1->print();
p2->print();
}
运行一下看看结果,哟呵,蓦然回首,结果却是两个This is A。问题来了,p2明明指向的是class B的对象但却是调用的class A的print()函数,这不是我们所期望的结果,那么解决这个问题就需要用到虚函数
class A{
public:
virtual void print(){ cout<<”This is A”<<endl;} //现在成了虚函数了
};
class B:public A{
public:
void print(){ cout<<”This is B”<<endl;} //这里需要在前面加上关键字virtual吗?
};
毫无疑问,class A的成员函数print()已经成了虚函数,那么class B的print()成了虚函数了吗?回答是Yes,我们只需在把基类的成员函数设为virtual,其派生类的相应的函数也会自动变为虚函数。所以,class B的print()也成了虚函数。那么对于在派生类的相应函数前是否需要用virtual关键字修饰,那就是你自己的问题了。
现在重新运行main2的代码,这样输出的结果就是This is A和This is B了。
现在来消化一下,我作个简单的总结,指向基类的指针在操作它的多态类对象时,会根据不同的类对象,调用其相应的函数,这个函数就是虚函数。
二, 虚函数是如何做到的(如果你没有看过《Inside The C++ Object Model》这本书,但又急切想知道,那你就应该从这里开始)
虚函数是如何做到因对象的不同而调用其相应的函数的呢?现在我们就来剖析虚函数。我们先定义两个类
class A{ //虚函数示例代码
public:
virtual void fun(){cout<<1<<endl;}
virtual void fun2(){cout<<2<<endl;}
};
class B:public A{
public:
void fun(){cout<<3<<endl;}
void fun2(){cout<<4<<endl;}
};
由于这两个类中有虚函数存在,所以编译器就会为他们两个分别插入一段你不知道的数据,并为他们分别创建一个表。那段数据叫做vptr指针,指向那个表。那个表叫做vtbl,每个类都有自己的vtbl,vtbl的作用就是保存自己类中虚函数的地址,我们可以把vtbl形象地看成一个数组,这个数组的每个元素存放的就是虚函数的地址,请看图
通过上图,可以看到这两个vtbl分别为class A和class B服务。现在有了这个模型之后,我们来分析下面的代码
A *p=new A;
p->fun();
毫无疑问,调用了A::fun(),但是A::fun()是如何被调用的呢?它像普通函数那样直接跳转到函数的代码处吗?No,其实是这样的,首先是取出vptr的值,这个值就是vtbl的地址,再根据这个值来到vtbl这里,由于调用的函数A::fun()是第一个虚函数,所以取出vtbl第一个slot里的值,这个值就是A::fun()的地址了,最后调用这个函数。现在我们可以看出来了,只要vptr不同,指向的vtbl就不同,而不同的vtbl里装着对应类的虚函数地址,所以这样虚函数就可以完成它的任务。
而对于class A和class B来说,他们的vptr指针存放在何处呢?其实这个指针就放在他们各自的实例对象里。由于class A和class B都没有数据成员,所以他们的实例对象里就只有一个vptr指针。通过上面的分析,现在我们来实作一段代码,来描述这个带有虚函数的类的简单模型。
#include<iostream>
using namespace std;
//将上面“虚函数示例代码”添加在这里
int main(){
void (*fun)(A*);
A *p=new B;
long lVptrAddr;
memcpy(&lVptrAddr,p,4);
memcpy(&fun,reinterpret_cast<long*>(lVptrAddr),4);
fun(p);
delete p;
system("pause");
}
用VC或Dev-C++编译运行一下,看看结果是不是输出3,如果不是,那么太阳明天肯定是从西边出来。现在一步一步开始分析
void (*fun)(A*); 这段定义了一个函数指针名字叫做fun,而且有一个A*类型的参数,这个函数指针待会儿用来保存从vtbl里取出的函数地址
A* p=new B; 这个我不太了解,算了,不解释这个了
long lVptrAddr; 这个long类型的变量待会儿用来保存vptr的值
memcpy(&lVptrAddr,p,4); 前面说了,他们的实例对象里只有vptr指针,所以我们就放心大胆地把p所指的4bytes内存里的东西复制到lVptrAddr中,所以复制出来的4bytes内容就是vptr的值,即vtbl的地址
现在有了vtbl的地址了,那么我们现在就取出vtbl第一个slot里的内容
memcpy(&fun,reinterpret_cast<long*>(lVptrAddr),4); 取出vtbl第一个slot里的内容,并存放在函数指针fun里。需要注意的是lVptrAddr里面是vtbl的地址,但lVptrAddr不是指针,所以我们要把它先转变成指针类型
fun(p); 这里就调用了刚才取出的函数地址里的函数,也就是调用了B::fun()这个函数,也许你发现了为什么会有参数p,其实类成员函数调用时,会有个this指针,这个p就是那个this指针,只是在一般的调用中编译器自动帮你处理了而已,而在这里则需要自己处理。
delete p;和system("pause"); 这个我不太了解,算了,不解释这个了
如果调用B::fun2()怎么办?那就取出vtbl的第二个slot里的值就行了
memcpy(&fun,reinterpret_cast<long*>(lVptrAddr+4),4); 为什么是加4呢?因为一个指针的长度是4bytes,所以加4。或者memcpy(&fun,reinterpret_cast<long*>(lVptrAddr)+1,4); 这更符合数组的用法,因为lVptrAddr被转成了long*型别,所以+1就是往后移sizeof(long)的长度
三, 以一段代码开始
#include<iostream>
using namespace std;
class A{ //虚函数示例代码2
public:
virtual void fun(){ cout<<"A::fun"<<endl;}
virtual void fun2(){cout<<"A::fun2"<<endl;}
};
class B:public A{
public:
void fun(){ cout<<"B::fun"<<endl;}
void fun2(){ cout<<"B::fun2"<<endl;}
}; //end//虚函数示例代码2
int main(){
void (A::*fun)(); //定义一个函数指针
A *p=new B;
fun=&A::fun;
(p->*fun)();
fun = &A::fun2;
(p->*fun)();
delete p;
system("pause");
}
你能估算出输出结果吗?如果你估算出的结果是A::fun和A::fun2,呵呵,恭喜恭喜,你中圈套了。其实真正的结果是B::fun和B::fun2,如果你想不通就接着往下看。给个提示,&A::fun和&A::fun2是真正获得了虚函数的地址吗?
首先我们回到第二部分,通过段实作代码,得到一个“通用”的获得虚函数地址的方法
#include<iostream>
using namespace std;
//将上面“虚函数示例代码2”添加在这里
void CallVirtualFun(void* pThis,int index=0){
void (*funptr)(void*);
long lVptrAddr;
memcpy(&lVptrAddr,pThis,4);
memcpy(&funptr,reinterpret_cast<long*>(lVptrAddr)+index,4);
funptr(pThis); //调用
}
int main(){
A* p=new B;
CallVirtualFun(p); //调用虚函数p->fun()
CallVirtualFun(p,1);//调用虚函数p->fun2()
system("pause");
}
现在我们拥有一个“通用”的CallVirtualFun方法。
这个通用方法和第三部分开始处的代码有何联系呢?联系很大。由于A::fun()和A::fun2()是虚函数,所以&A::fun和&A::fun2获得的不是函数的地址,而是一段间接获得虚函数地址的一段代码的地址,我们形象地把这段代码看作那段CallVirtualFun。编译器在编译时,会提供类似于CallVirtualFun这样的代码,当你调用虚函数时,其实就是先调用的那段类似CallVirtualFun的代码,通过这段代码,获得虚函数地址后,最后调用虚函数,这样就真正保证了多态性。同时大家都说虚函数的效率低,其原因就是,在调用虚函数之前,还调用了获得虚函数地址的代码。
最后的说明:本文的代码可以用VC6和Dev-C++4.9.8.0通过编译,且运行无问题。其他的编译器小弟不敢保证。其中,里面的类比方法只能看成模型,因为不同的编译器的低层实现是不同的。例如this指针,Dev-C++的gcc就是通过压栈,当作参数传递,而VC的编译器则通过取出地址保存在ecx中。所以这些类比方法不能当作具体实现。
1、 引言
Linux的兴起可以说是Internet创造的一个奇迹。Linux作为一个完全开放其原代码的免费的自由软件,兼容了各种UNIX标准(如POSIX、UNIX System V 和 BSD UNIX 等)的多用户、多任务的具有复杂内核的操作系统。在中国,随着Internet的普及,一批主要以高等院校的学生和ISP的技术人员组成的Linux爱好者队伍已经蓬勃成长起来。越来越多的编程爱好者也逐渐酷爱上这个优秀的自由软件。本文介绍了Linux下Socket的基本概念和函数调用。
2、 什么是Socket
Socket(套接字)是通过标准的UNIX文件描述符和其它程序通讯的一个方法。每一个套接字都用一个半相关描述:{协议,本地地址、本地端口}来表示;一个完整的套接字则用一个相关描述:{协议,本地地址、本地端口、远程地址、远程端口},每一个套接字都有一个本地的由操作系统分配的唯一的套接字号。
3、 Socket的三种类型
(1) 流式Socket(SOCK_STREAM)
流式套接字提供可靠的、面向连接的通信流;它使用TCP协议,从而保证了数据传输的正确性和顺序的。
(2) 数据报Socket(SOCK_DGRAM)
数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证可靠、无差错。它使用数据报协议UDP
(3) 原始Socket
原始套接字允许对底层协议如IP或ICMP直接访问,它功能强大但使用较为不便,主要用于一些协议的开发。
4、 利用套接字发送数据
1、 对于流式套接字用系统调用send()来发送数据。
2、 对于数据报套接字,则需要自己先加一个信息头,然后调用sendto()函数把数据发送出去。
5、 Linux中Socket的数据结构
(1) struct sockaddr { //用于存储套接字地址
unsigned short sa_family;//地址类型
char sa_data[14]; //14字节的协议地址
};
(2) struct sockaddr_in{ //in 代表internet
short int sin_family; //internet协议族
unsigned short int sin_port;//端口号,必须是网络字节顺序
struct in_addr sin_addr;//internet地址,必须是网络字节顺序
unsigned char sin_zero;//添0(和struct sockaddr一样大小
};
(3) struct in_addr{
unsigned long s_addr;
};
6、 网络字节顺序及其转换函数
(1) 网络字节顺序
每一台机器内部对变量的字节存储顺序不同,而网络传输的数据是一定要统一顺序的。所以对内部字节表示顺序与网络字节顺序不同的机器,一定要对数据进行转换,从程序的可移植性要求来讲,就算本机的内部字节表示顺序与网络字节顺序相同也应该在传输数据以前先调用数据转换函数,以便程序移植到其它机器上后能正确执行。真正转换还是不转换是由系统函数自己来决定的。
(2) 有关的转换函数
* unsigned short int htons(unsigned short int hostshort):
主机字节顺序转换成网络字节顺序,对无符号短型进行操作4bytes
* unsigned long int htonl(unsigned long int hostlong):
主机字节顺序转换成网络字节顺序,对无符号长型进行操作8bytes
* unsigned short int ntohs(unsigned short int netshort):
网络字节顺序转换成主机字节顺序,对无符号短型进行操作4bytes
* unsigned long int ntohl(unsigned long int netlong):
网络字节顺序转换成主机字节顺序,对无符号长型进行操作8bytes
注:以上函数原型定义在netinet/in.h里
7、 IP地址转换
有三个函数将数字点形式表示的字符串IP地址与32位网络字节顺序的二进制形式的IP地址进行转换
(1) unsigned long int inet_addr(const char * cp):该函数把一个用数字和点表示的IP地址的字符串转换成一个无符号长整型,如:struct sockaddr_in ina
ina.sin_addr.s_addr=inet_addr("202.206.17.101")
该函数成功时:返回转换结果;失败时返回常量INADDR_NONE,该常量=-1,二进制的无符号整数-1相当于255.255.255.255,这是一个广播地址,所以在程序中调用iner_addr()时,一定要人为地对调用失败进行处理。由于该函数不能处理广播地址,所以在程序中应该使用函数inet_aton()。
(2)int inet_aton(const char * cp,struct in_addr * inp):此函数将字符串形式的IP地址转换成二进制形式的IP地址;成功时返回1,否则返回0,转换后的IP地址存储在参数inp中。
(3) char * inet_ntoa(struct in-addr in):将32位二进制形式的IP地址转换为数字点形式的IP地址,结果在函数返回值中返回,返回的是一个指向字符串的指针。
8、 字节处理函数
Socket地址是多字节数据,不是以空字符结尾的,这和C语言中的字符串是不同的。Linux提供了两组函数来处理多字节数据,一组以b(byte)开头,是和BSD系统兼容的函数,另一组以mem(内存)开头,是ANSI C提供的函数。
以b开头的函数有:
(1) void bzero(void * s,int n):将参数s指定的内存的前n个字节设置为0,通常它用来将套接字地址清0。
(2) void bcopy(const void * src,void * dest,int n):从参数src指定的内存区域拷贝指定数目的字节内容到参数dest指定的内存区域。
(3) int bcmp(const void * s1,const void * s2,int n):比较参数s1指定的内存区域和参数s2指定的内存区域的前n个字节内容,如果相同则返回0,否则返回非0。
注:以上函数的原型定义在strings.h中。
以mem开头的函数有:
(1) void * memset(void * s,int c,size_t n):将参数s指定的内存区域的前n个字节设置为参数c的内容。
(2) void * memcpy(void * dest,const void * src,size_t n):功能同bcopy(),区别:函数bcopy()能处理参数src和参数dest所指定的区域有重叠的情况,memcpy()则不能。
(4) int memcmp(const void * s1,const void * s2,size_t n):比较参数s1和参数s2指定区域的前n个字节内容,如果相同则返回0,否则返回非0。
注:以上函数的原型定义在string.h中。
9、 基本套接字函数
(1) socket()
#include<sys/types.h>
#include<sys/socket.h>
int socket(int domain,int type,int protocol)
参数domain指定要创建的套接字的协议族,可以是如下值:
AF_UNIX //UNIX域协议族,本机的进程间通讯时使用
AF_INET //Internet协议族(TCP/IP)
AF_ISO //ISO协议族
参数type指定套接字类型,可以是如下值:
SOCK_STREAM //流套接字,面向连接的和可靠的通信类型
SOCK_DGRAM //数据报套接字,非面向连接的和不可靠的通信类型
SOCK_RAW //原始套接字,只对Internet协议有效,可以用来直接访问IP协议
参数protocol通常设置成0,表示使用默认协议,如Internet协议族的流套接字使用TCP协议,而数据报套接字使用UDP协议。当套接字是原始套接字类型时,需要指定参数protocol,因为原始套接字对多种协议有效,如ICMP和IGMP等。
Linux系统中创建一个套接字的操作主要是:在内核中创建一个套接字数据结构,然后返回一个套接字描述符标识这个套接字数据结构。这个套接字数据结构包含连接的各种信息,如对方地址、TCP状态以及发送和接收缓冲区等等,TCP协议根据这个套接字数据结构的内容来控制这条连接。
(2) 函数connect()
#include<sys/types.h>
#include<sys/socket.h>
int connect(int sockfd,struct sockaddr * servaddr,int addrlen)
参数sockfd是函数socket返回的套接字描述符;参数servaddr指定远程服务器的套接字地址,包括服务器的IP地址和端口号;参数addrlen指定这个套接字地址的长度。成功时返回0,否则返回-1,并设置全局变量为以下任何一种错误类型:ETIMEOUT、ECONNREFUSED、EHOSTUNREACH或ENETUNREACH。
在调用函数connect之前,客户机需要指定服务器进程的套接字地址。客户机一般不需要指定自己的套接字地址(IP地址和端口号),系统会自动从1024至5000的端口号范围内为它选择一个未用的端口号,然后以这个端口号和本机的IP地址填充这个套接字地址。
客户机调用函数connect来主动建立连接。这个函数将启动TCP协议的3次握手过程。在建立连接之后或发生错误时函数返回。连接过程可能出现的错误情况有:
(1) 如果客户机TCP协议没有接收到对它的SYN数据段的确认,函数以错误返回,错误类型为ETIMEOUT。通常TCP协议在发送SYN数据段失败之后,会多次发送SYN数据段,在所有的发送都高中失败之后,函数以错误返回。
注:SYN(synchronize)位:请求连接。TCP用这种数据段向对方TCP协议请求建立连接。在这个数据段中,TCP协议将它选择的初始序列号通知对方,并且与对方协议协商最大数据段大小。SYN数据段的序列号为初始序列号,这个SYN数据段能够被确认。当协议接收到对这个数据段的确认之后,建立TCP连接。
(2) 如果远程TCP协议返回一个RST数据段,函数立即以错误返回,错误类型为ECONNREFUSED。当远程机器在SYN数据段指定的目的端口号处没有服务进程在等待连接时,远程机器的TCP协议将发送一个RST数据段,向客户机报告这个错误。客户机的TCP协议在接收到RST数据段后不再继续发送SYN数据段,函数立即以错误返回。
注:RST(reset)位:表示请求重置连接。当TCP协议接收到一个不能处理的数据段时,向对方TCP协议发送这种数据段,表示这个数据段所标识的连接出现了某种错误,请求TCP协议将这个连接清除。有3种情况可能导致TCP协议发送RST数据段:(1)SYN数据段指定的目的端口处没有接收进程在等待;(2)TCP协议想放弃一个已经存在的连接;(3)TCP接收到一个数据段,但是这个数据段所标识的连接不存在。接收到RST数据段的TCP协议立即将这条连接非正常地断开,并向应用程序报告错误。
(3) 如果客户机的SYN数据段导致某个路由器产生“目的地不可到达”类型的ICMP消息,函数以错误返回,错误类型为EHOSTUNREACH或ENETUNREACH。通常TCP协议在接收到这个ICMP消息之后,记录这个消息,然后继续几次发送SYN数据段,在所有的发送都告失败之后,TCP协议检查这个ICMP消息,函数以错误返回。
注:ICMP:Internet 消息控制协议。Internet的运行主要是由Internet的路由器来控制,路由器完成IP数据包的发送和接收,如果发送数据包时发生错误,路由器使用ICMP协议来报告这些错误。ICMP数据包是封装在IP数据包的数据部分中进行传输的,其格式如下:
类型
码
校验和
数据
0 8 16 24 31
类型:指出ICMP数据包的类型。
代码:提供ICMP数据包的进一步信息。
校验和:提供了对整个ICMP数据包内容的校验和。
ICMP数据包主要有以下类型:
(1) 目的地不可到达:A、目的主机未运行;B、目的地址不存在;C、路由表中没有目的地址对应的条目,因而路由器无法找到去往目的主机的路由。
(2) 超时:路由器将接收到的IP数据包的生存时间(TTL)域减1,如果这个域的值变为0,路由器丢弃这个IP数据包,并且发送这种ICMP消息。
(3) 参数出错:当IP数据包中有无效域时发送。
(4) 重定向:将一条新的路径通知主机。
(5) ECHO请求、ECHO回答:这两条消息用语测试目的主机是否可以到达。请求者向目的主机发送ECHO请求ICMP数据包,目的主机在接收到这个ICMP数据包之后,返回ECHO回答ICMP数据包。
(6) 时戳请求、时戳回答:ICMP协议使用这两种消息从其他机器处获得其时钟的当前时间。
调用函数connect的过程中,当客户机TCP协议发送了SYN数据段的确认之后,TCP状态由CLOSED状态转为SYN_SENT状态,在接收到对SYN数据段的确认之后,TCP状态转换成ESTABLISHED状态,函数成功返回。如果调用函数connect失败,应该用close关闭这个套接字描述符,不能再次使用这个套接字描述符来调用函数connect。
注:TCP协议状态转换图:
被动OPEN CLOSE 主动OPEN
(建立TCB) (删除TCB) (建立TCB,
发送SYN)
接收SYN SEND
(发送SYN,ACK) (发送SYN)
接收SYN的ACK(无动作)
接收SYN的ACK 接收SYN,ACK
(无动作) (发送ACK)
CLOSE
(发送FIN) CLOSE 接收FIN
(发送FIN) (发送FIN)
接收FIN
接收FIN的ACK(无动作) (发送ACK) CLOSE(发送FIN)
接收FIN 接收FIN的ACK 接收FIN的ACK
(发送ACK) (无动作) (无动作)
2MSL超时(删除TCB)
(3) 函数bind()
函数bind将本地地址与套接字绑定在一起,其定义如下:
#include<sys/types.h>
#include<sys/socket.h>
int bind(int sockfd,struct sockaddr * myaddr,int addrlen);
参数sockfd是函数sockt返回的套接字描述符;参数myaddr是本地地址;参数addrlen是套接字地址结构的长度。执行成功时返回0,否则,返回-1,并设置全局变量errno为错误类型EADDRINUSER。
服务器和客户机都可以调用函数bind来绑定套接字地址,但一般是服务器调用函数bind来绑定自己的公认端口号。绑定操作一般有如下几种组合方式:
表1
程序类型
IP地址
端口号
说明
服务器
INADDR_ANY
非零值
指定服务器的公认端口号
服务器
本地IP地址
非零值
指定服务器的IP地址和公认端口号
客户机
INADDR_ANY
非零值
指定客户机的连接端口号
客户机
本地IP地址
非零值
指定客户机的IP地址连接端口号
客户机
本地IP地址
零
指定客户机的IP地址
分别说明如下:
(1) 服务器指定套接字地址的公认端口号,不指定IP地址:即服务器调用bind时,设置套接字的IP地址为特殊的INADDE-ANY,表示它愿意接收来自任何网络设备接口的客户机连接。这是服务器最常用的绑定方式。
(2) 服务器指定套接字地址的公认端口号和IP地址:服务器调用bind时,如果设置套接字的IP地址为某个本地IP地址,这表示这台机器只接收来自对应于这个IP地址的特定网络设备接口的客户机连接。当服务器有多块网卡时,可以用这种方式来限制服务器的接收范围。
(3) 客户机指定套接字地址的连接端口号:一般情况下,客户机调用connect函数时不用指定自己的套接字地址的端口号。系统会自动为它选择一个未用的端口号,并且用本地的IP地址来填充套接字地址中的相应项。但有时客户机需要使用一个特定的端口号(比如保留端口号),而系统不会未客户机自动分配一个保留端口号,所以需要调用函数bind来和一个未用的保留端口号绑定。
(4) 指定客户机的IP地址和连接端口号:表示客户机使用指定的网络设备接口和端口号进行通信。
(5) 指定客户机的IP地址:表示客户机使用指定的网络设备接口和端口号进行通信,系统自动为客户机选一个未用的端口号。一般只有在主机有多个网络设备接口时使用。
我们一般不在客户机上使用固定的客户机端口号,除非是必须使用的情况。在客户机上使用固定的端口号有以下不利:
(1) 服务器执行主动关闭操作:服务器最后进入TIME_WAIT状态。当客户机再次与这个服务器进行连接时,仍使用相同的客户机端口号,于是这个连接与前次连接的套接字对完全一样,但是一呢、为前次连接处于TIME_WAIT状态,并未消失,所以这次连接请求被拒绝,函connect以错误返回,错误类型为ECONNREFUSED
(2) 客户机执行主动关闭操作:客户机最后进入TIME_WAIT状态。当马上再次执行这个客户机程序时,客户机将继续与这个固定客户机端口号绑定,但因为前次连接处于TIME_WAIT状态,并未消失,系统会发现这个端口号仍被占用,所以这次绑定操作失败,函数bind以错误返回,错误类型为EADDRINUSE。
(4) 函数listen()
函数listen将一个套接字转换为征听套接字,定义如下;
#include<sys/socket,h>
int listen(int sockfd,int backlog)
参数sockfd指定要转换的套接字描述符;参数backlog设置请求队列的最大长度;执行成功时返回0, 否则返回-1。函数listen功能有两个:
(1) 将一个尚未连接的主动套接字(函数socket创建的可以用来进行主动连接但不能接受连接请求的套接字)转换成一个被动连接套接字。执行listen之后,服务器的TCP状态由CLOSED转为LISTEN状态。
(2) TCP协议将到达的连接请求队列,函数listen的第二个参数指定这个队列的最大长度。
注:参数backlog的作用:
TCP协议为每一个征听套接字维护两个队列:
(1) 未完成连接队列:每个尚未完成3次握手操作的TCP连接在这个队列中占有一项。TCP希望仪在接收到一个客户机SYN数据段之后,在这个队列中创建一个新条目,然后发送对客户机SYN数据段的确认和自己的SYN数据段(ACK+SYN数据段),等待客户机对自己的SYN数据段的确认。此时,套接字处于SYN_RCVD状态。这个条目将保存在这个队列中,直到客户机返回对SYN数据段的确认或者连接超时。
(2) 完成连接队列:每个已经完成3次握手操作,但尚未被应用程序接收(调用函数accept)的TCP连接在这个队列中占有一项。当一个在未完成连接队列中的连接接收到对SYN数据段的确认之后,完成3次握手操作,TCP协议将它从未完成连接队列移到完成连接队列中。此时,套接字处于ESTABLISHED状态。这个条目将保存在这个队列中,直到应用程序调用函数accept来接收它。
参数backlog指定某个征听套接字的完成连接队列的最大长度,表示这个套接字能够接收的最大数目的未接收连接。如果当一个客户机的SYN数据段到达时,征听套接字的完成队列已经满了,那么TCP协议将忽略这个SYN数据段。对于不能接收的SYN数据段,TCP协议不发送RST数据段,
(5) 函数accept()
函数accept从征听套接字的完成队列中接收一个已经建立起来的TCP连接。如果完成连接队列为空,那么这个进程睡眠。
#include<sys/socket.h>
int accept(int sockfd,struct sockaddr * addr,int * addrlen)
参数sockfd指定征听套接字描述符;参数addr为指向一个Internet套接字地址结构的指针;参数addrlen为指向一个整型变量的指针。执行成功时,返回3个结果:函数返回值为一个新的套接字描述符,标识这个接收的连接;参数addr指向的结构变量中存储客户机地址;参数addrlen指向的整型变量中存储客户机地址的长度。失败时返回-1。
征听套接字专为接收客户机连接请求,完成3次握手操作而用的,所以TCP协议不能使用征听套接字描述符来标识这个连接,于是TCP协议创建一个新的套接字来标识这个要接收的连接,并将它的描述符发挥给应用程序。现在有两个套接字,一个是调用函数accept时使用的征听套接字,另一个是函数accept返回的连接套接字(connected socket)。一个服务器通常只需创建一个征听套接字,在服务器进程的整个活动期间,用它来接收所有客户机的连接请求,在服务器进程终止前关闭这个征听套接字;对于没一个接收的(accepted)连接,TCP协议都创建一个新的连接套接字来标识这个连接,服务器使用这个连接套接字与客户机进行通信操作,当服务器处理完这个客户机请求时,关闭这个连接套接字。
当函数accept阻塞等待已经建立的连接时,如果进程捕获到信号,函数将以错误返回,错误类型为EINTR。对于这种错误,一般重新调用函数accept来接收连接。
(6) 函数close()
函数close关闭一个套接字描述符。定义如下:
#include<unistd.h>
int close(int sockfd);
执行成功时返回0,否则返回-1。与操作文件描述符的close一样,函数close将套接字描述符的引用计数器减1,如果描述符的引用计数大于0,则表示还有进程引用这个描述符,函数close正常返回;如果为0,则启动清除套接字描述符的操作,函数close立即正常返回。
调用close之后,进程将不再能够访问这个套接字,但TCP协议将继续使用这个套接字,将尚未发送的数据传递到对方,然后发送FIN数据段,执行关闭操作,一直等到这个TCP连接完全关闭之后,TCP协议才删除该套接字。
(7) 函数read()和write()
用于从套接字读写数据。定义如下:
int read(int fd,char * buf,int len)
int write(int fd,char * buf,int len)
函数执行成功时,返回读或写的数据量的大小,失败时返回-1。
每个TCP套接字都有两个缓冲区:套接字发送缓冲区、套接字接收缓冲区,分别处理发送和接收任务。从网络读、写数据的操作是由TCP协议在内核中完成的:TCP协议将从网络上接收到的数据保存在相应套接字的接收缓冲区中,等待用户调用函数将它们从接收缓冲区拷贝到用户缓冲区;用户将要发送的数据拷贝到相应套接字的发送缓冲区中,然后由TCP协议按照一定的算法处理这些数据。
读写连接套接字的操作与读写文件的操作类似,也可以使用函数read和write。函数read完成将数据从套接字接收缓冲区拷贝到用户缓冲区:当套接字接收缓冲区有数据可读时,1:可读数据量大于函数read指定值,返回函数参数len指定的数据量;2:了度数据量小于函数read指定值,函数read不等待请求的所有数据都到达,而是立即返回实际读到的数据量;当无数据可读时,函数read将阻塞不返回,等待数据到达。
当TCP协议接收到FIN数据段,相当于给读操作一个文件结束符,此时read函数返回0,并且以后所有在这个套接字上的读操作均返回0,这和普通文件中遇到文件结束符是一样的。
当TCP协议接收到RST数据段,表示连接出现了某种错误,函数read将以错误返回,错误类型为ECONNERESET。并且以后所有在这个套接字上的读操作均返回错误。错误返回时返回值小于0。
函数write完成将数据从用户缓冲区拷贝到套接字发送缓冲区的任务:到套接字发送缓冲区有足够拷贝所有用户数据的空间时,函数write将数据拷贝到这个缓冲区中,并返回老辈的数量大小,如果可用空间小于write参数len指定的大小时,函数write将阻塞不返回,等待缓冲区有足够的空间。
当TCP协议接收到RST数据段(当对方已经关闭了这条连接之后,继续向这个套接字发送数据将导致对方TCP协议返回RST数据段),TCP协议接收到RST数据段时,函数write将以错误返回,错误类型为EINTR。以后可以继续在这个套接字上写数据。
(8) 函数getsockname()和getpeername()
函数getsockname返回套接字的本地地址;函数getpeername返回套接字对应的远程地址。
10、 结束语
网络程序设计全靠套接字接收和发送信息。上文主要讲述了Linux 下Socket的基本概念、Sockets API以及Socket所涉及到的TCP常识。
一、ADO简介 ADO(ActiveX Data Object)是Microsoft数据库应用程序开发的新接口,是建立在OLE DB之上的高层数据库访问技术,请不必为此担心,即使你对OLE DB,COM不了解也能轻松对付ADO,因为它非常简单易用,甚至比你以往所接触的ODBC API、DAO、RDO都要容易使用,并不失灵活性。本文将详细地介绍在VC下如何使用ADO来进行数据库应用程序开发,并给出示例代码。 本文示例代码
二、基本流程 万事开头难,任何一种新技术对于初学者来说最重要的还是“入门”,掌握其要点。让我们来看看ADO数据库开发的基本流程吧! (1)初始化COM库,引入ADO库定义文件 (2)用Connection对象连接数据库 (3)利用建立好的连接,通过Connection、Command对象执行SQL命令,或利用Recordset对象取得结果记录集进行查询、处理。 (4)使用完毕后关闭连接释放对象。
准备工作: 为了大家都能测试本文提供的例子,我们采用Access数据库,您也可以直接在我们提供的示例代码中找到这个test.mdb。 下面我们将详细介绍上述步骤并给出相关代码。 【1】COM库的初始化 我们可以使用AfxOleInit()来初始化COM库,这项工作通常在CWinApp::InitInstance()的重载函数中完成,请看如下代码:
BOOL CADOTest1App::InitInstance() { AfxOleInit(); ......
【2】用#import指令引入ADO类型库 我们在stdafx.h中加入如下语句:(stdafx.h这个文件哪里可以找到?你可以在FileView中的Header Files里找到) #import "c:\program files\common files\system\ado\msado15.dll" no_namespace rename("EOF","adoEOF") 这一语句有何作用呢?其最终作用同我们熟悉的#include类似,编译的时候系统会为我们生成msado15.tlh,ado15.tli两个C++头文件来定义ADO库。
几点说明: (1) 您的环境中msado15.dll不一定在这个目录下,请按实际情况修改 (2) 在编译的时候肯能会出现如下警告,对此微软在MSDN中作了说明,并建议我们不要理会这个警告。 msado15.tlh(405) : warning C4146: unary minus operator applied to unsigned type, result still unsigned
【3】创建Connection对象并连接数据库 首先我们需要添加一个指向Connection对象的指针: _ConnectionPtr m_pConnection; 下面的代码演示了如何创建Connection对象实例及如何连接数据库并进行异常捕捉。
BOOL CADOTest1Dlg::OnInitDialog() { CDialog::OnInitDialog(); HRESULT hr; try { hr = m_pConnection.CreateInstance("ADODB.Connection");///创建Connection对象 if(SUCCEEDED(hr)) { hr = m_pConnection->Open("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=test.mdb","","",adModeUnknown);///连接数据库 ///上面一句中连接字串中的Provider是针对ACCESS2000环境的,对于ACCESS97,需要改为:Provider=Microsoft.Jet.OLEDB.3.51; } } catch(_com_error e)///捕捉异常 { CString errormessage; errormessage.Format("连接数据库失败!\r\n错误信息:%s",e.ErrorMessage()); AfxMessageBox(errormessage);///显示错误信息 }
在这段代码中我们是通过Connection对象的Open方法来进行连接数据库的,下面是该方法的原型 HRESULT Connection15::Open ( _bstr_t ConnectionString, _bstr_t UserID, _bstr_t Password, long Options ) ConnectionString为连接字串,UserID是用户名, Password是登陆密码,Options是连接选项,用于指定Connection对象对数据的更新许可权, Options可以是如下几个常量: adModeUnknown:缺省。当前的许可权未设置 adModeRead:只读 adModeWrite:只写 adModeReadWrite:可以读写 adModeShareDenyRead:阻止其它Connection对象以读权限打开连接 adModeShareDenyWrite:阻止其它Connection对象以写权限打开连接 adModeShareExclusive:阻止其它Connection对象打开连接 adModeShareDenyNone:允许其它程序或对象以任何权限建立连接
我们给出一些常用的连接方式供大家参考: (1)通过JET数据库引擎对ACCESS2000数据库的连接
m_pConnection->Open("Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\\test.mdb","","",adModeUnknown);
(2)通过DSN数据源对任何支持ODBC的数据库进行连接: m_pConnection->Open("Data Source=adotest;UID=sa;PWD=;","","",adModeUnknown);
(3)不通过DSN对SQL SERVER数据库进行连接: m_pConnection->Open("driver={SQL Server};Server=127.0.0.1;DATABASE=vckbase;UID=sa;PWD=139","","",adModeUnknown);
其中Server是SQL服务器的名称,DATABASE是库的名称
Connection对象除Open方法外还有许多方法,我们先介绍Connection对象中两个有用的属性ConnectionTimeOut与State ConnectionTimeOut用来设置连接的超时时间,需要在Open之前调用,例如: m_pConnection->ConnectionTimeout = 5;///设置超时时间为5秒 m_pConnection->Open("Data Source=adotest;","","",adModeUnknown);
State属性指明当前Connection对象的状态,0表示关闭,1表示已经打开,我们可以通过读取这个属性来作相应的处理,例如: if(m_pConnection->State) m_pConnection->Close(); ///如果已经打开了连接则关闭它
【4】执行SQL命令并取得结果记录集 为了取得结果记录集,我们定义一个指向Recordset对象的指针:_RecordsetPtr m_pRecordset; 并为其创建Recordset对象的实例: m_pRecordset.CreateInstance("ADODB.Recordset"); SQL命令的执行可以采用多种形式,下面我们一进行阐述。
(1)利用Connection对象的Execute方法执行SQL命令 Execute方法的原型如下所示: _RecordsetPtr Connection15::Execute ( _bstr_t CommandText, VARIANT * RecordsAffected, long Options ) 其中CommandText是命令字串,通常是SQL命令。参数RecordsAffected是操作完成后所影响的行数, 参数Options表示CommandText中内容的类型,Options可以取如下值之一: adCmdText:表明CommandText是文本命令 adCmdTable:表明CommandText是一个表名 adCmdProc:表明CommandText是一个存储过程 adCmdUnknown:未知
Execute执行完后返回一个指向记录集的指针,下面我们给出具体代码并作说明。 _variant_t RecordsAffected; ///执行SQL命令:CREATE TABLE创建表格users,users包含四个字段:整形ID,字符串username,整形old,日期型birthday m_pConnection->Execute("CREATE TABLE users(ID INTEGER,username TEXT,old INTEGER,birthday DATETIME)",&RecordsAffected,adCmdText); ///往表格里面添加记录 m_pConnection->Execute("INSERT INTO users(ID,username,old,birthday) valueS (1, ''''''''Washington'''''''',25,''''''''1970/1/1'''''''')",&RecordsAffected,adCmdText); ///将所有记录old字段的值加一 m_pConnection->Execute("UPDATE users SET old = old+1",&RecordsAffected,adCmdText); ///执行SQL统计命令得到包含记录条数的记录集 m_pRecordset = m_pConnection->Execute("SELECT COUNT(*) FROM users",&RecordsAffected,adCmdText); _variant_t vIndex = (long)0; _variant_t vCount = m_pRecordset->GetCollect(vIndex);///取得第一个字段的值放入vCount变量 m_pRecordset->Close();///关闭记录集 CString message; message.Format("共有%d条记录",vCount.lVal); AfxMessageBox(message);///显示当前记录条数
(2)利用Command对象来执行SQL命令 _CommandPtr m_pCommand; m_pCommand.CreateInstance("ADODB.Command"); _variant_t vNULL; vNULL.vt = VT_ERROR; vNULL.scode = DISP_E_PARAMNOTFOUND;///定义为无参数 m_pCommand->ActiveConnection = m_pConnection;///非常关键的一句,将建立的连接赋值给它 m_pCommand->CommandText = "SELECT * FROM users";///命令字串 m_pRecordset = m_pCommand->Execute(&vNULL,&vNULL,adCmdText);///执行命令,取得记录集
在这段代码中我们只是用Command对象来执行了SELECT查询语句,Command对象在进行存储过程的调用中能真正体现它的作用。下次我们将详细介绍。
(3)直接用Recordset对象进行查询取得记录集 例如
m_pRecordset->Open("SELECT * FROM users",_variant_t((IDispatch *)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText);
Open方法的原型是这样的: HRESULT Recordset15::Open ( const _variant_t & Source, const _variant_t & ActiveConnection, enum CursorTypeEnum CursorType, enum LockTypeEnum LockType, long Options ) 其中: ①Source是数据查询字符串 ②ActiveConnection是已经建立好的连接(我们需要用Connection对象指针来构造一个_variant_t对象) ③CursorType光标类型,它可以是以下值之一,请看这个枚举结构: enum CursorTypeEnum { adOpenUnspecified = -1,///不作特别指定 adOpenForwardOnly = 0,///前滚静态光标。这种光标只能向前浏览记录集,比如用MoveNext向前滚动,这种方式可以提高浏览速度。但诸如BookMark,RecordCount,AbsolutePosition,AbsolutePage都不能使用 adOpenKeyset = 1,///采用这种光标的记录集看不到其它用户的新增、删除操作,但对于更新原有记录的操作对你是可见的。 adOpenDynamic = 2,///动态光标。所有数据库的操作都会立即在各用户记录集上反应出来。 adOpenStatic = 3///静态光标。它为你的记录集产生一个静态备份,但其它用户的新增、删除、更新操作对你的记录集来说是不可见的。 }; ④LockType锁定类型,它可以是以下值之一,请看如下枚举结构: enum LockTypeEnum { adLockUnspecified = -1,///未指定 adLockReadOnly = 1,///只读记录集 adLockPessimistic = 2,悲观锁定方式。数据在更新时锁定其它所有动作,这是最安全的锁定机制 adLockOptimistic = 3,乐观锁定方式。只有在你调用Update方法时才锁定记录。在此之前仍然可以做数据的更新、插入、删除等动作 adLockBatchOptimistic = 4,乐观分批更新。编辑时记录不会锁定,更改、插入及删除是在批处理模式下完成。 }; ⑤Options请参考本文中对Connection对象的Execute方法的介绍
【5】记录集的遍历、更新 根据我们刚才通过执行SQL命令建立好的users表,它包含四个字段:ID,username,old,birthday 以下的代码实现:打开记录集,遍历所有记录,删除第一条记录,添加三条记录,移动光标到第二条记录,更改其年龄,保存到数据库。
_variant_t vUsername,vBirthday,vID,vOld; _RecordsetPtr m_pRecordset; m_pRecordset.CreateInstance("ADODB.Recordset"); m_pRecordset->Open("SELECT * FROM users",_variant_t((IDispatch*)m_pConnection,true),adOpenStatic,adLockOptimistic,adCmdText); while(!m_pRecordset->adoEOF)///这里为什么是adoEOF而不是EOF呢?还记得rename("EOF","adoEOF")这一句吗? { vID = m_pRecordset->GetCollect(_variant_t((long)0));///取得第1列的值,从0开始计数,你也可以直接给出列的名称,如下一行 vUsername = m_pRecordset->GetCollect("username");///取得username字段的值 vOld = m_pRecordset->GetCollect("old"); vBirthday = m_pRecordset->GetCollect("birthday"); ///在DEBUG方式下的OUTPUT窗口输出记录集中的记录 if(vID.vt != VT_NULL && vUsername.vt != VT_NULL && vOld.vt != VT_NULL && vBirthday.vt != VT_NULL) TRACE("id:%d,姓名:%s,年龄:%d,生日:%s\r\n",vID.lVal,(LPCTSTR)(_bstr_t)vUsername,vOld.lVal,(LPCTSTR)(_bstr_t)vBirthday); m_pRecordset->MoveNext();///移到下一条记录 } m_pRecordset->MoveFirst();///移到首条记录 m_pRecordset->Delete(adAffectCurrent);///删除当前记录 ///添加三条新记录并赋值 for(int i=0;i<3;i++) { m_pRecordset->AddNew();///添加新记录 m_pRecordset->PutCollect("ID",_variant_t((long)(i+10))); m_pRecordset->PutCollect("username",_variant_t("叶利钦")); m_pRecordset->PutCollect("old",_variant_t((long)71)); m_pRecordset->PutCollect("birthday",_variant_t("1930-3-15")); } m_pRecordset->Move(1,_variant_t((long)adBookmarkFirst));///从第一条记录往下移动一条记录,即移动到第二条记录处 m_pRecordset->PutCollect(_variant_t("old"),_variant_t((long)45));///修改其年龄 m_pRecordset->Update();///保存到库中
【6】关闭记录集与连接 记录集或连接都可以用Close方法来关闭 m_pRecordset->Close();///关闭记录集 m_pConnection->Close();///关闭连接
至此,我想您已经熟悉了ADO操作数据库的大致流程,也许您已经胸有成竹,也许您还有点胡涂,不要紧!建议你尝试写几个例子,这样会更好地熟悉ADO,最后我给大家写了一个小例子,例子中读出所有记录放到列表控件中、并可以添加、删除、修改记录。 点这里下载示例代码
后记:限于篇幅ADO中的许多内容还没有介绍,下次我们将详细介绍Recordset对象的属性、方法并解决几个关键的技术:绑定方式处理记录集数据、存储过程的调用、事务处理、图象在数据库中的保存与读取、与表格控件的配合使用等。 下次再见吧!
Linux 操作系统从一开始就对串行口提供了很好的支持,本文就 Linux 下的串行口通讯编程进行简单的介绍。
串口简介
串行口是计算机一种常用的接口,具有连接线少,通讯简单,得到广泛的使用。常用的串口是 RS-232-C 接口(又称 EIA RS-232-C)它是在 1970 年由美国电子工业协会(EIA)联合贝尔系统、 调制解调器厂家及计算机终端生产厂家共同制定的用于串行通讯的标准。它的全名是"数据终端设备(DTE)和数据通讯设备(DCE)之间串行二进制数据交换接口技术标准"该标准规定采用一个 25 个脚的 DB25 连接器,对连接器的每个引脚的信号内容加以规定,还对各种信号的电平加以规定。传输距离在码元畸变小于 4% 的情况下,传输电缆长度应为 50 英尺。
Linux 操作系统从一开始就对串行口提供了很好的支持,本文就 Linux 下的串行口通讯编程进行简单的介绍,如果要非常深入了解,建议看看本文所参考的 《Serial Programming Guide for POSIX Operating Systems》
计算机串口的引脚说明
序号 |
信号名称 |
符号 |
流向 |
功能 |
2 |
发送数据 |
TXD |
DTE→DCE |
DTE发送串行数据 |
3 |
接收数据 |
RXD |
DTE←DCE |
DTE 接收串行数据 |
4 |
请求发送 |
RTS |
DTE→DCE |
DTE 请求 DCE 将线路切换到发送方式 |
5 |
允许发送 |
CTS |
DTE←DCE |
DCE 告诉 DTE 线路已接通可以发送数据 |
6 |
数据设备准备好 |
DSR |
DTE←DCE |
DCE 准备好 |
7 |
信号地 |
|
|
信号公共地 |
8 |
载波检测 |
DCD |
DTE←DCE |
表示 DCE 接收到远程载波 |
20 |
数据终端准备好 |
DTR |
DTE→DCE |
DTE 准备好 |
22 |
振铃指示 |
RI |
DTE←DCE |
表示 DCE 与线路接通,出现振铃 |
串口操作
串口操作需要的头文件
#include <stdio.h> /*标准输入输出定义*/
#include <stdlib.h> /*标准函数库定义*/
#include <unistd.h> /*Unix 标准函数定义*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h> /*文件控制定义*/
#include <termios.h> /*PPSIX 终端控制定义*/
#include <errno.h> /*错误号定义*/
|
打开串口
在 Linux 下串口文件是位于 /dev 下的
串口一 为 /dev/ttyS0
串口二 为 /dev/ttyS1
打开串口是通过使用标准的文件打开函数操作:
int fd;
/*以读写方式打开串口*/
fd = open( "/dev/ttyS0", O_RDWR);
if (-1 == fd){
/* 不能打开串口一*/
perror(" 提示错误!");
}
|
设置串口
最基本的设置串口包括波特率设置,效验位和停止位设置。
串口的设置主要是设置 struct termios 结构体的各成员值。
struct termio
{ unsigned short c_iflag; /* 输入模式标志 */
unsigned short c_oflag; /* 输出模式标志 */
unsigned short c_cflag; /* 控制模式标志*/
unsigned short c_lflag; /* local mode flags */
unsigned char c_line; /* line discipline */
unsigned char c_cc[NCC]; /* control characters */
};
|
设置这个结构体很复杂,我这里就只说说常见的一些设置:
波特率设置
下面是修改波特率的代码:
struct termios Opt;
tcgetattr(fd, &Opt);
cfsetispeed(&Opt,B19200); /*设置为19200Bps*/
cfsetospeed(&Opt,B19200);
tcsetattr(fd,TCANOW,&Opt);
|
设置波特率的例子函数:
/**
*@brief 设置串口通信速率
*@param fd 类型 int 打开串口的文件句柄
*@param speed 类型 int 串口速度
*@return void
*/
int speed_arr[] = { B38400, B19200, B9600, B4800, B2400, B1200, B300,
B38400, B19200, B9600, B4800, B2400, B1200, B300, };
int name_arr[] = {38400, 19200, 9600, 4800, 2400, 1200, 300, 38400,
19200, 9600, 4800, 2400, 1200, 300, };
void set_speed(int fd, int speed){
int i;
int status;
struct termios Opt;
tcgetattr(fd, &Opt);
for ( i= 0; i < sizeof(speed_arr) / sizeof(int); i++) {
if (speed == name_arr[i]) {
tcflush(fd, TCIOFLUSH);
cfsetispeed(&Opt, speed_arr[i]);
cfsetospeed(&Opt, speed_arr[i]);
status = tcsetattr(fd1, TCSANOW, &Opt);
if (status != 0) {
perror("tcsetattr fd1");
return;
}
tcflush(fd,TCIOFLUSH);
}
}
}
|
效验位和停止位的设置:
无效验 |
8位 |
Option.c_cflag &= ~PARENB; Option.c_cflag &= ~CSTOPB; Option.c_cflag &= ~CSIZE; Option.c_cflag |= ~CS8; |
奇效验(Odd) |
7位 |
Option.c_cflag |= ~PARENB; Option.c_cflag &= ~PARODD; Option.c_cflag &= ~CSTOPB; Option.c_cflag &= ~CSIZE; Option.c_cflag |= ~CS7; |
偶效验(Even) |
7位 |
Option.c_cflag &= ~PARENB; Option.c_cflag |= ~PARODD; Option.c_cflag &= ~CSTOPB; Option.c_cflag &= ~CSIZE; Option.c_cflag |= ~CS7; |
Space效验 |
7位 |
Option.c_cflag &= ~PARENB; Option.c_cflag &= ~CSTOPB; Option.c_cflag &= &~CSIZE; Option.c_cflag |= CS8; |
设置效验的函数:
/**
*@brief 设置串口数据位,停止位和效验位
*@param fd 类型 int 打开的串口文件句柄
*@param databits 类型 int 数据位 取值 为 7 或者8
*@param stopbits 类型 int 停止位 取值为 1 或者2
*@param parity 类型 int 效验类型 取值为N,E,O,,S
*/
int set_Parity(int fd,int databits,int stopbits,int parity)
{
struct termios options;
if ( tcgetattr( fd,&options) != 0) {
perror("SetupSerial 1");
return(FALSE);
}
options.c_cflag &= ~CSIZE;
switch (databits) /*设置数据位数*/
{
case 7:
options.c_cflag |= CS7;
break;
case 8:
options.c_cflag |= CS8;
break;
default:
fprintf(stderr,"Unsupported data size\n"); return (FALSE);
}
switch (parity)
{
case 'n':
case 'N':
options.c_cflag &= ~PARENB; /* Clear parity enable */
options.c_iflag &= ~INPCK; /* Enable parity checking */
break;
case 'o':
case 'O':
options.c_cflag |= (PARODD | PARENB); /* 设置为奇效验*/
options.c_iflag |= INPCK; /* Disnable parity checking */
break;
case 'e':
case 'E':
options.c_cflag |= PARENB; /* Enable parity */
options.c_cflag &= ~PARODD; /* 转换为偶效验*/
options.c_iflag |= INPCK; /* Disnable parity checking */
break;
case 'S':
case 's': /*as no parity*/
options.c_cflag &= ~PARENB;
options.c_cflag &= ~CSTOPB;break;
default:
fprintf(stderr,"Unsupported parity\n");
return (FALSE);
}
/* 设置停止位*/
switch (stopbits)
{
case 1:
options.c_cflag &= ~CSTOPB;
break;
case 2:
options.c_cflag |= CSTOPB;
break;
default:
fprintf(stderr,"Unsupported stop bits\n");
return (FALSE);
}
/* Set input parity option */
if (parity != 'n')
options.c_iflag |= INPCK;
tcflush(fd,TCIFLUSH);
options.c_cc[VTIME] = 150; /* 设置超时15 seconds*/
options.c_cc[VMIN] = 0; /* Update the options and do it NOW */
if (tcsetattr(fd,TCSANOW,&options) != 0)
{
perror("SetupSerial 3");
return (FALSE);
}
return (TRUE);
}
|
需要注意的是:
如果不是开发终端之类的,只是串口传输数据,而不需要串口来处理,那么使用原始模式(Raw Mode)方式来通讯,设置方式如下:
options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); /*Input*/
options.c_oflag &= ~OPOST; /*Output*/
|
读写串口
设置好串口之后,读写串口就很容易了,把串口当作文件读写就是。
关闭串口
关闭串口就是关闭文件。
例子
下面是一个简单的读取串口数据的例子,使用了上面定义的一些函数和头文件
/**********************************************************************代码说明:使用串口二测试的,发送的数据是字符,
但是没有发送字符串结束符号,所以接收到后,后面加上了结束符号。我测试使用的是单片机发送数据到第二个串口,测试通过。
**********************************************************************/
#define FALSE -1
#define TRUE 0
/*********************************************************************/
int OpenDev(char *Dev)
{
int fd = open( Dev, O_RDWR ); //| O_NOCTTY | O_NDELAY
if (-1 == fd)
{
perror("Can't Open Serial Port");
return -1;
}
else
return fd;
}
int main(int argc, char **argv){
int fd;
int nread;
char buff[512];
char *dev = "/dev/ttyS1"; //串口二
fd = OpenDev(dev);
set_speed(fd,19200);
if (set_Parity(fd,8,1,'N') == FALSE) {
printf("Set Parity Error\n");
exit (0);
}
while (1) //循环读取数据
{
while((nread = read(fd, buff, 512))>0)
{
printf("\nLen %d\n",nread);
buff[nread+1] = '\0';
printf( "\n%s", buff);
}
}
//close(fd);
// exit (0);
}
|
作者:未知 来源:未知 加入时间:2004-7-20 天新软件园
|
/*==========================*/ /*本程序由sunny编写,如有传载*/ /*请注明http://sunny1979.icpcn.com */ /*或者http://tchome.icpcn.com */ #include <dos.h> #include <bios.h> #include <stdio.h> #include <math.h> #include <conio.h> #include <graphics.h> #ifdef __cplusplus #define __CPPARGS ... #else #define __CPPARGS #endif #define SER_RBF 0 #define SER_THR 0 #define SER_IER 1 #define SER_IIR 2 #define SER_LCR 3 #define SER_MCR 4 #define SER_LSR 5 #define SER_MSR 6 #define SER_DLL 0 #define SER_DLH 1
#define SER_BAUD_1200 96 #define SER_BAUD_2400 48 #define SER_BAUD_9600 12 #define SER_BAUD_19200 6 #define SER_GP02 8 #define COM_1 0x3F8 #define COM_2 0x2F8 /*/ base port address of port 1*/ #define SER_STOP_1 0 /*/ 1 stop bit per character*/ #define SER_STOP_2 4 /*/ 2 stop bits per character*/ #define SER_BITS_5 0 /*/ send 5 bit characters*/ #define SER_BITS_6 1 /*/ send 6 bit characters*/ #define SER_BITS_7 2 /*/ send 7 bit characters*/ #define SER_BITS_8 3 /*/ send 8 bit characters*/ #define SER_PARITY_NONE 0 /*/ no parity*/ #define SER_PARITY_ODD 8 /*/ odd parity*/ #define SER_PARITY_EVEN 24 /*/ even parity*/ #define SER_DIV_LATCH_ON 128 /*/ used to turn reg 0,1 into divisor latch*/ #define PIC_IMR 0x21 /*/ pic's interrupt mask reg.*/ #define PIC_ICR 0x20 /*/ pic's interupt control reg.*/ #define INT_SER_PORT_0 0x0C /*/ port 0 interrupt com 1 & 3*/ #define INT_SER_PORT_1 0x0B /*/ port 0 interrupt com 2 & 4*/ #define SERIAL_BUFF_SIZE 128 /*/ current size of circulating receive buffer*/
void interrupt far (*Old_Isr)(__CPPARGS); /*/ holds old com port interrupt handler*/
char ser_buffer[SERIAL_BUFF_SIZE]; /*/ the receive buffer*/
int ser_end = -1,ser_start=-1; /*/ indexes into receive buffer*/ int ser_ch, char_ready=0; /*/ current character and ready flag*/ int old_int_mask; /*/ the old interrupt mask on the PIC*/ int open_port; /*/ the currently open port*/ int serial_lock = 0; /*/ serial ISR semaphore so the buffer*/ /*/ isn't altered will it is being written*/ /*/ to by the ISR*/
/*-------------写串口-----------------*/ void interrupt far Serial_Isr(__CPPARGS) { serial_lock = 1; ser_ch = inp(open_port + SER_RBF); if (++ser_end > SERIAL_BUFF_SIZE-1) ser_end = 0; ser_buffer[ser_end] = ser_ch;
++char_ready; outp(PIC_ICR,0x20); serial_lock = 0;
}
int Ready_Serial() { return(char_ready);
}
/*--------------读串口--------------*/
int Serial_Read() { int ch; while(serial_lock){} if (ser_end != ser_start) { if (++ser_start > SERIAL_BUFF_SIZE-1) ser_start = 0; ch = ser_buffer[ser_start]; if (char_ready > 0) --char_ready; return(ch);
} else return(0);
}
/*--------------写串口-----------------*/ Serial_Write(char ch) { while(!(inp(open_port + SER_LSR) & 0x20)){} asm cli outp(open_port + SER_THR, ch); asm sti }
/*-----------初始化串口---------------*/ Open_Serial(int port_base, int baud, int configuration) { open_port = port_base; outp(port_base + SER_LCR, SER_DIV_LATCH_ON); outp(port_base + SER_DLL, baud); outp(port_base + SER_DLH, 0); outp(port_base + SER_LCR, configuration); outp(port_base + SER_MCR, SER_GP02); outp(port_base + SER_IER, 1); if (port_base == COM_1) { Old_Isr = _dos_getvect(INT_SER_PORT_0); _dos_setvect(INT_SER_PORT_0, Serial_Isr); printf("\nOpening Communications Channel Com Port #1...\n");
} else { Old_Isr = _dos_getvect(INT_SER_PORT_1); _dos_setvect(INT_SER_PORT_1, Serial_Isr); printf("\nOpening Communications Channel Com Port #2...\n"); } old_int_mask = inp(PIC_IMR); outp(PIC_IMR, (port_base==COM_1) ? (old_int_mask & 0xEF) : (old_int_mask & 0xF7 )); } /*-------------关闭串口--------------*/ Close_Serial(int port_base) { outp(port_base + SER_MCR, 0); outp(port_base + SER_IER, 0); outp(PIC_IMR, old_int_mask ); if (port_base == COM_1) { _dos_setvect(INT_SER_PORT_0, Old_Isr); printf("\nClosing Communications Channel Com Port #1.\n"); } else { _dos_setvect(INT_SER_PORT_1, Old_Isr); printf("\nClosing Communications Channel Com Port #2.\n"); }
}
/*-------------发送应用----------------*/
void main(int argc,char *argv[]) {
char ch,press; int done=0; FILE *fp; argc=2; argv[1]="test.cpp"; if(argc<2) { printf("\nUsage:display filename.wav!!!"); exit(0); } if((fp=fopen(argv[1],"r+b"))==NULL) { printf("cannot open the file\n"); exit(0); } fseek(fp, 0, SEEK_SET); Open_Serial(COM_1,SER_BAUD_9600,SER_PARITY_NONE | SER_BITS_8 | SER_STOP_1); printf("press any key to begin sending"); getch(); Serial_Write(' '); while(!done&&ch != EOF) { ch = fgetc(fp); if(ch==EOF) Serial_Write(27); Serial_Write(ch); if (kbhit()) { press=getch(); if (press==27) { Serial_Write(27); done=1; } } } Close_Serial(COM_1); fclose(fp); }
|
一、基本知识
Win32下串口通信与16位串口通信有很大的区别。在Win32下,可以使用两种编程方式实现串口通信,其一是调用的Windows的API函数,其二是使用ActiveX控件。使用API 调用,可以清楚地掌握串口通信的机制,熟悉各种配置和自由灵活采用不同的流控进行串口通信。下面介绍串口操作的基本知识。
打开串口:使用CreateFile()函数,可以打开串口。有两种方法可以打开串口,一种是同步方式(NonOverlapped),另外一种异步方式(Overlapped)。使用Overlapped打开时,适当的方法是:
HANDLE hComm; hComm = CreateFile( gszPort, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0); if (hComm == INVALID_HANDLE_value) // error opening port; abort 配置串口:
1.DCB配置
DCB(Device Control Block)结构定义了串口通信设备的控制设置。许多重要设置都是在DCB结构中设置的,有三种方式可以初始化DCB。
(1)通过GetCommState()函数得DCB的初始值,其使用方式为:
DCB dcb = {0}; if (!GetCommState(hComm, &dcb)) // Error getting current DCB settings else // DCB is ready for use.
(2)用BuildCommDCB()函数初始化DCB结构,该函数填充 DCB的波特率、奇偶校验类型、数据位、停止位。对于流控成员函数设置了缺省值。其用法是:
DCB dcb; FillMemory(&dcb, sizeof(dcb), 0); dcb.DCBlength = sizeof(dcb); if (!BuildCommDCB(“9600,n,8,1", &dcb)) { // Couldn't build the DCB. Usually a problem // with the communications specification string. return FALSE; } else // DCB is ready for use.
(3)用SetCommState()函数手动设置DCB初值。用法如下:
DCB dcb; FillMemory(&dcb, sizeof(dcb), 0); if (!GetCommState(hComm, &dcb)) // get current DCB // Error in GetCommState return FALSE; // Update DCB rate. dcb.BaudRate = CBR_9600 ; // Set new state. if (!SetCommState(hComm, &dcb)) // Error in SetCommState. Possibly a problem with the communications // port handle or a problem with the DCB structure itself.
手动设置DCB值时,DCB的结构的各成员的含义,可以参看MSDN帮助。
2.流控设置
硬件流控:串口通信中的硬件流控有两种,DTE/DSR方式和RTS/CTS方式,这与DCB结构的初始化有关系,DCB结构中的OutxCtsFlow、 fOutxDsrFlow、fDsrSensitivity、fRtsControl、fDtrControl几个成员的初始值很关键,不同的值代表不同流控,也可以自己设置流控,但建议采用标准流行的流控方式。采用硬件流控时,DTE、DSR、RTS、CTS的逻辑位直接影响到数据的读写及收发数据的缓冲区控制。
软件流控:串口通信中采用特殊字符XON和XOFF作为控制串口数据的收发。与此相关的DCB成员是:fOut、fInX、XoffChar、XonChar、 XoffLim和XonLim。具体含义参见MSDN帮助。
串口读写操作:串口读写有两种方式:同步方式(NonOverlapped)和异步方式(Overlapped)。同步方式是指必须完成了读写操作,函数才返回,这可能造成程序死掉,因为如果在读写时发生了错误,永远不返回就会出错,可能线程将永远等待在那儿。而异步方式则灵活得多,一旦读写不成功,就将读写挂起,函数直接返回,可以通过GetLastError函数得知读写未成功的原因,所以常常采用异步方式操作。
读操作:ReadFile()函数用于完成读操作。异步方式的读操作为:
DWORD dwRead; BOOL fWaitingOnRead = FALSE; OVERLAPPED osReader = {0}; // Create the overlapped event. Must be closed before exiting // to avoid a handle leak. osReader.hEvent = CreateEvent (NULL, TRUE, FALSE, NULL); if (osReader.hEvent == NULL) // Error creating overlapped event; abort. if (!fWaitingOnRead) { // Issue read operation. if (!ReadFile(hComm, lpBuf, READ_BUF_SIZE, &dwRead, &osReader)) { if (GetLastError() != ERROR_IO_PENDING) // read not delayed? // Error in communications; report it. else fWaitingOnRead = TRUE; } else { // read completed immediately HandleASuccessfulRead(lpBuf, dwRead); } }
如果读操作被挂起,可以调用WaitForSingleObject()函数或WaitForMuntilpleObjects()函数等待读操作完成或者超时发生,再调用 GetOverlappedResult()得到想要的信息。
写操作:与读操作相似,故不详述,调用的API函数是: WriteFile函数。
串口状态:
(1)通信事件:用SetCommMask()函数设置想要得到的通信事件的掩码,再调用WaitCommEvent()函数检测通信事件的发生。可设置的通信事件标志(即SetCommMask()函数所设置的掩码)可以有EV_BREAK、EV_CTS、EV_DSR、 EV_ERR、EV_RING、EV_RLSD、EV_RXCHAR、EV_RXFLAG、EV_TXEMPTY。
注意:1对于EV_RING标志的设置,WIN95是不会返回EV_RING事件的,因为WIN95不检测该事件。2设置EV_RXCHAR,可以检测到字符到达,但是在绑定此事件和ReadFile()函数一起读取串口接收数据时,可能会出现错误,造成少读字节数,具体原因查看MSDN帮助。可以采用循环读的办法,另外一个比较好的解决办法是调用ClearCommError()函数,确定在一次读操作中在缓冲区中等待被读的字节数。
(2)错误处理和通信状态:在串口通信中,可能会产生很多的错误,使用ClearCommError()函数可以检测错误并且清除错误条件。
(3)Modem状态:用SetcommMask()可以包含很多事件标志,但是这些事件标志只指示在串口线路上的电压变化情况。而调用 GetCommModemStatus()函数可以获得线路上真正的电压状态。
扩展函数:如果应用程序想用自己的流控,可以使用 EscapeCommFunction()函数设置DTR和RTS线路的电平。
通信超时:在通信中,超时是个很重要的考虑因素,因为如果在数据接收过程中由于某种原因突然中断或停止,如果不采取超时控制机制,将会使得I/O线程被挂起或无限阻塞。串口通信中的超时设置分为两步,首先设置 COMMTIMEOUTS结构的五个变量,然后调用SetcommTimeouts()设置超时值。对于使用异步方式读写的操作,如果操作挂起后,异步成功完成了读写,WaitForSingleObject()或 WaitForMultipleObjects()函数将返回WAIT_OBJECT_0,GetOverlappedResult()返回TRUE。其实还可以用GetCommTimeouts()得到系统初始值。
关闭串口:程序结束或需要释放串口资源时,应该正确关闭串口,关闭串口比较简单,使用API调用CloseHandle()关闭串口的句柄就可以了。
调用方法为:CloseHandle(hComm);
但是值得注意的是在关闭串口之前必须保证读写串口线程已经退出,否则会引起误操作,一般采用的办法是使用事件驱动机制,启动一事件,通知串口读写线程强制退出,在线程退出之前,通知主线程可以关闭串口。
二、实现
1.程序设计思路
对于不同的应用程序,虽然界面不同,但是如果采用串口与主机之间的通信,对串口的处理方式大致相似,无非就是通过串口收发数据,对于通过串口接收到的数据,交给上层软件处理显示,对于上层要发给串口的数据,进行转发。但在实际编程中,由于采用的通信方式和流控不同,串口设置也不同,这就涉及到 DCB的初始化问题和读写串口等细节问题。串口通信应用程序设计的总体思路(即操作过程)是:首先,确定要打开的串口名、波特率、奇偶校验方式、数据位、停止位,传递给CreateFile()函数打开特定串口;其次,为了保护系统对串口的初始设置,调用 GetCommTimeouts()得到串口的原始超时设置;然后,初始化DCB对象,调用SetCommState() 设置DCB,调用SetCommTimeouts()设置串口超时控制;再次,调用SetupComm()设置串口接收发送数据的缓冲区大小,串口的设置就基本完成,之后就可以启动读写线程了。
一般来说,串口的读写由串口读写线程完成,这样可以避免读写阻塞时主程序死锁。对于全双工的串口读写,应该分别开启读线程和写线程;对于半双工和单工的,建议只需开启一个线程即可。在线程中,按照预定好的通信握手方式,正确检测串口状态,读取发送串口数据。
2.实现细节
在半双工的情况下,首先完成必要的串口配置,成功打开串口、DCB设置、超时设置;然后开启线程,如: CwinThread hSerialThread = (CWinThread*) AfxBeginThread(SerialOperation,hWnd,THREAD_PRIORITY_NORMAL); 其中开启之线程为SerialOperation,优先级为普通。
全双工情况下的串口编程,与单工差不多,区别仅仅在于启动双线程,分别为读线程和写线程,读线程根据不同的事件或消息,通过不断查询串口所收到的有效数据,完成读操作;写线程通过接收主线程的发送数据事件和要发送的数据,向串口发送。
本文详细介绍了串行通信的基本原理,以及在Windows NT、Win98环境下用MFC实现串口(COM)通信的方法:使用ActiveX控件或Win API.并给出用Visual C++6.0编写的相应MFC32位应用程序。关键词:串行通信、VC++6.0、ActiveX控件、Win API、MFC32位应用程序、事件驱动、非阻塞通信、多线程.
在Windows应用程序的开发中,我们常常需要面临与外围数据源设备通信的问题。计算机和单片机(如MCS-51)都具有串行通信口,可以设计相应的串口通信程序,完成二者之间的数据通信任务。
实际工作中利用串口完成通信任务的时候非常之多。已有一些文章介绍串口编程的文章在计算机杂志上发表。但总的感觉说来不太全面,特别是介绍32位下编程的更少,且很不详细。笔者在实际工作中积累了较多经验,结合硬件、软件,重点提及比较新的技术,及需要注意的要点作一番探讨。希望对各位需要编写串口通信程序的朋友有一些帮助。
一.串行通信的基本原理
串行端口的本质功能是作为CPU和串行设备间的编码转换器。当数据从 CPU经过串行端口发送出去时,字节数据转换为串行的位。在接收数据时,串行的位被转换为字节数据。
在Windows环境(Windows NT、Win98、Windows2000)下,串口是系统资源的一部分。
应用程序要使用串口进行通信,必须在使用之前向操作系统提出资源申请要求(打开串口),通信完成后必须释放资源(关闭串口)。
串口通信程序的流程如下图:
二.串口信号线的接法
一个完整的RS-232C接口有22根线,采用标准的25芯插头座(或者9芯插头座)。25芯和9芯的主要信号线相同。以下的介绍是以25芯的RS-232C为例。
①主要信号线定义:
2脚:发送数据TXD; 3脚:接收数据RXD; 4脚:请求发送RTS; 5脚:清除发送CTS;
6脚:数据设备就绪DSR;20脚:数据终端就绪DTR; 8脚:数据载波检测DCD;
1脚:保护地; 7脚:信号地。
②电气特性:
数据传输速率最大可到20K bps,最大距离仅15m.
注:看了微软的MSDN 6.0,其Windows API中关于串行通讯设备(不一定都是串口RS-232C或RS-422或RS-449)速率的设置,最大可支持到RS_256000,即256K bps! 也不知道到底是什么串行通讯设备?但不管怎样,一般主机和单片机的串口通讯大多都在9600 bps,可以满足通讯需求。
③接口的典型应用:
大多数计算机应用系统与智能单元之间只需使用3到5根信号线即可工作。这时,除了TXD、RXD以外,还需使用RTS、CTS、DCD、DTR、DSR等信号线。(当然,在程序中也需要对相应的信号线进行设置。)
图 最简单的RS232-C信号线接法
以上接法,在设计程序时,直接进行数据的接收和发送就可以了,不需要 对信号线的状态进行判断或设置。(如果应用的场合需要使用握手信号等,需要对相应的信号线的状态进行监测或设置。)
三.16位串口应用程序的简单回顾
16位串口应用程序中,使用的16位的Windows API通信函数:
① OpenComm() 打开串口资源,并指定输入、输出缓冲区的大小(以字节计);
CloseComm() 关闭串口;
例:int idComDev;
idComDev = OpenComm("COM1", 1024, 128);
CloseComm(idComDev);
② BuildCommDCB() 、setCommState()填写设备控制块DCB,然后对已打开的串口进行参数配置;
例:DCB dcb;
BuildCommDCB("COM1:2400,n,8,1", &dcb);
SetCommState(&dcb);
③ ReadComm 、WriteComm()对串口进行读写操作,即数据的接收和发送.
例:char *m_pRecieve; int count;
ReadComm(idComDev,m_pRecieve,count);
Char wr[30]; int count2;
WriteComm(idComDev,wr,count2);
16位下的串口通信程序最大的特点就在于:串口等外部设备的操作有自己特有的API函数;而32位程序则把串口操作(以及并口等)和文件操作统一起来了,使用类似的操作。
四.在MFC下的32位串口应用程序
32位下串口通信程序可以用两种方法实现:利用ActiveX控件;使用API 通信函数。
使用ActiveX控件,程序实现非常简单,结构清晰,缺点是欠灵活;使用API 通信函数的优缺点则基本上相反。
以下介绍的都是在单文档(SDI)应用程序中加入串口通信能力的程序。
㈠ 使用ActiveX控件:
VC++ 6.0提供的MSComm控件通过串行端口发送和接收数据,为应用程序提供串行通信功能。使用非常方便,但可惜的是,很少有介绍MSComm控件的资料。
⑴.在当前的Workspace中插入MSComm控件。
Project菜单------>Add to Project---->Components and Controls----->Registered
ActiveX Controls--->选择Components: Microsoft Communications Control,
version 6.0 插入到当前的Workspace中。
结果添加了类CMSComm(及相应文件:mscomm.h和mscomm.cpp )。
⑵.在MainFrm.h中加入MSComm控件。
protected:
CMSComm m_ComPort;
在Mainfrm.cpp::OnCreare()中:
DWORD style=WS_VISIBLE|WS_CHILD;
if (!m_ComPort.Create(NULL,style,CRect(0,0,0,0),this,ID_COMMCTRL)){
TRACE0("Failed to create OLE Communications Control\n");
return -1; // fail to create
}
⑶.初始化串口
m_ComPort.SetCommPort(1); //选择COM?
m_ComPort. SetInBufferSize(1024); //设置输入缓冲区的大小,Bytes
m_ComPort. SetOutBufferSize(512); //设置输入缓冲区的大小,Bytes//
if(!m_ComPort.GetPortOpen()) //打开串口
m_ComPort.SetPortOpen(TRUE);
m_ComPort.SetInputMode(1); //设置输入方式为二进制方式
m_ComPort.SetSettings("9600,n,8,1"); //设置波特率等参数
m_ComPort.SetRThreshold(1); //为1表示有一个字符引发一个事件
m_ComPort.SetInputLen(0);
⑷.捕捉串口事项。MSComm控件可以采用轮询或事件驱动的方法从端口获取数据。我们介绍比较使用的事件驱动方法:有事件(如接收到数据)时通知程序。在程序中需要捕获并处理这些通讯事件。
在MainFrm.h中:
protected:
afx_msg void OnCommMscomm();
DECLARE_EVENTSINK_MAP()
在MainFrm.cpp中:
BEGIN_EVENTSINK_MAP(CMainFrame,CFrameWnd )
ON_EVENT(CMainFrame,ID_COMMCTRL,1,OnCommMscomm,VTS_NONE)
//映射ActiveX控件事件
END_EVENTSINK_MAP()
⑸.串口读写. 完成读写的函数的确很简单,GetInput()和SetOutput()就可。两个函数的原型是:
VARIANT GetInput();及 void SetOutput(const VARIANT& newValue);都要使用VARIANT类型(所有Idispatch::Invoke的参数和返回值在内部都是作为VARIANT对象处理的)。
无论是在PC机读取上传数据时还是在PC机发送下行命令时,我们都习惯于使用字符串的形式(也可以说是数组形式)。查阅VARIANT文档知道,可以用BSTR表示字符串,但遗憾的是所有的BSTR都是包含宽字符,即使我们没有定义_UNICODE_UNICODE也是这样! WinNT支持宽字符, 而Win95并不支持。为解决上述问题,我们在实际工作中使用CbyteArray,给出相应的部分程序如下:
void CMainFrame::OnCommMscomm(){
VARIANT vResponse; int k;
if(m_commCtrl.GetCommEvent()==2) {
k=m_commCtrl.GetInBufferCount(); //接收到的字符数目
if(k>0) {
vResponse=m_commCtrl.GetInput(); //read
SaveData(k,(unsigned char*) vResponse.parray->pvData);
} // 接收到字符,MSComm控件发送事件 }
。。。。。 // 处理其他MSComm控件
}
void CMainFrame::OnCommSend() {
。。。。。。。。 // 准备需要发送的命令,放在TxData[]中
CByteArray array;
array.RemoveAll();
array.SetSize(Count);
for(i=0;i<Count;i++)
array.SetAt(i, TxData[i]);
m_ComPort.SetOutput(COleVariant(array)); // 发送数据
}
请大家认真关注第⑷、⑸中内容,在实际工作中是重点、难点所在。
㈡ 使用32位的API 通信函数:
可能很多朋友会觉得奇怪:用32位API函数编写串口通信程序,不就是把16位的API换成32位吗?16位的串口通信程序可是多年之前就有很多人研讨过了……
此文主要想介绍一下在API串口通信中如何结合非阻塞通信、多线程等手段,编写出高质量的通信程序。特别是在CPU处理任务比较繁重、与外围设备中有大量的通信数据时,更有实际意义。
⑴.在中MainFrm.cpp定义全局变量
HANDLE hCom; // 准备打开的串口的句柄
HANDLE hCommWatchThread ;//辅助线程的全局函数
⑵.打开串口,设置串口
hCom =CreateFile( "COM2", GENERIC_READ | GENERIC_WRITE, // 允许读写
0, // 此项必须为0
NULL, // no security attrs
OPEN_EXISTING, //设置产生方式
FILE_FLAG_OVERLAPPED, // 我们准备使用异步通信
NULL );
请大家注意,我们使用了FILE_FLAG_OVERLAPPED结构。这正是使用API实现非阻塞通信的关键所在。
ASSERT(hCom!=INVALID_HANDLE_VALUE); //检测打开串口操作是否成功
SetCommMask(hCom, EV_RXCHAR|EV_TXEMPTY );//设置事件驱动的类型
SetupComm( hCom, 1024,512) ; //设置输入、输出缓冲区的大小
PurgeComm( hCom, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR
| PURGE_RXCLEAR ); //清干净输入、输出缓冲区
COMMTIMEOUTS CommTimeOuts ; //定义超时结构,并填写该结构
…………
SetCommTimeouts( hCom, &CommTimeOuts ) ;//设置读写操作所允许的超时
DCB dcb ; // 定义数据控制块结构
GetCommState(hCom, &dcb ) ; //读串口原来的参数设置
dcb.BaudRate =9600; dcb.ByteSize =8; dcb.Parity = NOPARITY;
dcb.StopBits = ONESTOPBIT ;dcb.fBinary = TRUE ;dcb.fParity = FALSE;
SetCommState(hCom, &dcb ) ; //串口参数配置
上述的COMMTIMEOUTS结构和DCB都很重要,实际工作中需要仔细选择参数。
⑶启动一个辅助线程,用于串口事件的处理。
Windows提供了两种线程,辅助线程和用户界面线程。区别在于:辅助线程没有窗口,所以它没有自己的消息循环。但是辅助线程很容易编程,通常也很有用。
在次,我们使用辅助线程。主要用它来监视串口状态,看有无数据到达、通信有无错误;而主线程则可专心进行数据处理、提供友好的用户界面等重要的工作。
hCommWatchThread=
CreateThread( (LPSECURITY_ATTRIBUTES) NULL, //安全属性
0,//初始化线程栈的大小,缺省为与主线程大小相同
(LPTHREAD_START_ROUTINE)CommWatchProc, //线程的全局函数
GetSafeHwnd(), //此处传入了主框架的句柄
0, &dwThreadID );
ASSERT(hCommWatchThread!=NULL);
⑷为辅助线程写一个全局函数,主要完成数据接收的工作。请注意OVERLAPPED结构的使用,以及怎样实现了非阻塞通信。
UINT CommWatchProc(HWND hSendWnd){
DWORD dwEvtMask=0 ;
SetCommMask( hCom, EV_RXCHAR|EV_TXEMPTY );//有哪些串口事件需要监视?
WaitCommEvent( hCom, &dwEvtMask, os );// 等待串口通信事件的发生
检测返回的dwEvtMask,知道发生了什么串口事件:
if ((dwEvtMask & EV_RXCHAR) == EV_RXCHAR){ // 缓冲区中有数据到达
COMSTAT ComStat ; DWORD dwLength;
ClearCommError(hCom, &dwErrorFlags, &ComStat ) ;
dwLength = ComStat.cbInQue ; //输入缓冲区有多少数据?
if (dwLength > 0) {
BOOL fReadStat ;
fReadStat = ReadFile( hCom, lpBuffer,dwLength, &dwBytesRead,
&READ_OS( npTTYInfo ) ); //读数据
注:我们在CreareFile()时使用了FILE_FLAG_OVERLAPPED,现在ReadFile()也必须使用
LPOVERLAPPED结构.否则,函数会不正确地报告读操作已完成了.
使用LPOVERLAPPED结构, ReadFile()立即返回,不必等待读操作完成,实现非阻塞
通信.此时, ReadFile()返回FALSE, GetLastError()返回ERROR_IO_PENDING.
if (!fReadStat){
if (GetLastError() == ERROR_IO_PENDING){
while(!GetOverlappedResult(hCom,
&READ_OS( npTTYInfo ), & dwBytesRead, TRUE )){
dwError = GetLastError();
if(dwError == ERROR_IO_INCOMPLETE) continue;
//缓冲区数据没有读完,继续
…… ……
::PostMessage((HWND)hSendWnd,WM_NOTIFYPROCESS,0,0);//通知主线程,串口收到数据 }
所谓的非阻塞通信,也即异步通信。是指在进行需要花费大量时间的数据读写操作(不仅仅是指串行通信操作)时,一旦调用ReadFile()、WriteFile(), 就能立即返回,而让实际的读写操作在后台运行;相反,如使用阻塞通信,则必须在读或写操作全部完成后才能返回。由于操作可能需要任意长的时间才能完成,于是问题就出现了。
非常阻塞操作还允许读、写操作能同时进行(即重叠操作?),在实际工作中非常有用。
要使用非阻塞通信,首先在CreateFile()时必须使用FILE_FLAG_OVERLAPPED;然后在 ReadFile()时lpOverlapped参数一定不能为NULL,接着检查函数调用的返回值,调用GetLastError(),看是否返回ERROR_IO_PENDING。如是,最后调用GetOverlappedResult()返回重叠操作(overlapped operation)的结果;WriteFile()的使用类似。
⑸.在主线程中发送下行命令。
BOOL fWriteStat ; char szBuffer[count];
…………//准备好发送的数据,放在szBuffer[]中
fWriteStat = WriteFile(hCom, szBuffer, dwBytesToWrite,
&dwBytesWritten, &WRITE_OS( npTTYInfo ) ); //写数据
注:我们在CreareFile()时使用了FILE_FLAG_OVERLAPPED,现在WriteFile()也必须使用 LPOVERLAPPED结构.否则,函数会不正确地报告写操作已完成了.
使用LPOVERLAPPED结构,WriteFile()立即返回,不必等待写操作完成,实现非阻塞 通信.此时, WriteFile()返回FALSE, GetLastError()返回ERROR_IO_PENDING.
int err=GetLastError();
if (!fWriteStat) {
if(GetLastError() == ERROR_IO_PENDING){
while(!GetOverlappedResult(hCom, &WRITE_OS( npTTYInfo ),
&dwBytesWritten, TRUE )) {
dwError = GetLastError();
if(dwError == ERROR_IO_INCOMPLETE){
// normal result if not finished
dwBytesSent += dwBytesWritten; continue; }
......................
综上,我们使用了多线程技术,在辅助线程中监视串口,有数据到达时依靠事件驱动,读入数据并向主线程报告(发送数据在主线程中,相对说来,下行命令的数据总是少得多);并且,WaitCommEvent()、ReadFile()、WriteFile()都使用了非阻塞通信技术,依靠重叠(overlapped)读写操作,让串口读写操作在后台运行。
依托vc6.0丰富的功能,结合我们提及的技术,写出有强大控制能力的串口通信应用程序。就个人而言,我更偏爱API技术,因为控制手段要灵活的多,功能也要强大得多。
|