服务器设计系列:架构综述

一 系统框架概述

  网络上的服务器,无论是嵌入式的网络设备,还是pc上服务器,整体结构以及主要思想都大体相同:根据业务模型确定主要数据结构,根据数据结构确定线程模型,在各个业务线程内根据围绕主要数据结构进行的操作确定状态机模型,低层使用网络层收发数据完成和其它网元的通讯。线程交互模型简单描述如下图:


图1 线程交互模型

二 网络层

  相对而言,网络层的实现相对呆板、模式化,这个层面的要点在系统调用,实现方式要符合操作系统提供的api允许的使用方式,而不能天马行空想当然,因此提高这部分能力的重点在于系统性的学习(《unix网络编程》),不在于经验。
  网络层有3部分构成:连接细节、多路复用函数、协议解析。
  (1)连接细节。要实现各个协议的网络层(协议栈),首先要面对的就是承载该协议的传输层协议,udp还是tcp,理论本身就不再多说了。简单说下编程上的差异:udp的网络连接简单、收数据简单,tcp的则网络连接复杂、收数据需要在应用层面确定是否一个收包完毕,可以参见《服务器设计系列:网络模型》。
  (2)多路复用函数。除了处理udp、tcp本身网络连接的系统调用之外,还存在和udp/tcp无关的多路复用函数(select等),它们可以监控tcp的网络事件,也可以监控udp的网络事件,属于网络层的核心驱动部分。
  (3)协议解析。这部分相对独立,是网络层中和网络连接、收发消息无关的部分,主要功能则是对该协议各种消息的解包(decode)、打包(encode)。
  网络层的主要线程是多路复用监控线程(select/poll/epoll_wait等),网络消息触发该线程的运转,如果是收包,则调用read类函数,收包完毕,进行解包操作,之后根据需要向业务线程发送消息(也可以收包完毕后即把数据包裹在消息中发送给业务线程,由业务线程解包,单独把解包打包操作归在网络层中)。

  性能方面:为了描述方便,引入使用场景:转发rtp码流,这个场景需要尽量大的并发行和实时性。
  (1)高性能函数。如果系统支持,使用epoll/port/kqueue等高性能多路复用函数。在此,将多路复用监控线程封装在RtpService类中,将rtp连接,封装在RtpConnection类中。使用模型可以参见《服务器设计系列:网络模型》。
  (2)多线程支持。启动多个RtpService示例,也既是启动多个多路复用监控线程。将RtpConnection对象均匀的插入到各个RtpService中,同时在RtpConnection中记录它属于的RtpService,便于删除的时候找到它所在的RtpService。
  (3)收数据线程直接转发。处理实时性的需要,一定要在收数据的线程中转发数据,而不是向其它线程发送消息,让其它线程完成发送。这样做一是避免不必要的内存复制,最重要的是线程调度引起的时间不确定性不能保证转发的实时性。
  (4)读写锁代替普通锁。
分发数据的时候(转发不需要)势必要扫描一个容器中的对象,进行分发操作,分发发生在不同的线程中,加锁成为必然。读写锁代替普通锁,使扫描操作不必互斥,也避免(2)中的多线程不能发挥多线程的效果。实际测试发现,linux2.6内核中的读写锁,只有在静态初时化的时候,才能写优先,使用pthread_rwlock_init进行初始化,不管如何设置它的属性(即便是设置属性为写优先),都不能实现写优先效果,因此需要自己使用pthread_mutex_t和pthread_cond_t实现写优先的读写锁,具体实现的细节就不再多说了(可以参考《服务器设计系列:线程》中线程消息队列中锁的实现),重要的是想法,不是实现。写优先的必要性是因为转发线程活跃频繁,而读线程可以一直进入读锁,造成写线程永久性的处于等待状态。
  (5)使用Epoll的ET模式。
再此对epoll多说一点,在《服务器设计系列:网络模型》中因为我当时的测试场景是普通的http交互,得出“LT和ET性能相当”的结论,跟帖中网友bluesky给予更正,非常感谢。在这个rtp转发的场景中,特别适合ET模式,一次触发,必须读尽接收缓冲区的数据,一是保证转发实时性,一是避免剩余数据再次触发(并发高的情况下,多路复用函数的被触发已非常频繁,因此要尽量减少不必要的触发),这个场景下,多一次的读操作微不足道。
  (6)减少系统调用次数。系统调用是比内存copy性能更差的操作,这个再后面的文章中会再详细描述。网络层中的系统可以减少的就是read/recv/recvfrom类的操作,极端化低性能的操作就是一次读一个字节,造成系统调用的次数大幅上升,一般的做法,是开辟缓存(比如char buf[4096];),一次读取尽可能多的字节。
  (7)二进制包使用结构直接解包,字符性包延迟解包。
这两点的出发点都是尽量减少内存复制。

  二进制解包举例:首先根据协议规定的包结构,定义结构体。比如(注:网友powervv 跟帖指出,要点在于大小端主机序、网络序和主机序之间的转换、以及字节对齐问题,避免误导读者,举例做出修改):

struct RTPHeader
{
#if __BYTE_ORDER == __BIG_ENDIAN
  unsigned char v:2; 
  unsigned char p:1;
  unsigned char x:1;
  unsigned char cc:4;
  unsigned char m:1;
  unsigned char pt:7; 
#else
  unsigned char cc:4; 
  unsigned char x:1; 
  unsigned char p:1; 
  unsigned char v:2; 
  unsigned char pt:7;
  unsigned char m:1;
#endif
  unsigned seq:16;
  unsigned tm:32;
  unsigned ssrc:32;
};
  收数据到buf,解包过程则是:
Packet *pack=(Packet *)buf
  完成解包,读取seq的时候,需要ntohs转化,tm同样要ntohl。打包相同:
char buf[12];
Packet *pack=(Packet *)buf;
packe->v=2;
...
pack->seq=htons(1);
      字符性包解包,则一般是预解包扫描buf,将每个字段的偏移和长度记录下来,等需要的时候在进行内存复制操作(常用的则是立即复制出来)。通常将字段使用枚举定义,比如有字段MAX_FIEDS_NUM个,定义开始位置和偏移结构:

struct FieldLoc
{
 int loc;
 int len;
};
      则定义 FieldLoc[MAX_FIEDS_NUM],准备保存各个字段的偏移和长度。至于扫描字段引起的性能损耗和内存复制引起的性能比较将在后面阐述。
      (8)内存池相关、系统调用以及内存复制等。这些通用性能部分后面会再有描述。

三 业务线程

  上面介绍了网络层部分。网络层基本都是多路复用函数作为运行的主线程,使用管道或者sockpair与之通讯,这是网络层线程的固有特点,和业务线程的呈现方式完全不同。经过网络层以后,数据开始流向业务线程,现在就顺着数据流向往上看。《服务器设计系列:线程》有对线程的一个入门描述,里面实现了一个消息队列。

   (1)业务线程的划分
  在业务层面,数据结构仍然是服务器程序的核心。 整理出业务需要的数据结构,据此划分线程,保证每个数据结构都在它所属的线程中被修改。如果其它线程想修改该结构,则需要向本线程发送消息,由本线程修改。当然这只是理想情况,有时候处于性能的需要或者和网络层的交互,需要跨线程访问其它线程的数据,此时则需要加锁,这也是线程间交互陷入混乱的一个开端,这种情况要限制在一个很小可控的范围内。
   (2)带消息队列的线程实现

  OA/CRM/WorkFlow的系统的关键在于业务的整理、面向对象的设计。而网络服务器不同,服务器的业务相对清晰,相关性强,它设计的关键在于数据模型的整理、线程的划分。可以说线程是服务器的骨架。这里引入几乎每个服务器都会有的一个基础模块:带消息队列的线程类。《服务器设计系列:线程》实现了一个简单的线程消息队列。在该消息队列基础之上进一步封装,实现带消息队列的线程类。该类的静态类图如下:

图2 带消息队列的线程类

  ThreadQueue参见《服务器设计系列:线程》。上图中的start方法中参数默认为1,大于1则是线程池的实现,里面以线程的方式启动run函数,线程号存入vector,供停止时用。putq方法在其它线程中调用,向该线程发送消息,run方法中循环调用getq获取消息,调用deal_msg处理。实际的业务线程只需要继承该类即可成为带消息队列的线程。
  (3)监控线程
  基于上面这个基础模块,可以进一步开发监控线程,监控线程定时向各个线程发送心跳消息,各普通线程收到心跳信息后向监控线程回复,如果某个线程在一定时间内没有回复心跳,则可以采取进一步的修复处理。该方案可以作为系统安全的一个备选方案。
  以上思想同样适用于嵌入式平台,很多嵌入式平台使用多进程协同处理消息,进程之间使用共享内存或者系统消息队列通讯,思想大同小异,并且同样可以设计监控进程。

四 消息映射

  (1)消息定义以及处理示例
  有了线程消息队列,也有就有消息传递,接下来就是业务线程获取到消息处理消息。一个业务线程可能要处理多种消息,为区分不同的消息,引入消息类型。如下:

enum MsgType
{
    MSG_TYPE_1=65,//64以下预留,用于统一的管理控制
    MSG_TYPE_2,
    ..
    MSG_TYPE_MAX
};
struct Msg
{
    MsgType type;
    MsgData data;
};
  业务线程在deal_msg方法中处理消息,很容易想到的处理流程如下:
switch(msg->type)
{
    case MSG_TYPE_1:
         do_msg_type_1_();
         break;
    case MSG_TYPE_2:
         do_msg_type_2_();
         break;
    ......
    default:
             do_default_msg_();
             break;
}
   (2)从代码熵引入查表法
  这里引入“代码熵”的概念,用于描述代码的混乱程度。每千行代码中,出现一个“else”,代码熵加1,出现一个“case”,代码熵也加1。 如果1k行代码的熵大于7,我们就认为这个代码已经开始变的混乱了。因此当消息类型不多的时候,使用case是个不错的选择,当处理的消息大于7个并且有扩充趋势的时候,我们就要想另外的办法来代替这种switch...case...的写法。
   消除else和case最直接的想法是查表法,使用数组下标标记消息类型,数组保存消息处理方法指针。这里不使用map,查询需要o(lgn)不如数组来的直接,另外设计期间就已明确知道消息的类型以及对应的处理函数,不需要动态增减,也不需要使用vector,直接简单的方法就是定义一个static的数组表。
   (3)消息映射
  直接的数组展现方式不携带语义,展现方式不直观,维护和扩充都让人头大。这里可用借助宏包裹数组,提供可读的展现方式,如下:
BEGIN_MESSAGE_MAP(SessionManager,SessionMsg)
    ON_MESSAGE(MSG_TYPE_1, SessionManager::do_msg_type_1_)
    ON_MESSAGE(MSG_TYPE_2, SessionManager::do_msg_type_2_)
    ......
END_MESSAGE_MAP()
      如果你熟悉MFC,一定很熟悉这种消息映射的定义方式。这种消息映射的定义方式,从可维护、可读方面比直接的数组更进了一步。宏BEGIN_MESSAGE_MAP、ON_MESSAGE的实现方式不再详写,如果读者实在想象不出来,可以参见《服务器设计系列:状态机》中的状态机映射宏的定义方式。使用的时候在deal_msg中直接根据消息类型找到数组中的消息处理函数进行处理,如果你认为这样暴露了消息映射背后的数组结构,可以把这个寻找消息处理函数的工作也封装到基类IMsgThread中。
   (4)成员函数委托
  上面的消息映射宏展开后实际是一个静态数组,而方法do_msg_type_1_/do_msg_type_2_也必须是类的静态成员函数(普通类成员函数指针不能转化为普通函数指针)。 通过类的静态成员函数访问类的非静态属性或者方法如下:在消息中携带该类指针handler,处理方法中取到handler指针转换类型,通过指针操作。
  当代码中充斥大量通过静态成员函数访问对象私有属性的时候,这无疑是一种丑陋的写法(事实并没有这么严重)。这里就该boost::function,boost::bind出场了。如果你喜欢,也可以直接写模版实现。
   (5)题外话
  要有统一的展现方式。不仅变量的命名需要统一规范,方法的调用逻辑同样需要统一,这可以帮助你明确程序中数据的流向以及保证程序持续的扩充、维护。相对于简单的命名规范,方法调用逻辑的统一更为重要。以线程类举例说2点。例如,一个线程类不能直接调用其它线程的putq方法向对应线程发送消息。正确的做法是调用对应线程类的方法,由该方法负责向本线程发送消息。

  另外,发送消息的方法/处理消息的方法职责要明确、命名要统一。发送消息的方法负责把方法参数转化为消息内容,调用putq发送消息,该方法不得操作本类的任何私有属性。处理消息的方法负责对消息做出处理、响应。命名方面,比如发送消息的方法可以以ON_开头,处理消息的方法可以以DO_开头。
  这些规范不应该只是规范,而应该是发自内心的需要。当然没有什么规范是必须的,你仍然可以使用你喜欢的或者认为可行的方式,如果你的方法在程序1w行、10w行、50w行的时候,仍能清晰表现程序的数据流向,仍有很好的可维护性、可扩充性。
  开发领域的烙印。不多说了,一句话:重要的是思想,不是平台和语言。

五 业务处理

  网络请求包经过网络层被解析翻译成程序自定义的消息,之后被投递到业务线程的线程消息队列中。业务线程在队列的另一端取出消息,开始处理。业务处理部分主要有会话类(Session)和会话管理类(SessionManager,常见该类为单例)。先给出类图:


图3 业务处理类图

  1、SessionManager的职责:
  (1)继承IMsgThread,调用该类的start方法,启动业务线程。
  (2)提供on_.../do_...方法,供其它线程向业务线程投递消息,以及消息在业务线程的处理入口。
  (3)主要的私有属性是session类的容器对象。容器类的选型的依据,首先是查询性能,之后是插入删除的性能。
  array:数组下标为session对象的seesionid_(此处sessionid,不要理解为常见协议中的session字段,可以理解为session对象的索引)。可以做到插入删除(参考内存管理chunk分配算法)查询都在O(1)完成,缺点是不能动态增大。

  为了实现动态增长,可以参考定长内存池的分配算法,动态申请255个array为一个chunk,所有chunk使用vector管理,各chunk中array编号递增255,同样可以达到增删查O(1)的效率。缺点:一方面,sessionid_会被重复使用,加上消息经过消息队列形成的处理延迟,可能造成下一个session对象处理上一个同样sessionid的session对象遗留下来的消息,实际使用中每个session对象有自己的状态机,这种残留的消息危害并不大。另一方面,需要自己实现,对比map的O(logn),这点细微的性能提升无任何意义(也就是减少了几次整型之间的对比)。相比不需要自己额外实现的普通数组还算有点实用价值。
  vector:如果被插入的session的sessionid是递增的,查询可以做到折半查找logn的性能,但随机删除造成的内存移动是O(n),无法接受。
  map:以红黑树为基础实现。增删查找的性能都可以平稳的保持在O(logn),当sessionid_为整型或者其operater<实现简单的时候是最常用的容器。
  hash_map:哈希表,使用大量内存尽量使数据均匀分布,查询性能分hashcode的计算(hash函数)和查找部分,hash函数一般为所有有效信息的移位计算(经典的是字符串的33算法)叠加,查找部分最理想的是O(1),最差是O(n),取决于hash函数计算结果碰撞的几率。当sessionid_为字符串或者其operater<实现复杂的时候常用。
      (4)该类处理消息的方式举例如下:(容器以map<int,session *> mapSessions_为例)

void do_msg_type_1_(MsgData &msg)
{
  int id=msg.sessionid;
  if(mapSessions_.find(id)!=mapSessions_.end())
  {
    session *pSession=mapSessions_[id];
    pSession->on_msg_1(msg);
  }
  else
  {
    write_warning_log;
  }
}
  如上例所示,session管理类对消息的处理方式必须简单固定,方便可持续维护、扩充。
  2、Session的职责
  (1)数据结构:每一路连接的业务处理部分,首先有sessionid_标记session本身,其次包含业务处理需要的必须的数据部分,另外最重要的一个数据结构就是状态机了。
  (2)状态机。状态机标识session对象的状态,接收外部输入的事件,驱动状态机运行,并作出行为响应。详细见《服务器设计系列:状态机》,不多说了。
  (3)方法。主要分两类:on_msg和do_event。举例如下:

void on_msg...(MsgData &msg)
{
    EventData event;
    event.detail=msg.detail;//todo
    fsm.do_event(event);
}
void do_event...(EventData &event)
{
    //change session's data or send msg to other thread or response request or others.. 
}
      注意:
      (1)所有有可能改变session内蕴状态的操作都必须纳入状态机的严格控制,不能存在不通过状态机即可改变session内蕴状态的操作入口。
      (2)如果两个状态对同样的事件做出同样的反应,并且都迁移到相同的状态,那么这两个是同一个状态。
      (3)尽可能减少状态的个数。如果两个状态具有严格的时序关系,处理的事件不同并且有严格的时序关系,那么考虑合并这两个状态,防止状态机膨胀。
  总结:理解业务部分,先区分消息(Msg)和事件(Event)。
  1、消息(Msg):是指线程之间传递的数据结构,即被投递到线程消息队列中的数据结构。SessionManager主要职责是接收消息,通过消息映射,找到处理该消息的函数,该函数根据消息中携带的sessionid,找到session对象,调用该session的消息处理函数继续后续的处理。
  2、事件(Event):是指被session对象中的状态机处理的数据结构。该数据结构,在session的消息处理函数中通过消息的内容拼凑,之后交给状态机对象处理,状态机对象根据事件类型,通过事件映射,找到处理该事件的函数,继续该事件的处理。
  3、代码流程:一般情况下(逻辑控制部分)其它线程不能直接调用session对象,正确的调用方式是发消息SessionManager,SessionManager根据消息找到session对象再进行后续处理。
  4、代码要写的足够呆板。 写出好的系统关键在于对业务的理解,不在于对代码技巧的玩弄。

参考文献:
服务器技术系列综述:http://www.cppblog.com/CppExplore/MyPosts.html

  • 1
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
服务器概要设计说明全文共5页,当前为第1页。服务器概要设计说明全文共5页,当前为第1页。 服务器概要设计说明全文共5页,当前为第1页。 服务器概要设计说明全文共5页,当前为第1页。 目录 功能概述 2 网络通信层 3 连接生命周期的管理 3 接口 3 异步IO缓冲内存池 3 本地数据与字节流数据的互相转换 4 信令和通信数据结构 5 伪代码定义 5 命令管理 7 数据有效性检测 8 文件传输通道 9 日志 10 功能概述 服务器主要业务功能是连接物管和终端,为社区物管和管理中心提供管理功能,使其能够统一管理终端。 服务器的功能模块包括: 数据管理,数据包括房屋数据、住户数据、配租数据、门禁卡数据、终端配置数据等; 状态管理,服务器需要维持物管和终端的连接,保持连接状态的可增删改查; 命令管理,物管和终端之间的交互命令有确认机制,命令通过服务器传递,服务器需要保证命令传递的可靠性; 数据有效性检测,服务器需要定期检测一些数据的有效性,包括配租数据是否〔临近〕到期、门禁卡白名单数据与终端定期交换等; 文件传输通道,包括软件版本升级、数据文件传输等; 日志。 网络通信层 通信层负责业务命令和数据的发送接收。由于物管、终端和服务器之间命令和数据需要精确送达,所有业务都采用TCP来实现。 IOCP模型是Windows服务器开发中性能最好的非阻塞异步IO模型,所以通信层采用IOCP模型构建。Windows下有五种非阻塞I/O模型:选择〔Select〕、异步选择〔WSAAsyncSelect〕、事件选择〔WSAEventSelect〕、重叠I/O〔Overlapped I/O〕和完成端服务器概要设计说明全文共5页,当前为第2页。服务器概要设计说明全文共5页,当前为第2页。口〔Completion Port>。Select是同步IO模型,同时处理的任务有限〔上限1024〕,不符合处理成千上万连接的要求;WSAAsyncSelect也是同步IO模型,以接收Windows消息为基础,不符合服务器控制台程序要求;WSAEventSelect也是同步IO模型,需要创建与连接数等同的事件内核对象,资源未能高效利用,也排除在外;上面三种IO模型其实是一回事,都是类select模型,适合开发小型服务器或者客户端程序,而不适合需要接受成千上万连接的服务器程序。Overlapped I/O是异步IO模型,但是它需要程序员关心线程池的实现和调度〔类似Linux下面的epoll模型,但是epoll是同步IO模型〕;而IOCP克服了上面四种模型的缺点,对实现XX接数的服务器有可靠的性能和较少的资源占用,而且伸缩性比较强,占用资源数跟连接数量相关,甚至可以用在客户端程序上面。 服务器概要设计说明全文共5页,当前为第2页。 服务器概要设计说明全文共5页,当前为第2页。 连接生命周期的管理 C++语言没有对象回收〔GC〕机制,生命周期的管理和防止内存泄露需要程序自己实现,而一条连接从产生后到销毁的过程中会有多个线程同时对其进行操作,同时读写甚至同时关闭,对象的多线程同步也需要程序实现。这里采用智能指针〔shared_ptr, stl_c++11〕来管理连接的生命周期,通信层维护各个连接在内存中唯一一份数据,同时提供引用计数,统计当前该数据被外界使用情况,当外界没有角色再需要该数据时〔引用计数减到0〕,通信层会删除这份数据,同时表明该连接生命周期终止。 接口 数据接口采用handle/body手法,连接的handle采用整形数据,body采用C++对象封装连接数据,数据包含SOCKET句柄、连接状态和当前接收缓存〔业务层〕等。连接生命周期反映到handle上表现为该handle是否为有效。发送内存采用智能指针〔unique_ptr, stl_c++11〕进行传递,这里用到了智能指针对数据和数据析构的封装,发送完成之后直接调用其删除器〔deleter〕进行内存的删除,这样上下层之间就避免了一次内存拷贝。 回调接口为C++接口〔纯虚函数〕。 异步IO缓冲内存池 由于系统层和stl层容器都实现了小内存内存池,所以程序将不再实现自己的内存池,发送缓冲内存完全动态分配,接收缓冲内存每个连接有一份,也通过动态分配而来。 本地数据与字节流数据的互相转换 本地数据转 为字节流数据时,根据本地数据大小构造字节流对象,然后将本地数据逐字节填入流中,可变数组先填入数组大小再逐个填充数组内容。 字节流数据转换为本地数据时,根据字节流中标识的大小动态构造本地数据,构造时使用智能指针〔unique_ptr, stl_c++11〕管理数据,加上C++多态特性,可以大大简化内存的管理。 服务器概要设计说明全文共5页,当前为第3页。服务器概要设计说明全文共5页,当前为第3页。信令和通信数据结构 服务器概要
毕业论文文献综述 信息与计算科学 基于JAVA的学生通讯录管理系统设计和实现 一、前言部分 Java是由Sun公司于1995年5月推出的Java程序设计语言和Java平台的总称。它具有 简洁、安全、面向对象、动态、体系结构中立、可移植、分布式、平台无关性等多个优 点,被广泛地运用到计算机、便携式计算机、电视、电话、手机和其他大量设备上。Ja va的前景被广泛地看好,并有许多相关机构和人员在解决其运行速度等瑕疵[1]。 二、主题部分 一、Java简介 Java是由Sun Microsystems公司于1995年5月推出的Java程序设计语言和Java平台的 总称。用Java实现的HotJava浏览器(支持Java applet)显示了Java的魅力:跨平台、 动态的Web、Internet计算。从此,Java被广泛接受并推动了Web的迅速发展,常用的浏 览器现在均支持Java applet[2]。 (一)平台架构 Java平台由Java虚拟机(Java Virtual Machine)和Java应用编程接口(Applicat ion Programming Interface、简称API)构成。Java应用编程接口为Java应用提供了一 个独立于操作系统的标准接口,可分为基本部分和扩展部分。在硬件或操作系统平台上 安装一个Java平台之后,Java应用程序就可运行。现在Java平台已经嵌入了几乎所有的 操作系统。这样Java程序可以只编译一次,就可以在各种系统中运行。 Java分为JavaSE, JavaEE,JavaME三个体系[3]。 Java SE允许开发和部署在桌面、服务器、嵌入式环境和实时环境中使用的Java应用 程序,它包含了支持Java Web服务开发的类,并为Java Platform,Enterprise Editio n(Java EE)提供基础。Java EE帮助开发和部署可移植、健壮、可伸缩且安全的服务器 端Java应用程序,它是在Java SE的基础上构建的,它提供Web服务、组件模型、管理和 通信API,可以用来实现企业级的面向服务体系结构(SOA)和Web 2。0应用程序。Java ME为在移动设备和嵌入式设备(比如手机、PDA、电视机顶盒和打印机)上运行的应用 程序提供一个健壮且灵活的环境,它包括灵活的用户界面、健壮的安全模型、许多内置 的网络协议以及对可以动态下载的连网和离线应用程序的丰富支持。基于Java ME规范的 应用程序只需编写一次,就可以用于许多设备,而且可以利用每个设备的本机功能[4]。 (二)Java语言的特点[5] 1、Java语言是简单的。Java语言的语法与C语言和C++语言很接近,使得大多数程序 员很容易学习和使用Java。另一方面,Java丢弃了C++中很少使用的、很难理解的、令人 迷惑的那些特性,如操作符重载、多继承、自动的强制类型转换。特别地,Java语言不 使用指针,并提供了自动的废料收集,使得程序员不必为内存管理而担忧。 2、Java语言是一个面向对象的。Java语言提供类、接口和继承等原语,为了简单起 见,只支持类之间的单继承,但支持接口之间的多继承,并支持类与接口之间的实现机 制。Java语言全面支持动态绑定,而C++语言只对虚函数使用动态绑定。 3、Java语言是分布式的。Java语言支持Internet应用的开发,在基本的Java应用编 程接口中有一个网络应用编程接口(java net),它提供了用于网络应用编程的类库, 包括URL、URLConnection、Socket、ServerSocket等。Java的RMI(远程方法激活)机制也 是开发分布式应用的重要手段。 4、Java语言是健壮的。Java的强类型机制、异常处理、废料的自动收集等是Java程 序健壮性的重要保证。对指针的丢弃是Java的明智选择。Java的安全检查机制使得Java 更具健壮性。 5、Java语言是安全的。Java通常被用在网络环境中,为此,Java提供了一个安全机 制以防恶意代码的攻击。除了Java语言具有的许多安全特性以外,Java对通过网络下载 的类具有一个安全防范机制(类ClassLoader),如分配不同的名字空间以防替代本地的 同名类、字节代码检查,并提供安全管理机制让Java应用设置安全哨兵。 6、Java语言是体系结构中立的。Java程序(后缀为java的文件)在Java平台上被编 译为体系结构中立的字节码格式(后缀为class的文件),然后可以在实现这个Java平台 的任何系统中运行。这种途径适合于异构的网络环境和软件的分发。 7、Java语言是可移植的。这种可移植性来源于体系结构中立性,另外,Java还严格 规定了各个基本数据类型的长度。Java系统本身也具

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值