本文详细的介绍了使用Java语言建立一套多线程服务器的过程
该服务器使用对象传递消息
在线程中使用队列机制
使服务器的性能大大提高了
这套服务器可以被用于各种C/S或B/S结构的应用程序中
Java语言是完全面向对象的它的线程机制和对象序列化特别容易使用使用Java来建立一套多线程服务器要比使用其它语言方便的多如果你再把它的异常处理机制利用好那么你就可以建立一个商业级的多线程服务器了由于采用了消息队列和Socket传输方式所以不会出现丢消息的问题这套服务器可以作为实时聊天服务器多人协同的协作服务器等等
消息系统的建立
这套服务器的消息系统采用的是对象传输的机制而不是以前常常使用的字符串传输采用对象传输的好处是扩展方便如需要建立一个新的消息只需要从一个统一的基类继承下来然后再写自己实现的方法就行了这样也符合面向对象领域里一条重要的原则OCP(open_closed Principle)即一个好的设计应该能够容纳新的功能的增加但是增加的方式不是修改原有的类而是添加新的类
首先建立一个基类Msg该抽象类中有两个域sender和receiver分别纪录消息的发送者和接收者这两个域是在构造消息类时就填写的receiver域可以为空空表示发给谁都可以由转发服务器来决定该类的方法包括取得这两个域的值和消息的处理函数消息的处理函数process()是空函数供继承者重载
建立了这个抽象基类后你就可以继承它完成你自己的类举个例子假如我要建立一个分组协同工作的绘图系统而且支持组员之间的对话那么我可以建立如下的类集合
SendTextMsg(String senderString receiverString info)//向指定的人发送对话
AddLineMsg(String senderPoint aPoint b)//在指定的点之间绘制一条直线
AddRectangle(String senderpoint startPoint end)//建立指定的矩形
AddRotundaMsg(String senderPoint centerint radius)//建立指定的圆
RemoveObjectMsg(String senderint ID)//删除指定编号的图形对象
……
以此类推可以建立很多的消息类在每个类的内部都由一个处理该类的方法process()填写该方法就可以实现对消息类的处理而服务器只负责完成消息的转发功能这样一套消息系统就建立了
服务器的结构
如果要服务器实现同时为每个客户端服务就要使用多线程建立一个线程池当有客户端连接时就在池中开辟一个线程为它服务同样要避免大量消息到达时处理不过来而导致丢失的情况就要使用消息队列这个服务器是分层的处理的
服务器的工作过程是这样的建立了一个Server类作为主类它含有程序的入口函数main()在构造函数中初始化一个数组存放ClientSingle类它其实就是单独处理一个连接用户的类然后启动一个线程PORTListenThread该线程的作用就是监听端口上有没有人登陆当有人连接时交给Server的addClient()处理Server的addClient()方法会在刚才那个数组中建立一个ClientSingle对象然后把剩下的事都交给它做
端口监听线程类PORTListenThread
该线程类在run()函数的开始部分首先要检查serverScoket是否为空保证循环开始时不要出错然后进入一个死循环的监听
while(true) { //死循环监
try{Socket clientSocket=null;
clientSocket=serverSocketaccept();
serveraddClient(clientSocket);//转交Server处理
}
catch (IOException e){Systemoutprintln(监听端口时出错+e);}//显示错误
}
单个客户端在连接池中的映像类ClientSingle
每一个客户端连接到服务器后服务器会自动在连接池中建立该客户端的一个映像所有的操作都交给这个映像去具体执行所以ClientSingle中一定要包含客户端的一些基本的信息比如客户端的名称登陆时间等等在该类中有两个消息队列sendQueue(发送队列)和receiveQueue(接收队列)缓存消息
ClientSingle类是继承自Thread的它还是一个调用者在初始化的时候启动两个子线程类SingleSender和SingleListener运行SingleSender负责监听指令发送队列中有没有指令有则发送SingleListener负责监听有没有消息到达有则把这些消息加入到接收队列中去由ClientSingle处理所以ClientSingle的主要任务就是对这两个队列的处理这两个队列可以用Vector实现非常地简单
//将消息加入发送队列中
synchronized void send(Object o) { sendQueueadd(o); }
为了稳定控制子线程的运行并不鼓励在run()方法的死循环标志都用true而是使用了一个布尔型的变量finish外部可以通过把这个标志置为假而停止线程的运行
发送子线程类启动后执行run()中的循环(以finish为结束标志)在该循环内首先判断ClientSingle中的发送队列是否为空为空时睡眠一定的时间再重新判断这也是一个while循环不为空则开始处理队列中的消息把它取出后放入输出流中发送
public void run(){
while (!fatherfinish){ //循环监听
while(fathervisEmpty()){ //当发送队列为空的时候线程睡眠毫秒
try{Threadsleep();}
catch(InterruptedException e){Systemoutprintln(e);}
}
if (!fathervisEmpty()){ //发送队列不为空时
try{
Object a=fathervfirstElement();//取出队列中的第一个消息
fathervremoveElementAt();//从队列中删除
ooswriteObject(a);//发送该消息
oosflush();
}catch(IOException e){
displayMessage( 传输失败 !);
fatherfinish=false;
}
}
}
}
接收子线程SingleListener类和发送子线程是类似的它们的run()方法都差不多不同的是接收子线程把收到的消息加入到ClientSingle的接收队列中去由它处理
ClientSingle类的run()方法就在循环地读取接收队列receiveQueue中的内容为空时等待不为空时依次取出处理和转发处理消息的函数是processMsg()它只是执行消息类自己的process()方法罢了在处理完后会调用Server类的方法进行各种类型的转发
分组转发的实现类Group
为了实现对客户端分组我建立了Group类在这个类中有一个列表存放已经存在于连接池中的那些ClientSingle类的引址只要遍历整个列表就能访问所有组中的成员这个列表可以用Vector实现也可以用哈希表我推荐后者主要是为了能够按名字存取
组对象本身也是可以存在Server类的组列表中的
分组功能对多人的协同系统来说是非常重要的特别是分组对某一个共享空间操作的时候就以上面的协同绘图系统为例如果个人里有三个人要另起炉灶那么他们三个的画板就不能让其他人看到这就必须有组个划分
主服务器类Server
Server类是最核心的类它在这个框架中起到调度全局的作用上面介绍的那些类都由它来统一的构造和调用
Server类的域包括一个定长的数组存放ClientSingle实例它就是连接池的实现还要有一个哈希表存放Group实例Server类的方法都是对这两个类的操作
建立ClientSingle数组的目的是保证服务器的稳定性其实你也可以选择不建立它只是动态地构造对象但是那样不好管理连接的用户而且由于各种操作系统对进程的处理不同动态建立服务线程会很不稳定所以我先建立一个数组作为这些对象的容器在开始时就估计好连接者的最大数量Server类的addClient()函数
void addClient(Socket socket){
int c=;
try{while (sch[c]!=null) C++;}//搜索数组中的空余空间
catch(ArrayIndexOutOfBoundsException e){
try{ socketclose();}//出现异常关闭槽连接
catch(IOException ee){ Systemoutprintln(数组溢出);}
return;
}
sch[c]=new ClientSingle(csocketfatherthis);//在搜索到的位置建立ClientSingle对象
}
Server类中转发的方法有sendToAll()sendToOne()sendToGroup()等等这些方法都是对线程池中的方法的操作比较简单不外乎都是找到线程池中的某个ClientSingle对象然后调用它的send()方法罢了
注意这些转发的方法可能被很多子线程同时调用所以为了保持线程的稳定千万记住要在方法前加synchronized关键字
总结
通过上面的描述你可以发现要建立稳定的服务器程序消息队列和线程池是很重要的此外也要考虑到很多的意外情况的发生一般的程序员在写完线程的run()方法的循环后就不管了其实还应该考虑跳出循环后的资源释放等等问题