使用JAVA建立稳定的多线程服务器

原创 2002年08月21日 09:41:00
使用JAVA建立稳定的多线程服务器
内容:
消息系统的建立
服务器的结构
端口监听线程类PORTListenThread
单个客户端在连接池中的映像类ClientSingle
分组转发的实现类Group
主服务器类Server
总结
关于作者
Java 专区中还有:
教学
工具与产品
代码与组件
所有文章
实用技巧

侯光敏 (wearebug@etang.com)

2002 年 7 月

本文详细的介绍了使用Java语言建立一套多线程服务器的过程,该服务器使用对象传递消息,在线程中使用队列机制,使服务器的性能大大提高了。这套服务器可以被用于各种C/S或B/S结构的应用程序中。

Java语言是完全面向对象的,它的线程机制和对象序列化特别容易使用,使用Java来建立一套多线程服务器要比使用其它语言方便的多,如果你再把它的异常处理机制利用好,那么你就可以建立一个商业级的多线程服务器了。由于采用了消息队列和Socket传输方式,所以不会出现丢消息的问题。这套服务器可以作为实时聊天服务器、多人协同的协作服务器等等。

消息系统的建立

这套服务器的消息系统采用的是对象传输的机制,而不是以前常常使用的字符串传输。采用对象传输的好处是扩展方便,如需要建立一个新的消息只需要从一个统一的基类继承下来,然后再写自己实现的方法就行了。这样也符合面向对象领域里一条重要的原则:OCP(open_closed Principle),即一个好的设计应该能够容纳新的功能的增加,但是增加的方式不是修改原有的类,而是添加新的类。

首先建立一个基类:Msg,该抽象类中有两个域sender和receiver分别纪录消息的发送者和接收者。这两个域是在构造消息类时就填写的,receiver域可以为空,空表示发给谁都可以,由转发服务器来决定。该类的方法包括取得这两个域的值和消息的处理函数。消息的处理函数process()是空函数,供继承者重载。

建立了这个抽象基类后,你就可以继承它完成你自己的类。举个例子,假如我要建立一个分组协同工作的绘图系统,而且支持组员之间的对话,那么我可以建立如下的类集合:

SendTextMsg(String sender,String receiver,String info)//向指定的人发送对话。
AddLineMsg(String sender,Point a,Point b)//在指定的点之间绘制一条直线
AddRectangle(String sender,point start,Point end)//建立指定的矩形
AddRotundaMsg(String sender,Point center,int radius)//建立指定的圆
RemoveObjectMsg(String sender,int ID)//删除指定编号的图形对象
……

以此类推,可以建立很多的消息类。在每个类的内部都由一个处理该类的方法process(),填写该方法就可以实现对消息类的处理,而服务器只负责完成消息的转发功能。这样,一套消息系统就建立了。

服务器的结构

如果要服务器实现同时为每个客户端服务,就要使用多线程,建立一个线程池,当有客户端连接时就在池中开辟一个线程为它服务。同样,要避免大量消息到达时处理不过来而导致丢失的情况,就要使用消息队列。这个服务器是分层的处理的。

类关系图如下所示:

服务器的工作过程是这样的,建立了一个Server类作为主类,它含有程序的入口函数main()。在构造函数中初始化一个数组存放ClientSingle类,它其实就是单独处理一个连接用户的类。然后启动一个线程PORTListenThread,该线程的作用就是监听端口上有没有人登陆,当有人连接时交给Server的addClient()处理。Server的addClient()方法会在刚才那个数组中建立一个ClientSingle对象,然后把剩下的事都交给它做。

端口监听线程类PORTListenThread

该线程类在run()函数的开始部分首先要检查serverScoket是否为空,保证循环开始时不要出错。然后进入一个死循环的监听:

while(true) { //死循环监
  try{Socket clientSocket=null;
      clientSocket=serverSocket.accept();
      server.addClient(clientSocket);//转交Server处理
  }
  catch (IOException e){System.out.println("监听端口时出错"+e);}//显示错误
}

单个客户端在连接池中的映像类ClientSingle

每一个客户端连接到服务器后,服务器会自动在连接池中建立该客户端的一个映像,所有的操作都交给这个映像去具体执行,所以ClientSingle中一定要包含客户端的一些基本的信息。比如客户端的名称、登陆时间等等。在该类中有两个消息队列sendQueue(发送队列)和receiveQueue(接收队列)缓存消息。

ClientSingle类是继承自Thread的,它还是一个调用者。在初始化的时候启动两个子线程类SingleSender和SingleListener运行。SingleSender负责监听指令发送队列中有没有指令,有则发送;SingleListener负责监听有没有消息到达,有则把这些消息加入到接收队列中去,由ClientSingle处理。所以ClientSingle的主要任务就是对这两个队列的处理。这两个队列可以用Vector实现,非常地简单。

//-------将消息加入发送队列中------------
synchronized void send(Object o)
{ sendQueue.add(o);
}

为了稳定控制子线程的运行,并不鼓励在run()方法的死循环标志都用true,而是使用了一个布尔型的变量finish。外部可以通过把这个标志置为假而停止线程的运行。

发送子线程类启动后执行run()中的循环(以finish为结束标志),在该循环内首先判断ClientSingle中的发送队列是否为空,为空时睡眠一定的时间再重新判断,这也是一个while循环。不为空则开始处理队列中的消息,把它取出后放入输出流中发送。

public void run(){
    while (!father.finish){ //循环监听
      while(father.v.isEmpty()){ //当发送队列为空的时候线程睡眠500毫秒
        try{Thread.sleep(500);}
catch(InterruptedException e){System.out.println(e);}
      }
      if (!father.v.isEmpty()){  //发送队列不为空时
        try{
          Object a=father.v.firstElement();//取出队列中的第一个消息
          father.v.removeElementAt(0);//从队列中删除
          oos.writeObject(a);//发送该消息
          oos.flush();
        }catch(IOException e){
          displayMessage(" 传输失败 !");
          father.finish=false;
        }
      }
    }
}

接收子线程SingleListener类和发送子线程是类似的,它们的run()方法都差不多。不同的是接收子线程把收到的消息加入到ClientSingle的接收队列中去,由它处理。

ClientSingle类的run()方法就在循环地读取接收队列receiveQueue中的内容,为空时等待;不为空时依次取出处理和转发。处理消息的函数是processMsg(),它只是执行消息类自己的process()方法罢了。在处理完后,会调用Server类的方法进行各种类型的转发。

分组转发的实现类Group

为了实现对客户端分组,我建立了Group类。在这个类中有一个列表存放已经存在于连接池中的那些ClientSingle类的引址。只要遍历整个列表就能访问所有组中的成员。这个列表可以用Vector实现,也可以用哈希表,我推荐后者,主要是为了能够按名字存取。

组对象本身也是可以存在Server类的组列表中的。

分组功能对多人的协同系统来说是非常重要的,特别是分组对某一个共享空间操作的时候。就以上面的协同绘图系统为例,如果10个人里有三个人要另起炉灶,那么他们三个的画板就不能让其他人看到,这就必须有"组"个划分。

主服务器类Server

Server类是最核心的类,它在这个框架中起到调度全局的作用,上面介绍的那些类都由它来统一的构造和调用。

Server类的域包括一个定长的数组存放ClientSingle实例,它就是连接池的实现。还要有一个哈希表存放Group实例。Server类的方法都是对这两个类的操作。

建立ClientSingle数组的目的是保证服务器的稳定性。其实,你也可以选择不建立它,只是动态地构造对象,但是那样不好管理连接的用户,而且由于各种操作系统对进程的处理不同,动态建立服务线程会很不稳定。所以我先建立一个数组作为这些对象的容器,在开始时就估计好连接者的最大数量。Server类的addClient()函数:

void addClient(Socket socket){
    int c=0;
    try{while (sch[c]!=null) c++;}//搜索数组中的空余空间
    catch(ArrayIndexOutOfBoundsException e){
      try{ socket.close();}//出现异常关闭槽连接
      catch(IOException ee){ System.out.println("数组溢出");}
      return;
    }
    sch[c]=new ClientSingle(c,socket,father,this);//在搜索到的位置建立ClientSingle对象
}

Server类中转发的方法有:sendToAll()、sendToOne()、sendToGroup()等等。这些方法都是对线程池中的方法的操作,比较简单,不外乎都是找到线程池中的某个ClientSingle对象,然后调用它的send()方法罢了。

注意,这些转发的方法可能被很多子线程同时调用,所以为了保持线程的稳定,千万记住要在方法前加synchronized关键字。

总结

通过上面的描述你可以发现,要建立稳定的服务器程序,消息队列和线程池是很重要的。此外,也要考虑到很多的意外情况的发生。一般的程序员在写完线程的run()方法的循环后就不管了,其实还应该考虑跳出循环后的资源释放等等问题。

这只是我在编程中总结出来的一些比较初级的经验,希望能够抛砖引玉。

使用JAVA建立稳定的多线程服务器

侯光敏 (wearebug@etang.com), 简介: 本文详细的介绍了使用Java语言建立一套多线程服务器的过程,该服务器使用对象传递消息,在线程中使用队列机制,使服务器的性能大大...
  • novelly
  • novelly
  • 2013年10月07日 22:11
  • 537

基于tcp服务器的多线程版-java-简单

 /** * TCPServerMutiThread */import java.io.*;import java.net.*;public class TCPServerMutiThread imp...
  • york_software
  • york_software
  • 2010年04月20日 21:21
  • 770

vc socket 多线程 (记录一次自己调用window 底层API编写 vc socket 多线程的服务器网络程序)

我写的上一个网络程序是调用MFC库的CAsyncSocket(异步非阻塞)类完成的,根据网上的描述,这种类是基于Window的消息机制实现的,收到socket消息会调用OnXX(例如:Onreceiv...
  • u013030599
  • u013030599
  • 2016年08月25日 15:18
  • 516

用Java构建稳定的Ftp服务器 (3)

下面我们要处理用户连接,也就是FtpConnection类。Ftp连接本质上是一个状态机,当FtpConnection接收到用户命令后,根据当前状态决定响应及下一个状态。不过我们不需要考虑实现一个复杂...
  • asklxf
  • asklxf
  • 2004年08月18日 21:48
  • 3128

服务端编程中多线程的应用

本文是陈硕的《Linux多线程服务端编程  使用muduo C++网络库》一书中,第三章的读书笔记。其中暗红颜色的文字是自己的理解,鲜红颜色的文字表示原书中需要注意的地方。   一:进程和线程 ...
  • gqtcgq
  • gqtcgq
  • 2016年08月11日 18:52
  • 2626

【Java TCP/IP Socket】基于线程池的TCP服务器(含代码)

了解线程池      在http://blog.csdn.net/ns_code/article/details/14105457(读书笔记一:TCP Socket)这篇博文中,服务器端采用的实现方...
  • mmc_maodun
  • mmc_maodun
  • 2013年11月08日 08:23
  • 20090

Java Socket实现一个简单的多线程回显服务器。

需要两个类,一个是EchoServer,代表服务器。另外一个是EchoServerClient,代表客户端。代码如下: package interview; import java.io.Buffe...
  • zwcwu31
  • zwcwu31
  • 2017年06月08日 14:02
  • 493

Java 用线程池实现web服务器

今天在应用中用到了线程池,于是我就想web服务器肯定是用到了线程池,但是他们是怎样实现的呢?于是我就试着写了一个简单的模型package httpserver;import java.io.IOExc...
  • googlevsbing
  • googlevsbing
  • 2015年07月27日 21:29
  • 1269

java socket服务器客户端多线程小例子

java socket服务器客户端多线程小例子
  • wild46cat
  • wild46cat
  • 2016年09月12日 15:25
  • 1744

易语言多线程

易语言多线程之启动线程  http://www.c0ks.com/thread-1357-1-1.html  (出处: 从零开始学编程)  易语言多线程之关闭线程句柄  http://www....
  • lzhergd
  • lzhergd
  • 2016年09月13日 16:37
  • 576
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:使用JAVA建立稳定的多线程服务器
举报原因:
原因补充:

(最多只允许输入30个字)