毕业三年后想回青岛,空气好、房价可以接受、研发岗位待遇还不错、户口好解决、消费中等、离家近等等众多因素。感觉最想回青岛。
但是了解了一下青岛的研发大环境:适合的公司主要就是:海尔、海信、中车、鼎信通讯、青岛百灵等
下面收集了一些最想去的鼎信通讯公司的招聘信息,留着以后提醒自己,有个参考
(最好学习C/C++)-------------这个方向最适合
青岛鼎信通讯股份有限公司
2016年招聘简章
青岛鼎信通讯股份有限公司成立于2008年4月,于2012年7月进行股份制改革,注册资本人民币3.9亿元。公司已于2014年5月5日在证监会作IPO预披露,申请上海主板上市。公司是一家集研发、生产、销售为一体的具有自主知识产权的高新技术企业。
青岛被评为全球宜居城市之一,是中国重要的经济中心城市和沿海开放城市,是国家级历史文化名城和风景旅游、度假胜地, 处于山东蓝色半岛经济区的核心位置,下辖与上海浦东平级的第9个国家级新区-西海岸新区。
公司总部位于青岛市核心区域市南区青岛软件园,园内设有6000余平方米的研发中心,在城阳区拥有20000余平方米的生产基地,同时正在建设位于青岛高新区内的35万平米的鼎信高新科技产业园区。2009年9月通过ISO9001:2008质量管理体系认证,2012年通过ISO14001:2008环境体系认证。2012年被青岛市认定为创新型企业。2014年被认定为国家火炬计划重点高新技术企业,拥有CNAS认证实验室一个。公司现有27项专利、32项软件著作权。
公司以载波及总线通信芯片的研发成果为基础,将其应用于电力、消防等嵌入式智能产品的研发、生产、销售,成为一个有扎实基础理论与系统技术的高新技术企业。初步建成了以IC设计、嵌入式软件、自动化生产及自动化设计为基础的企业集团。青岛鼎信通讯股份有限公司下属青岛鼎信通讯电力工程有限公司、青岛鼎信通讯消防安全有限公司、青岛鼎信通讯智能装备有限公司、青岛鼎信通讯科技有限公司、上海鼎信通讯集成电路设计有限公司、青岛鼎信通讯股份有限公司西安分公司、青岛鼎信通讯电力工程有限公司重庆分公司、四川分公司、河北分公司、湖南分公司等子公司和分公司,并在全国31个省、自治区、直辖市设立了办事处。
青岛鼎信通讯股份有限公司致力于电力线载波通信芯片、总线通信芯片的研发及智能电网、消防安防、智能家居等方面的产品应用推广,在扩频通信、信号处理、自动控制、电能计量、电网终端采集、变配电技术、电能质量控制、新能源、计算机应用及机电一体化等领域具有较强的科研、生产能力。根据国家电网数据信息,青岛鼎信通讯的电力线载波采集系列产品,凭借产品的优异性能,在国网供应商中,市场占有率达40%,处于电力线载波芯片行业的领先地位。目前公司有成体系的自动抄表系统,四合一采集控制终端,家用电智能显示终端等系列产品,并在国内外有较好应用(如以色列国家电网、俄罗斯国家电网、南非国家电网等)。自2008年成立至今,公司取得大量的技术积累和不俗的经营业绩,截止到2015年底,公司总营业额累计达36.85亿元。
青岛鼎信通讯股份有限公司成立以来一直遵循“诚信务实、追求卓越、以人为本”的基本原则,同时努力营造良好的沟通环境,为员工提供一个平等、友爱、合作和不断进步的工作氛围。截止2016年2月,公司已有员工2800余人,其中研发人员500余人。在未来,公司将打造千人规模的精英研发团队。
公司为员工提供舒适的工作环境,完善的就职培训体系,良好的福利待遇,有竞争力的薪酬。公司虚位以待,诚挚欢迎您选择鼎信,和鼎信共同成长,并以鼎信为平台实现人生价值。
招聘对象:
招聘专业:
招聘岗位:
薪酬及福利待遇:
招聘流程:
简历投递方式:[email protected]
公司网址:www.topscomm.com
青岛鼎信通笔试题2016春季
主要考查的知识点:
编程语言
数据库
数组排序
查找
面向对象
网络编程中的多线程与多进程
前几天和朋友聊天,朋友问我怎么最近不写博客了,一个是因为最近在忙着公司使用的一些控件的开发,浏览器兼容性搞死人;但主要是因为这段时间一直在看html5的东西,看到web socket时觉得很有意思,动手写几个demo,但web socket需要特定的服务器支持,由于标准制定工作还没完成,所以没有多少主流的服务器支持,自己在网上下载了几个实现,包括PHP的、C#的、甚至Node.js的,但一个是协议变化比较大,很多代码已经过时了,再就是有一些支持最新的标准,但是我想稍微改造一下,看人家源代码的时候云里雾里,看看别人的代码行数也不多,决定自己实现一个。
悲剧由此开始,虽然哥们儿国内非知名工科大学毕业,但好歹也是科班CS出身,但大学得过且过,什么TCP/IP协议,什么socket了都没概念。为了做出一个简单的支持广播的websocket server,在网上找了很多相关代码,左抄一句,右抄一句,弄了一个星期竟然还是漏洞百出,调试不起来,只好从头来过了,先补一些基本知识,然后再一步步根据原理实现,今天终于实现了绝大部分功能,由此真的感受到了,搞计算机必须得有理论指导实践,否则只能像个没头苍蝇到处乱撞。
要想理解socket首先得熟悉一下TCP/IP协议族, TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,定义了主机如何连入因特网及数据如何再它们之间传输的标准,
从字面意思来看TCP/IP是TCP和IP协议的合称,但实际上TCP/IP协议是指因特网整个TCP/IP协议族。不同于ISO模型的七个分层,TCP/IP协议参考模型把所有的TCP/IP系列协议归类到四个抽象层中
应用层:TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet等等
传输层:TCP,UDP
网络层:IP,ICMP,OSPF,EIGRP,IGMP
数据链路层:SLIP,CSLIP,PPP,MTU
每一抽象层建立在低一层提供的服务上,并且为高一层提供服务,看起来大概是这样子的
估计有兴趣打开此文的同学都对此有一定了解了,加上我也是一知半解,所以就不详细解释,有兴趣同学可以上网上搜一下资料
在TCP/IP协议中两个因特网主机通过两个路由器和对应的层连接。各主机上的应用通过一些数据通道相互执行读取操作
我们知道两个进程如果需要进行通讯最基本的一个前提能能够唯一的标示一个进程,在本地进程通讯中我们可以使用PID来唯一标示一个进程,但PID只在本地唯一,网络中的两个进程PID冲突几率很大,这时候我们需要另辟它径了,我们知道IP层的ip地址可以唯一标示主机,而TCP层协议和端口号可以唯一标示主机的一个进程,这样我们可以利用ip地址+协议+端口号唯一标示网络中的一个进程。
能够唯一标示网络中的进程后,它们就可以利用socket进行通信了,什么是socket呢?我们经常把socket翻译为套接字,socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信。
socket起源于UNIX,在Unix一切皆文件哲学的思想下,socket是一种"打开—读/写—关闭"模式的实现,服务器和客户端各自维护一个"文件",在建立连接打开后,可以向自己文件写入内容供对方读取或者读取对方内容,通讯结束时关闭文件。
socket是"打开—读/写—关闭"模式的实现,以使用TCP协议通讯的socket为例,其交互流程大概是这样子的
服务器根据地址类型(ipv4,ipv6)、socket类型、协议创建socket
服务器为socket绑定ip地址和端口号
服务器socket监听端口号请求,随时准备接收客户端发来的连接,这时候服务器的socket并没有被打开
客户端创建socket
客户端打开socket,根据服务器ip地址和端口号试图连接服务器socket
服务器socket接收到客户端socket请求,被动打开,开始接收客户端请求,直到客户端返回连接信息。这时候socket进入阻塞状态,所谓阻塞即accept()方法一直到客户端返回连接信息后才返回,开始接收下一个客户端谅解请求
客户端连接成功,向服务器发送连接状态信息
服务器accept方法返回,连接成功
客户端向socket写入信息
服务器读取信息
客户端关闭
服务器端关闭
在TCP/IP协议中,TCP协议通过三次握手建立一个可靠的连接
第一次握手:客户端尝试连接服务器,向服务器发送syn包(同步序列编号Synchronize Sequence Numbers),syn=j,客户端进入SYN_SEND状态等待服务器确认
第二次握手:服务器接收客户端syn包并确认(ack=j+1),同时向客户端发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态
第三次握手:第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手
定睛一看,服务器socket与客户端socket建立连接的部分其实就是大名鼎鼎的三次握手
为了建立冗余较小、结构合理的数据库,设计数据库时必须遵循一定的规则。在关系型数据库中这种规则就称为范式。范式是符合某一种设计要求的总结。要想设计一个结构合理的关系型数据库,必须满足一定的范式。
在实际开发中最为常见的设计范式有三个:
1.第一范式(确保每列保持原子性)
第一范式是最基本的范式。如果数据库表中的所有字段值都是不可分解的原子值,就说明该数据库表满足了第一范式。
第一范式的合理遵循需要根据系统的实际需求来定。比如某些数据库系统中需要用到“地址”这个属性,本来直接将“地址”属性设计成一个数据库表的字段就行。但是如果系统经常会访问“地址”属性中的“城市”部分,那么就非要将“地址”这个属性重新拆分为省份、城市、详细地址等多个部分进行存储,这样在对地址中某一部分操作的时候将非常方便。这样设计才算满足了数据库的第一范式,如下表所示。
上表所示的用户信息遵循了第一范式的要求,这样在对用户使用城市进行分类的时候就非常方便,也提高了数据库的性能。
2.第二范式(确保表中的每列都和主键相关)
第二范式在第一范式的基础之上更进一层。第二范式需要确保数据库表中的每一列都和主键相关,而不能只与主键的某一部分相关(主要针对联合主键而言)。也就是说在一个数据库表中,一个表中只能保存一种数据,不可以把多种数据保存在同一张数据库表中。
比如要设计一个订单信息表,因为订单中可能会有多种商品,所以要将订单编号和商品编号作为数据库表的联合主键,如下表所示。
订单信息表
这样就产生一个问题:这个表中是以订单编号和商品编号作为联合主键。这样在该表中商品名称、单位、商品价格等信息不与该表的主键相关,而仅仅是与商品编号相关。所以在这里违反了第二范式的设计原则。
而如果把这个订单信息表进行拆分,把商品信息分离到另一个表中,把订单项目表也分离到另一个表中,就非常完美了。如下所示。
这样设计,在很大程度上减小了数据库的冗余。如果要获取订单的商品信息,使用商品编号到商品信息表中查询即可。
3.第三范式(确保每列都和主键列直接相关,而不是间接相关)
第三范式需要确保数据表中的每一列数据都和主键直接相关,而不能间接相关。
比如在设计一个订单数据表的时候,可以将客户编号作为一个外键和订单表建立相应的关系。而不可以在订单表中添加关于客户其它信息(比如姓名、所属公司等)的字段。如下面这两个表所示的设计就是一个满足第三范式的数据库表。
这样在查询订单信息的时候,就可以使用客户编号来引用客户信息表中的记录,也不必在订单信息表中多次输入客户信息的内容,减小了数据冗余。
C语言中讲讲static变量和static函数有什么作用
static关键字有两种意思,你看上下文来判断
1,表示变量是静态存储变量
表示变量存放在静态存储区.
2,表示该变量是内部连接
(这种情况是指该变量不在任何{}之内,就象全局变量那样,这时候加上static)
,也就是说在其它的.cpp文件中,该变量是不可见的(你不能用).
当static加在函数前面的时候
表示该函数是内部连接,之在本文件中有效,别的文件中不能应用该函数.
不加static的函数默认为是全局的.
也就是说在其他的.cpp中只要申明一下这个函数,就可以使用它.
1、static全局变量与普通的全局变量有什么区别?static局部变量和普通局部变量有什么区别?static函数与普通函数有什么区别?
答:全局变量(外部变量)的说明之前再冠以static就构成了静态的全局变量。全局变量本身就是静态存储方式,静态全局变量当然也是静态存储方式。这两者在存储方式上并无不同。这两者的区别虽在于非静态全局变量的作用域是整个源程序,当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。而静态全局变量则限制了其作用域,即只在定义该变量的源文件内有效,在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其它源文件中引起错误。
从以上分析可以看出,把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域,限制了它的使用范围。
static函数与普通函数作用域不同。static函数仅在本文件中使用。只在当前源文件中使用的函数应该说明为内部函数(static),内部函数应该在当前源文件中说明和定义。对于可在当前源文件以外使用的函数,应该在一个头文件中说明,要使用这些函数的源文件要包含这个头文件
static全局变量与普通的全局变量有什么区别:static全局变量只初使化一次,防止在其他文件单元中被引用;
static局部变量和普通局部变量有什么区别:static局部变量只被初始化一次,下一次依据上一次结果值;
static函数与普通函数有什么区别:static函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝
析构函数也是特殊的类成员函数,它没有返回类型,没有参数,不能随意调用,也没有重载。只是在类对象生命期结束的时候,由系统自动调用释放在构造函数中分配的资源。这种在运行时,能依据其类型确认调用那个函数的能力称为多态性,或称迟后联编。另: 析构函数一般在对象撤消前做收尾工作,比如回收内存等工作,
虚拟函数的功能是使子类可以用同名的函数对父类函数进行覆盖,并且在调用时自动调用子类覆盖函数,如果是纯虚函数,则纯粹是为了在子类覆盖时有个统一的命名而已。
注意:子类重新定义父类的虚函数的做法叫覆盖,override,而不是overload(重载),重载的概念不属于面向对象编程,重载指的是存在多个同名函数,这些函数的参数表不同..重载是在编译期间就决定了的,是静态的,因此,重载与多态无关.与面向对象编程无关.
阅读 评论(1) 收藏 举报
面向对象的三个基本特征是:封装、继承、多态。
封装
封装最好理解了。封装是面向对象的特征之一,是对象和类概念的主要特性。
封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
继承
面向对象编程 (OOP)语言的一个主要功能就是“继承”。继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。
通过继承创建的新类称为“子类”或“派生类”。
被继承的类称为“基类”、“父类”或“超类”。
继承的过程,就是从一般到特殊的过程。
要实现继承,可以通过“继承”(Inheritance)和“组合”(Composition)来实现。
在某些 OOP语言中,一个子类可以继承多个基类。但是一般情况下,一个子类只能有一个基类,要实现多重继承,可以通过多级继承来实现。
继承概念的实现方式有三类:实现继承、接口继承和可视继承。
Ø 实现继承是指使用基类的属性和方法而无需额外编码的能力;
Ø 接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力;
Ø 可视继承是指子窗体(类)使用基窗体(类)的外观和实现代码的能力。
在考虑使用继承时,有一点需要注意,那就是两个类之间的关系应该是“属于”关系。例如,Employee是一个人,Manager也是一个人,因此这两个类都可以继承 Person类。但是 Leg类却不能继承 Person类,因为腿并不是一个人。
抽象类仅定义将由子类创建的一般属性和方法,创建抽象类时,请使用关键字 Interface 而不是 Class。
OO开发范式大致为:划分对象→抽象类→将类组织成为层次化结构(继承和合成) →用类与实例进行设计和实现几个阶段。
多态
多态性(polymorphisn)是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。
实现多态,有二种方式,覆盖,重载。
覆盖,是指子类重新定义父类的虚函数的做法。
重载,是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。
其实,重载的概念并不属于“面向对象编程”,重载的实现是:编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数(至少对于编译器来说是这样的)。如,有两个同名函数:function func(p:integer):integer;和function func(p:string):integer;。那么编译器做过修饰后的函数名称可能是这样的:int_func、str_func。对于这两个函数的调用,在编译器间就已经确定了,是静态的(记住:是静态)。也就是说,它们的地址在编译期就绑定了(早绑定),因此,重载和多态无关!真正和多态相关的是“覆盖”。当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态(记住:是动态!)的调用属于子类的该函数,这样的函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出)。因此,这样的函数地址是在运行期绑定的(晚邦定)。结论就是:重载只是一种语言特性,与多态无关,与面向对象也无关!引用一句Bruce Eckel的话:“不要犯傻,如果它不是晚邦定,它就不是多态。”
那么,多态的作用是什么呢?我们知道,封装可以隐藏实现细节,使得代码模块化;继承可以扩展已存在的代码模块(类);它们的目的都是为了——代码重用。而多态则是为了实现另一个目的——接口重用!多态的作用,就是为了类在继承和派生的时候,保证使用“家谱”中任一类的实例的某一属性时的正确调用。
标签: 服务器bufferstructserversocket编程
2011-07-0610:50 2495人阅读 评论(0) 收藏 举报
分类:
C语言(13)
版权声明:本文为博主原创文章,未经博主允许不得转载。
目录(?)[+]
并发服务器是socket应用编程中最常见的应用模型。并发服务器模型根据连接方式分为长连接和短连接,长连接为通信双方建立连接后一直保持连接,然后一直用此连接进行读写操作;短连接为通信双方每一次交易过程都建立连接和关闭连接。并发服务器模型根据处理方式可分为同步方式和异步方式,同步是客户端发送请求给服务器等待服务器返回处理结果;异步是指客户端发送请求给服务器,不等待服务器返回处理结果,而直接去完成其他的流程,对于处理结果客户端可以事后查询和让服务器进行主动通知。
进程是一个程序的一次运行过程,它是一个动态实体,是独立的任务,它拥有独立的地址空间、执行堆栈、文件描述符等。每个进程拥有独立的地址空间,在进程不存在父子关系的情况下,互不影响。
进程的终止存在两个可能:父进程先于子进程终止(由init进程领养),子进程先于主进程终止。对于后者,系统内核为子进程保留一定的状态信息(进程ID、终止状态、CPU时间等),并向其父进程发送SIGCHLD信号。当父进程调用wait或waitpid函数时,将获取这些信息,获取后内核将对僵尸进程进行清理。如果父进程设置了忽略SIGCHLD信号或对SIGCHLD信号提供了处理函数,即使不调用wait或waitpid函数内核也会清理僵尸进程。
但父进程调用wait函数处理子进程退出信息时,会存在下面所述的问题。在有多个子进程情况下,wait函数只等待最先到达的子进程的终止信息。下图18-7父进程有3个子进程,由于SIGCHLD信号不排队,在SIGCHLD信号同时到来后,父进程的wait函数只执行一次,这样将留下2个僵尸进程,而使用waitpid函数并设置WNOHANG选项可以解决这个问题。
图18-7 多进程信号图
综上所述,在多进程并发的情况下,防止子进程变成僵尸进程常见有如下两种方法:
① 父进程调用signal(SIGCHLD,SIG_IGN)对子进程退出信号进行忽略,或者把SIG_IGN替换为其他处理函数,设置对SIGCHLD信号的处理。
② 父进程调用waitpid(-1,NULL,WNOHANG)对所有子进程SIGCHLD信号进行处理。
图18-8~图18-11画出了并发服务器文件描述符的变化流程图。其中listenfd为服务端的socket监听文件描述符,connfd为accept函数返回的socket连接文件描述符。
服务器调用accept函数后,客户与服务器文件描述符如下图18-8所示。
图18-8 调用accept函数时套接字描述符图
服务器调用accept函数后,客户与服务器文件描述符如下图18-9所示。
图18-9调用accept函数后套接字描述符图
服务器调用fork函数后,客户与服务器文件描述符如下图18-10所示。
图18-10调用fork函数后套接字描述符图
服务端父进程关闭连接套接字,子进程关闭监听套接字,客户与服务器文件描述符状况如下图18-11所示。
图18-11 并发服务器最终连接图
在这里强调的是,并发服务器fork后父进程一定要关闭子进程连接套接字;而子进程要关闭父进程监听套接字,以免误操作。
(1)并发服务器处理流程
并发服务器处理流程如下:
① 客户端首先发起连接。
② 服务端进程accept打开一个新的连接套接字与客户端进行连接,accept在一个while(1)循环内等待客户端的连接。
③ 服务端fork一个子进程,同时父进程close子进程连接套接字,循环等待下一进程。
④ 服务端子进程close父进程监听套接字,并用连接套接字保持与客户端的连接,客户发送数据到服务端,然后阻塞等待服务端返回。
⑤ 子进程进行接收数据,进行业务处理,然后再发送数据给客户端。
⑥ 子进程关闭连接,然后退出。
(2)程序报文协议说明
该程序报文协议模式为常见行业应用软件协议模式,其具体说明如下:
发送和接收报文协议:8位报文长度(不包含本身)+6位交易码+报文内容,交易码标识该交易的类型。
实际应用中服务端进程根据6位交易码调度不同的应用服务。
(3)并发服务器服务端代码
tcpsrv.c源代码如下:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#defineMY_PORT 10000
externint readn(int fd,void *buffer,int length) ;
externint writen(int fd,void *buffer,int length) ;
intmain(int argc ,char **argv)
{
int listen_fd,accept_fd;
struct sockaddr_in server_addr;
struct sockaddr_in cli_addr;
int n;
int cliaddr_len ;
char buffer[1024];
char data[1024] ;
long length ;
int nbytes ;
if((listen_fd=socket(AF_INET,SOCK_STREAM,0))<0)
{
printf("Socket Error:%s\n",strerror(errno));
return -1;
}
memset(&server_addr,0x00, sizeof(struct sockaddr_in));
memset(&cli_addr,0x00, sizeof(struct sockaddr_in));
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons(MY_PORT);
server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
n=1;
/* 如果服务器终止后,服务器可以第二次快速启动而不用等待一段时间*/
setsockopt(listen_fd,SOL_SOCKET,SO_REUSEADDR,&n,sizeof(int));
if(bind(listen_fd,(struct sockaddr *)&server_addr,sizeof(server_addr))<0)
{
printf("Bind Error:%s\n",strerror(errno));
return -1;
}
listen(listen_fd,5);
while(1)
{
cliaddr_len= sizeof( cli_addr ) ;
accept_fd=accept(listen_fd, (struct sockaddr *)&cli_addr, &cliaddr_len);
if((accept_fd<0)&&(errno==EINTR))
continue;
else if(accept_fd<0)
{
printf("Accept Error:%s\n",strerror(errno));
continue;
}
if((n=fork())==0)
{
/* 子进程处理客户端的连接*/
fprintf(stdout,"listen_fd:%d accept_fd:%d\n",listen_fd, accept_fd);
close(listen_fd);
memset(buffer, 0x00, sizeof(buffer)) ;
memset(data, 0x00, sizeof(data));
if((nbytes=readn(accept_fd, data, 8 ))==-1)
{
fprintf(stderr,"Read Error:%s\n",strerror(errno));
fprintf(stderr,"data:%s\n",data );
close(accept_fd);
return -1;
}
fprintf(stdout,"data:%s,nbytes=%d\n",data, nbytes );
data[nbytes]='\0' ;
length=atol(data) ;
fprintf(stdout,"data:%s,nbytes=%d\n",data, nbytes );
if((nbytes=readn(accept_fd, data, length ))==-1)
{
fprintf(stderr,"Read Error:%s\n",strerror(errno));
close(accept_fd);
return -1;
}
data[nbytes]='\0' ;
fprintf(stdout,"data:%s,nbytes=%d\n",data, nbytes );
if( strncmp(data, "000000", 6 )==0 )
{
strcpy(buffer, "I am sorry! who am I? I don't know also.") ;
length=strlen(buffer) ;
sprintf(data,"%08ld%6.6s%s", (length+6),"000000", buffer );
if((nbytes=writen(accept_fd,data, (length+6+8)))==-1)
{
fprintf(stderr,"Read Error:%s\n",strerror(errno));
close(accept_fd);
return -1;
}
fprintf(stdout,"data:%s\n",data );
}else{
/*非000000交易请求为非法,沉默是最好的回答*/
}
close(accept_fd);
return 0;
}
else if(n<0)
printf("Fork Error:%s\n\a",strerror(errno));
close(accept_fd);
while(waitpid(-1,NULL,WNOHANG) > 0); /* clean up child processes */
}
}
(4)客户端代码
tcpcli.c源代码如下:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
externint readn(int fd,void *buffer,int length) ;
externint writen(int fd,void *buffer,int length) ;
intmain(int argc, char *argv[])
{
int sockfd;
char buffer[1024];
char data[1024];
long length ;
struct sockaddr_in server_addr;
struct hostent *host;
int portnumber,nbytes;
if(argc!=3)
{
fprintf(stderr,"Usage:%s hostname portnumber\a\n",argv[0]);
return -1;
}
if((host=gethostbyname(argv[1]))==NULL)
{
fprintf(stderr,"Gethostname error\n");
return -1;
}
if((portnumber=atoi(argv[2]))<0)
{
fprintf(stderr,"Usage:%s hostname portnumber\a\n",argv[0]);
return -1;
}
/* 客户程序开始建立sockfd描述符*/
if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)
{
fprintf(stderr,"Socket Error:%s\a\n",strerror(errno));
return -1;
}
/* 客户程序填充服务端的地址端口信息*/
bzero(&server_addr,sizeof(server_addr));
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons(portnumber);
server_addr.sin_addr= *((struct in_addr *)host->h_addr);
/* 客户程序发起连接请求*/
if(connect(sockfd,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr)
)==-1)
{
fprintf(stderr,"Connect Error:%s\a\n",strerror(errno));
return -1;
}
memset(buffer, 0x00, sizeof(buffer)) ;
memset(data, 0x00, sizeof(data));
strcpy(buffer, "Hello! who are you? could you tell me?") ;
length=strlen(buffer) ;
/***000000为假设的交易码***/
sprintf(data,"%08ld%6.6s%s", (length+6),"000000", buffer );
length=length+8+6 ;
if((nbytes=writen(sockfd,data, length ))==-1)
{
fprintf(stderr,"Read Error:%s\n",strerror(errno));
close(sockfd);
return -1;
}
printf("I have send:%s\n", data+8);
if((nbytes=readn(sockfd, data, 8 ))==-1)
{
fprintf(stderr,"Read Error:%s\n",strerror(errno));
close(sockfd);
return -1;
}
data[nbytes]='\0' ;
length=atol(data) ;
if((nbytes=readn(sockfd, data, length ))==-1)
{
fprintf(stderr,"ReadError:%s\n",strerror(errno));
close(sockfd);
return -1;
}
data[nbytes]='\0' ;
printf("I have received:%s\n", data);
close(sockfd);
return 0;
}
(5)编译与执行
编译 gcc tcpsrv.c tcpio.c -otcpsrv。
编译 gcc tcpcli.c tcpio.c -otcpcli。
在一界面下启动服务端进程 ./tcpsrv。
在另一界面下执行./tcpcli 127.0.0.1 10000,执行结果如下:
I havesend:000000Hello! who are you? could you tell me?
I have received:000000Iam sorry! who am I? I don't know also.
补充:
关键字:
volatile
用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最的值。volatile很容易被误用,用来进行原子性操作。
Q:transient关键字能实现什么?
A:当对象被序列化时(写入字节序列到目标文件)时,transient阻止实例中那些用此关键字声明的变量持久化;当对象被反序列化时(从源文件读取字节序列进行重构),这样的实例变量值不会被持久化和恢复。例如,当反序列化对象——数据流(例如,文件)可能不存在时,原因是你的对象中存在类型为Java.io.InputStream的变量,序列化时这些变量引用的输入流无法被打开。
毕业三年后想回青岛,空气好、房价可以接受、研发岗位待遇还不错、户口好解决、消费中等、离家近等等众多因素。感觉最想回青岛。
但是了解了一下青岛的研发大环境:适合的公司主要就是:海尔、海信、中车、鼎信通讯、青岛百灵等
下面收集了一些最想去的鼎信通讯公司的招聘信息,留着以后提醒自己,有个参考
(最好学习C/C++)-------------这个方向最适合
青岛鼎信通讯股份有限公司
2016年招聘简章
青岛鼎信通讯股份有限公司成立于2008年4月,于2012年7月进行股份制改革,注册资本人民币3.9亿元。公司已于2014年5月5日在证监会作IPO预披露,申请上海主板上市。公司是一家集研发、生产、销售为一体的具有自主知识产权的高新技术企业。
青岛被评为全球宜居城市之一,是中国重要的经济中心城市和沿海开放城市,是国家级历史文化名城和风景旅游、度假胜地, 处于山东蓝色半岛经济区的核心位置,下辖与上海浦东平级的第9个国家级新区-西海岸新区。
公司总部位于青岛市核心区域市南区青岛软件园,园内设有6000余平方米的研发中心,在城阳区拥有20000余平方米的生产基地,同时正在建设位于青岛高新区内的35万平米的鼎信高新科技产业园区。2009年9月通过ISO9001:2008质量管理体系认证,2012年通过ISO14001:2008环境体系认证。2012年被青岛市认定为创新型企业。2014年被认定为国家火炬计划重点高新技术企业,拥有CNAS认证实验室一个。公司现有27项专利、32项软件著作权。
公司以载波及总线通信芯片的研发成果为基础,将其应用于电力、消防等嵌入式智能产品的研发、生产、销售,成为一个有扎实基础理论与系统技术的高新技术企业。初步建成了以IC设计、嵌入式软件、自动化生产及自动化设计为基础的企业集团。青岛鼎信通讯股份有限公司下属青岛鼎信通讯电力工程有限公司、青岛鼎信通讯消防安全有限公司、青岛鼎信通讯智能装备有限公司、青岛鼎信通讯科技有限公司、上海鼎信通讯集成电路设计有限公司、青岛鼎信通讯股份有限公司西安分公司、青岛鼎信通讯电力工程有限公司重庆分公司、四川分公司、河北分公司、湖南分公司等子公司和分公司,并在全国31个省、自治区、直辖市设立了办事处。
青岛鼎信通讯股份有限公司致力于电力线载波通信芯片、总线通信芯片的研发及智能电网、消防安防、智能家居等方面的产品应用推广,在扩频通信、信号处理、自动控制、电能计量、电网终端采集、变配电技术、电能质量控制、新能源、计算机应用及机电一体化等领域具有较强的科研、生产能力。根据国家电网数据信息,青岛鼎信通讯的电力线载波采集系列产品,凭借产品的优异性能,在国网供应商中,市场占有率达40%,处于电力线载波芯片行业的领先地位。目前公司有成体系的自动抄表系统,四合一采集控制终端,家用电智能显示终端等系列产品,并在国内外有较好应用(如以色列国家电网、俄罗斯国家电网、南非国家电网等)。自2008年成立至今,公司取得大量的技术积累和不俗的经营业绩,截止到2015年底,公司总营业额累计达36.85亿元。
青岛鼎信通讯股份有限公司成立以来一直遵循“诚信务实、追求卓越、以人为本”的基本原则,同时努力营造良好的沟通环境,为员工提供一个平等、友爱、合作和不断进步的工作氛围。截止2016年2月,公司已有员工2800余人,其中研发人员500余人。在未来,公司将打造千人规模的精英研发团队。
公司为员工提供舒适的工作环境,完善的就职培训体系,良好的福利待遇,有竞争力的薪酬。公司虚位以待,诚挚欢迎您选择鼎信,和鼎信共同成长,并以鼎信为平台实现人生价值。
招聘对象:
招聘专业:
招聘岗位:
薪酬及福利待遇:
招聘流程:
简历投递方式:[email protected]
公司网址:www.topscomm.com
青岛鼎信通笔试题2016春季
主要考查的知识点:
编程语言
数据库
数组排序
查找
面向对象
网络编程中的多线程与多进程
前几天和朋友聊天,朋友问我怎么最近不写博客了,一个是因为最近在忙着公司使用的一些控件的开发,浏览器兼容性搞死人;但主要是因为这段时间一直在看html5的东西,看到web socket时觉得很有意思,动手写几个demo,但web socket需要特定的服务器支持,由于标准制定工作还没完成,所以没有多少主流的服务器支持,自己在网上下载了几个实现,包括PHP的、C#的、甚至Node.js的,但一个是协议变化比较大,很多代码已经过时了,再就是有一些支持最新的标准,但是我想稍微改造一下,看人家源代码的时候云里雾里,看看别人的代码行数也不多,决定自己实现一个。
悲剧由此开始,虽然哥们儿国内非知名工科大学毕业,但好歹也是科班CS出身,但大学得过且过,什么TCP/IP协议,什么socket了都没概念。为了做出一个简单的支持广播的websocket server,在网上找了很多相关代码,左抄一句,右抄一句,弄了一个星期竟然还是漏洞百出,调试不起来,只好从头来过了,先补一些基本知识,然后再一步步根据原理实现,今天终于实现了绝大部分功能,由此真的感受到了,搞计算机必须得有理论指导实践,否则只能像个没头苍蝇到处乱撞。
要想理解socket首先得熟悉一下TCP/IP协议族, TCP/IP(Transmission Control Protocol/Internet Protocol)即传输控制协议/网间协议,定义了主机如何连入因特网及数据如何再它们之间传输的标准,
从字面意思来看TCP/IP是TCP和IP协议的合称,但实际上TCP/IP协议是指因特网整个TCP/IP协议族。不同于ISO模型的七个分层,TCP/IP协议参考模型把所有的TCP/IP系列协议归类到四个抽象层中
应用层:TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet等等
传输层:TCP,UDP
网络层:IP,ICMP,OSPF,EIGRP,IGMP
数据链路层:SLIP,CSLIP,PPP,MTU
每一抽象层建立在低一层提供的服务上,并且为高一层提供服务,看起来大概是这样子的
估计有兴趣打开此文的同学都对此有一定了解了,加上我也是一知半解,所以就不详细解释,有兴趣同学可以上网上搜一下资料
在TCP/IP协议中两个因特网主机通过两个路由器和对应的层连接。各主机上的应用通过一些数据通道相互执行读取操作
我们知道两个进程如果需要进行通讯最基本的一个前提能能够唯一的标示一个进程,在本地进程通讯中我们可以使用PID来唯一标示一个进程,但PID只在本地唯一,网络中的两个进程PID冲突几率很大,这时候我们需要另辟它径了,我们知道IP层的ip地址可以唯一标示主机,而TCP层协议和端口号可以唯一标示主机的一个进程,这样我们可以利用ip地址+协议+端口号唯一标示网络中的一个进程。
能够唯一标示网络中的进程后,它们就可以利用socket进行通信了,什么是socket呢?我们经常把socket翻译为套接字,socket是在应用层和传输层之间的一个抽象层,它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信。
socket起源于UNIX,在Unix一切皆文件哲学的思想下,socket是一种"打开—读/写—关闭"模式的实现,服务器和客户端各自维护一个"文件",在建立连接打开后,可以向自己文件写入内容供对方读取或者读取对方内容,通讯结束时关闭文件。
socket是"打开—读/写—关闭"模式的实现,以使用TCP协议通讯的socket为例,其交互流程大概是这样子的
服务器根据地址类型(ipv4,ipv6)、socket类型、协议创建socket
服务器为socket绑定ip地址和端口号
服务器socket监听端口号请求,随时准备接收客户端发来的连接,这时候服务器的socket并没有被打开
客户端创建socket
客户端打开socket,根据服务器ip地址和端口号试图连接服务器socket
服务器socket接收到客户端socket请求,被动打开,开始接收客户端请求,直到客户端返回连接信息。这时候socket进入阻塞状态,所谓阻塞即accept()方法一直到客户端返回连接信息后才返回,开始接收下一个客户端谅解请求
客户端连接成功,向服务器发送连接状态信息
服务器accept方法返回,连接成功
客户端向socket写入信息
服务器读取信息
客户端关闭
服务器端关闭
在TCP/IP协议中,TCP协议通过三次握手建立一个可靠的连接
第一次握手:客户端尝试连接服务器,向服务器发送syn包(同步序列编号Synchronize Sequence Numbers),syn=j,客户端进入SYN_SEND状态等待服务器确认
第二次握手:服务器接收客户端syn包并确认(ack=j+1),同时向客户端发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态
第三次握手:第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手
定睛一看,服务器socket与客户端socket建立连接的部分其实就是大名鼎鼎的三次握手
为了建立冗余较小、结构合理的数据库,设计数据库时必须遵循一定的规则。在关系型数据库中这种规则就称为范式。范式是符合某一种设计要求的总结。要想设计一个结构合理的关系型数据库,必须满足一定的范式。
在实际开发中最为常见的设计范式有三个:
1.第一范式(确保每列保持原子性)
第一范式是最基本的范式。如果数据库表中的所有字段值都是不可分解的原子值,就说明该数据库表满足了第一范式。
第一范式的合理遵循需要根据系统的实际需求来定。比如某些数据库系统中需要用到“地址”这个属性,本来直接将“地址”属性设计成一个数据库表的字段就行。但是如果系统经常会访问“地址”属性中的“城市”部分,那么就非要将“地址”这个属性重新拆分为省份、城市、详细地址等多个部分进行存储,这样在对地址中某一部分操作的时候将非常方便。这样设计才算满足了数据库的第一范式,如下表所示。
上表所示的用户信息遵循了第一范式的要求,这样在对用户使用城市进行分类的时候就非常方便,也提高了数据库的性能。
2.第二范式(确保表中的每列都和主键相关)
第二范式在第一范式的基础之上更进一层。第二范式需要确保数据库表中的每一列都和主键相关,而不能只与主键的某一部分相关(主要针对联合主键而言)。也就是说在一个数据库表中,一个表中只能保存一种数据,不可以把多种数据保存在同一张数据库表中。
比如要设计一个订单信息表,因为订单中可能会有多种商品,所以要将订单编号和商品编号作为数据库表的联合主键,如下表所示。
订单信息表
这样就产生一个问题:这个表中是以订单编号和商品编号作为联合主键。这样在该表中商品名称、单位、商品价格等信息不与该表的主键相关,而仅仅是与商品编号相关。所以在这里违反了第二范式的设计原则。
而如果把这个订单信息表进行拆分,把商品信息分离到另一个表中,把订单项目表也分离到另一个表中,就非常完美了。如下所示。
这样设计,在很大程度上减小了数据库的冗余。如果要获取订单的商品信息,使用商品编号到商品信息表中查询即可。
3.第三范式(确保每列都和主键列直接相关,而不是间接相关)
第三范式需要确保数据表中的每一列数据都和主键直接相关,而不能间接相关。
比如在设计一个订单数据表的时候,可以将客户编号作为一个外键和订单表建立相应的关系。而不可以在订单表中添加关于客户其它信息(比如姓名、所属公司等)的字段。如下面这两个表所示的设计就是一个满足第三范式的数据库表。
这样在查询订单信息的时候,就可以使用客户编号来引用客户信息表中的记录,也不必在订单信息表中多次输入客户信息的内容,减小了数据冗余。
C语言中讲讲static变量和static函数有什么作用
static关键字有两种意思,你看上下文来判断
1,表示变量是静态存储变量
表示变量存放在静态存储区.
2,表示该变量是内部连接
(这种情况是指该变量不在任何{}之内,就象全局变量那样,这时候加上static)
,也就是说在其它的.cpp文件中,该变量是不可见的(你不能用).
当static加在函数前面的时候
表示该函数是内部连接,之在本文件中有效,别的文件中不能应用该函数.
不加static的函数默认为是全局的.
也就是说在其他的.cpp中只要申明一下这个函数,就可以使用它.
1、static全局变量与普通的全局变量有什么区别?static局部变量和普通局部变量有什么区别?static函数与普通函数有什么区别?
答:全局变量(外部变量)的说明之前再冠以static就构成了静态的全局变量。全局变量本身就是静态存储方式,静态全局变量当然也是静态存储方式。这两者在存储方式上并无不同。这两者的区别虽在于非静态全局变量的作用域是整个源程序,当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。而静态全局变量则限制了其作用域,即只在定义该变量的源文件内有效,在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其它源文件中引起错误。
从以上分析可以看出,把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域,限制了它的使用范围。
static函数与普通函数作用域不同。static函数仅在本文件中使用。只在当前源文件中使用的函数应该说明为内部函数(static),内部函数应该在当前源文件中说明和定义。对于可在当前源文件以外使用的函数,应该在一个头文件中说明,要使用这些函数的源文件要包含这个头文件
static全局变量与普通的全局变量有什么区别:static全局变量只初使化一次,防止在其他文件单元中被引用;
static局部变量和普通局部变量有什么区别:static局部变量只被初始化一次,下一次依据上一次结果值;
static函数与普通函数有什么区别:static函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝
析构函数也是特殊的类成员函数,它没有返回类型,没有参数,不能随意调用,也没有重载。只是在类对象生命期结束的时候,由系统自动调用释放在构造函数中分配的资源。这种在运行时,能依据其类型确认调用那个函数的能力称为多态性,或称迟后联编。另: 析构函数一般在对象撤消前做收尾工作,比如回收内存等工作,
虚拟函数的功能是使子类可以用同名的函数对父类函数进行覆盖,并且在调用时自动调用子类覆盖函数,如果是纯虚函数,则纯粹是为了在子类覆盖时有个统一的命名而已。
注意:子类重新定义父类的虚函数的做法叫覆盖,override,而不是overload(重载),重载的概念不属于面向对象编程,重载指的是存在多个同名函数,这些函数的参数表不同..重载是在编译期间就决定了的,是静态的,因此,重载与多态无关.与面向对象编程无关.
阅读 评论(1) 收藏 举报
面向对象的三个基本特征是:封装、继承、多态。
封装
封装最好理解了。封装是面向对象的特征之一,是对象和类概念的主要特性。
封装,也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。
继承
面向对象编程 (OOP)语言的一个主要功能就是“继承”。继承是指这样一种能力:它可以使用现有类的所有功能,并在无需重新编写原来的类的情况下对这些功能进行扩展。
通过继承创建的新类称为“子类”或“派生类”。
被继承的类称为“基类”、“父类”或“超类”。
继承的过程,就是从一般到特殊的过程。
要实现继承,可以通过“继承”(Inheritance)和“组合”(Composition)来实现。
在某些 OOP语言中,一个子类可以继承多个基类。但是一般情况下,一个子类只能有一个基类,要实现多重继承,可以通过多级继承来实现。
继承概念的实现方式有三类:实现继承、接口继承和可视继承。
Ø 实现继承是指使用基类的属性和方法而无需额外编码的能力;
Ø 接口继承是指仅使用属性和方法的名称、但是子类必须提供实现的能力;
Ø 可视继承是指子窗体(类)使用基窗体(类)的外观和实现代码的能力。
在考虑使用继承时,有一点需要注意,那就是两个类之间的关系应该是“属于”关系。例如,Employee是一个人,Manager也是一个人,因此这两个类都可以继承 Person类。但是 Leg类却不能继承 Person类,因为腿并不是一个人。
抽象类仅定义将由子类创建的一般属性和方法,创建抽象类时,请使用关键字 Interface 而不是 Class。
OO开发范式大致为:划分对象→抽象类→将类组织成为层次化结构(继承和合成) →用类与实例进行设计和实现几个阶段。
多态
多态性(polymorphisn)是允许你将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针。
实现多态,有二种方式,覆盖,重载。
覆盖,是指子类重新定义父类的虚函数的做法。
重载,是指允许存在多个同名函数,而这些函数的参数表不同(或许参数个数不同,或许参数类型不同,或许两者都不同)。
其实,重载的概念并不属于“面向对象编程”,重载的实现是:编译器根据函数不同的参数表,对同名函数的名称做修饰,然后这些同名函数就成了不同的函数(至少对于编译器来说是这样的)。如,有两个同名函数:function func(p:integer):integer;和function func(p:string):integer;。那么编译器做过修饰后的函数名称可能是这样的:int_func、str_func。对于这两个函数的调用,在编译器间就已经确定了,是静态的(记住:是静态)。也就是说,它们的地址在编译期就绑定了(早绑定),因此,重载和多态无关!真正和多态相关的是“覆盖”。当子类重新定义了父类的虚函数后,父类指针根据赋给它的不同的子类指针,动态(记住:是动态!)的调用属于子类的该函数,这样的函数调用在编译期间是无法确定的(调用的子类的虚函数的地址无法给出)。因此,这样的函数地址是在运行期绑定的(晚邦定)。结论就是:重载只是一种语言特性,与多态无关,与面向对象也无关!引用一句Bruce Eckel的话:“不要犯傻,如果它不是晚邦定,它就不是多态。”
那么,多态的作用是什么呢?我们知道,封装可以隐藏实现细节,使得代码模块化;继承可以扩展已存在的代码模块(类);它们的目的都是为了——代码重用。而多态则是为了实现另一个目的——接口重用!多态的作用,就是为了类在继承和派生的时候,保证使用“家谱”中任一类的实例的某一属性时的正确调用。
标签: 服务器bufferstructserversocket编程
2011-07-0610:50 2495人阅读 评论(0) 收藏 举报
分类:
C语言(13)
版权声明:本文为博主原创文章,未经博主允许不得转载。
目录(?)[+]
并发服务器是socket应用编程中最常见的应用模型。并发服务器模型根据连接方式分为长连接和短连接,长连接为通信双方建立连接后一直保持连接,然后一直用此连接进行读写操作;短连接为通信双方每一次交易过程都建立连接和关闭连接。并发服务器模型根据处理方式可分为同步方式和异步方式,同步是客户端发送请求给服务器等待服务器返回处理结果;异步是指客户端发送请求给服务器,不等待服务器返回处理结果,而直接去完成其他的流程,对于处理结果客户端可以事后查询和让服务器进行主动通知。
进程是一个程序的一次运行过程,它是一个动态实体,是独立的任务,它拥有独立的地址空间、执行堆栈、文件描述符等。每个进程拥有独立的地址空间,在进程不存在父子关系的情况下,互不影响。
进程的终止存在两个可能:父进程先于子进程终止(由init进程领养),子进程先于主进程终止。对于后者,系统内核为子进程保留一定的状态信息(进程ID、终止状态、CPU时间等),并向其父进程发送SIGCHLD信号。当父进程调用wait或waitpid函数时,将获取这些信息,获取后内核将对僵尸进程进行清理。如果父进程设置了忽略SIGCHLD信号或对SIGCHLD信号提供了处理函数,即使不调用wait或waitpid函数内核也会清理僵尸进程。
但父进程调用wait函数处理子进程退出信息时,会存在下面所述的问题。在有多个子进程情况下,wait函数只等待最先到达的子进程的终止信息。下图18-7父进程有3个子进程,由于SIGCHLD信号不排队,在SIGCHLD信号同时到来后,父进程的wait函数只执行一次,这样将留下2个僵尸进程,而使用waitpid函数并设置WNOHANG选项可以解决这个问题。
图18-7 多进程信号图
综上所述,在多进程并发的情况下,防止子进程变成僵尸进程常见有如下两种方法:
① 父进程调用signal(SIGCHLD,SIG_IGN)对子进程退出信号进行忽略,或者把SIG_IGN替换为其他处理函数,设置对SIGCHLD信号的处理。
② 父进程调用waitpid(-1,NULL,WNOHANG)对所有子进程SIGCHLD信号进行处理。
图18-8~图18-11画出了并发服务器文件描述符的变化流程图。其中listenfd为服务端的socket监听文件描述符,connfd为accept函数返回的socket连接文件描述符。
服务器调用accept函数后,客户与服务器文件描述符如下图18-8所示。
图18-8 调用accept函数时套接字描述符图
服务器调用accept函数后,客户与服务器文件描述符如下图18-9所示。
图18-9调用accept函数后套接字描述符图
服务器调用fork函数后,客户与服务器文件描述符如下图18-10所示。
图18-10调用fork函数后套接字描述符图
服务端父进程关闭连接套接字,子进程关闭监听套接字,客户与服务器文件描述符状况如下图18-11所示。
图18-11 并发服务器最终连接图
在这里强调的是,并发服务器fork后父进程一定要关闭子进程连接套接字;而子进程要关闭父进程监听套接字,以免误操作。
(1)并发服务器处理流程
并发服务器处理流程如下:
① 客户端首先发起连接。
② 服务端进程accept打开一个新的连接套接字与客户端进行连接,accept在一个while(1)循环内等待客户端的连接。
③ 服务端fork一个子进程,同时父进程close子进程连接套接字,循环等待下一进程。
④ 服务端子进程close父进程监听套接字,并用连接套接字保持与客户端的连接,客户发送数据到服务端,然后阻塞等待服务端返回。
⑤ 子进程进行接收数据,进行业务处理,然后再发送数据给客户端。
⑥ 子进程关闭连接,然后退出。
(2)程序报文协议说明
该程序报文协议模式为常见行业应用软件协议模式,其具体说明如下:
发送和接收报文协议:8位报文长度(不包含本身)+6位交易码+报文内容,交易码标识该交易的类型。
实际应用中服务端进程根据6位交易码调度不同的应用服务。
(3)并发服务器服务端代码
tcpsrv.c源代码如下:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#defineMY_PORT 10000
externint readn(int fd,void *buffer,int length) ;
externint writen(int fd,void *buffer,int length) ;
intmain(int argc ,char **argv)
{
int listen_fd,accept_fd;
struct sockaddr_in server_addr;
struct sockaddr_in cli_addr;
int n;
int cliaddr_len ;
char buffer[1024];
char data[1024] ;
long length ;
int nbytes ;
if((listen_fd=socket(AF_INET,SOCK_STREAM,0))<0)
{
printf("Socket Error:%s\n",strerror(errno));
return -1;
}
memset(&server_addr,0x00, sizeof(struct sockaddr_in));
memset(&cli_addr,0x00, sizeof(struct sockaddr_in));
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons(MY_PORT);
server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
n=1;
/* 如果服务器终止后,服务器可以第二次快速启动而不用等待一段时间*/
setsockopt(listen_fd,SOL_SOCKET,SO_REUSEADDR,&n,sizeof(int));
if(bind(listen_fd,(struct sockaddr *)&server_addr,sizeof(server_addr))<0)
{
printf("Bind Error:%s\n",strerror(errno));
return -1;
}
listen(listen_fd,5);
while(1)
{
cliaddr_len= sizeof( cli_addr ) ;
accept_fd=accept(listen_fd, (struct sockaddr *)&cli_addr, &cliaddr_len);
if((accept_fd<0)&&(errno==EINTR))
continue;
else if(accept_fd<0)
{
printf("Accept Error:%s\n",strerror(errno));
continue;
}
if((n=fork())==0)
{
/* 子进程处理客户端的连接*/
fprintf(stdout,"listen_fd:%d accept_fd:%d\n",listen_fd, accept_fd);
close(listen_fd);
memset(buffer, 0x00, sizeof(buffer)) ;
memset(data, 0x00, sizeof(data));
if((nbytes=readn(accept_fd, data, 8 ))==-1)
{
fprintf(stderr,"Read Error:%s\n",strerror(errno));
fprintf(stderr,"data:%s\n",data );
close(accept_fd);
return -1;
}
fprintf(stdout,"data:%s,nbytes=%d\n",data, nbytes );
data[nbytes]='\0' ;
length=atol(data) ;
fprintf(stdout,"data:%s,nbytes=%d\n",data, nbytes );
if((nbytes=readn(accept_fd, data, length ))==-1)
{
fprintf(stderr,"Read Error:%s\n",strerror(errno));
close(accept_fd);
return -1;
}
data[nbytes]='\0' ;
fprintf(stdout,"data:%s,nbytes=%d\n",data, nbytes );
if( strncmp(data, "000000", 6 )==0 )
{
strcpy(buffer, "I am sorry! who am I? I don't know also.") ;
length=strlen(buffer) ;
sprintf(data,"%08ld%6.6s%s", (length+6),"000000", buffer );
if((nbytes=writen(accept_fd,data, (length+6+8)))==-1)
{
fprintf(stderr,"Read Error:%s\n",strerror(errno));
close(accept_fd);
return -1;
}
fprintf(stdout,"data:%s\n",data );
}else{
/*非000000交易请求为非法,沉默是最好的回答*/
}
close(accept_fd);
return 0;
}
else if(n<0)
printf("Fork Error:%s\n\a",strerror(errno));
close(accept_fd);
while(waitpid(-1,NULL,WNOHANG) > 0); /* clean up child processes */
}
}
(4)客户端代码
tcpcli.c源代码如下:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
externint readn(int fd,void *buffer,int length) ;
externint writen(int fd,void *buffer,int length) ;
intmain(int argc, char *argv[])
{
int sockfd;
char buffer[1024];
char data[1024];
long length ;
struct sockaddr_in server_addr;
struct hostent *host;
int portnumber,nbytes;
if(argc!=3)
{
fprintf(stderr,"Usage:%s hostname portnumber\a\n",argv[0]);
return -1;
}
if((host=gethostbyname(argv[1]))==NULL)
{
fprintf(stderr,"Gethostname error\n");
return -1;
}
if((portnumber=atoi(argv[2]))<0)
{
fprintf(stderr,"Usage:%s hostname portnumber\a\n",argv[0]);
return -1;
}
/* 客户程序开始建立sockfd描述符*/
if((sockfd=socket(AF_INET,SOCK_STREAM,0))==-1)
{
fprintf(stderr,"Socket Error:%s\a\n",strerror(errno));
return -1;
}
/* 客户程序填充服务端的地址端口信息*/
bzero(&server_addr,sizeof(server_addr));
server_addr.sin_family=AF_INET;
server_addr.sin_port=htons(portnumber);
server_addr.sin_addr= *((struct in_addr *)host->h_addr);
/* 客户程序发起连接请求*/
if(connect(sockfd,(struct sockaddr *)(&server_addr),sizeof(struct sockaddr)
)==-1)
{
fprintf(stderr,"Connect Error:%s\a\n",strerror(errno));
return -1;
}
memset(buffer, 0x00, sizeof(buffer)) ;
memset(data, 0x00, sizeof(data));
strcpy(buffer, "Hello! who are you? could you tell me?") ;
length=strlen(buffer) ;
/***000000为假设的交易码***/
sprintf(data,"%08ld%6.6s%s", (length+6),"000000", buffer );
length=length+8+6 ;
if((nbytes=writen(sockfd,data, length ))==-1)
{
fprintf(stderr,"Read Error:%s\n",strerror(errno));
close(sockfd);
return -1;
}
printf("I have send:%s\n", data+8);
if((nbytes=readn(sockfd, data, 8 ))==-1)
{
fprintf(stderr,"Read Error:%s\n",strerror(errno));
close(sockfd);
return -1;
}
data[nbytes]='\0' ;
length=atol(data) ;
if((nbytes=readn(sockfd, data, length ))==-1)
{
fprintf(stderr,"ReadError:%s\n",strerror(errno));
close(sockfd);
return -1;
}
data[nbytes]='\0' ;
printf("I have received:%s\n", data);
close(sockfd);
return 0;
}
(5)编译与执行
编译 gcc tcpsrv.c tcpio.c -otcpsrv。
编译 gcc tcpcli.c tcpio.c -otcpcli。
在一界面下启动服务端进程 ./tcpsrv。
在另一界面下执行./tcpcli 127.0.0.1 10000,执行结果如下:
I havesend:000000Hello! who are you? could you tell me?
I have received:000000Iam sorry! who am I? I don't know also.
补充:
关键字:
volatile
用volatile修饰的变量,线程在每次使用变量的时候,都会读取变量修改后的最的值。volatile很容易被误用,用来进行原子性操作。
Q:transient关键字能实现什么?
A:当对象被序列化时(写入字节序列到目标文件)时,transient阻止实例中那些用此关键字声明的变量持久化;当对象被反序列化时(从源文件读取字节序列进行重构),这样的实例变量值不会被持久化和恢复。例如,当反序列化对象——数据流(例如,文件)可能不存在时,原因是你的对象中存在类型为Java.io.InputStream的变量,序列化时这些变量引用的输入流无法被打开。