使用JAVA建立稳定的多线程服务器
简介: 本文详细的介绍了使用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对象,然后把剩下的事都交给它做。
该线程类在run()函数的开始部分首先要检查serverScoket是否为空,保证循环开始时不要出错。然后进入一个死循环的监听:
while(true) { //死循环监 |
每一个客户端连接到服务器后,服务器会自动在连接池中建立该客户端的一个映像,所有的操作都交给这个映像去具体执行,所以ClientSingle中一定要包含客户端的一些基本的信息。比如客户端的名称、登陆时间等等。在该类中有两个消息队列sendQueue(发送队列)和receiveQueue(接收队列)缓存消息。
ClientSingle类是继承自Thread的,它还是一个调用者。在初始化的时候启动两个子线程类SingleSender和SingleListener运行。SingleSender负责监听指令发送队列中有没有指令,有则发送;SingleListener负责监听有没有消息到达,有则把这些消息加入到接收队列中去,由ClientSingle处理。所以ClientSingle的主要任务就是对这两个队列的处理。这两个队列可以用Vector实现,非常地简单。
//-------将消息加入发送队列中------------ |
为了稳定控制子线程的运行,并不鼓励在run()方法的死循环标志都用true,而是使用了一个布尔型的变量finish。外部可以通过把这个标志置为假而停止线程的运行。
发送子线程类启动后执行run()中的循环(以finish为结束标志),在该循环内首先判断ClientSingle中的发送队列是否为空,为空时睡眠一定的时间再重新判断,这也是一个while循环。不为空则开始处理队列中的消息,把它取出后放入输出流中发送。
public void run(){ |
接收子线程SingleListener类和发送子线程是类似的,它们的run()方法都差不多。不同的是接收子线程把收到的消息加入到ClientSingle的接收队列中去,由它处理。
ClientSingle类的run()方法就在循环地读取接收队列receiveQueue中的内容,为空时等待;不为空时依次取出处理和转发。处理消息的函数是processMsg(),它只是执行消息类自己的process()方法罢了。在处理完后,会调用Server类的方法进行各种类型的转发。
为了实现对客户端分组,我建立了Group类。在这个类中有一个列表存放已经存在于连接池中的那些ClientSingle类的引址。只要遍历整个列表就能访问所有组中的成员。这个列表可以用Vector实现,也可以用哈希表,我推荐后者,主要是为了能够按名字存取。
组对象本身也是可以存在Server类的组列表中的。
分组功能对多人的协同系统来说是非常重要的,特别是分组对某一个共享空间操作的时候。就以上面的协同绘图系统为例,如果10个人里有三个人要另起炉灶,那么他们三个的画板就不能让其他人看到,这就必须有"组"个划分。
Server类是最核心的类,它在这个框架中起到调度全局的作用,上面介绍的那些类都由它来统一的构造和调用。
Server类的域包括一个定长的数组存放ClientSingle实例,它就是连接池的实现。还要有一个哈希表存放Group实例。Server类的方法都是对这两个类的操作。
建立ClientSingle数组的目的是保证服务器的稳定性。其实,你也可以选择不建立它,只是动态地构造对象,但是那样不好管理连接的用户,而且由于各种操作系统对进程的处理不同,动态建立服务线程会很不稳定。所以我先建立一个数组作为这些对象的容器,在开始时就估计好连接者的最大数量。Server类的addClient()函数:
void addClient(Socket socket){ |
Server类中转发的方法有:sendToAll()、sendToOne()、sendToGroup()等等。这些方法都是对线程池中的方法的操作,比较简单,不外乎都是找到线程池中的某个ClientSingle对象,然后调用它的send()方法罢了。
注意,这些转发的方法可能被很多子线程同时调用,所以为了保持线程的稳定,千万记住要在方法前加synchronized关键字。
通过上面的描述你可以发现,要建立稳定的服务器程序,消息队列和线程池是很重要的。此外,也要考虑到很多的意外情况的发生。一般的程序员在写完线程的run()方法的循环后就不管了,其实还应该考虑跳出循环后的资源释放等等问题。
这只是我在编程中总结出来的一些比较初级的经验,希望能够抛砖引玉。
侯光敏,毕业于天津理工学院计算机科学与工程系,现在北京某公司从事软件开发工作,使用java编程。你可以通过wearebug@etang.com和我联系。
使用JAVA建立稳定的多线程服务器的更多相关文章
- 用Java实现多线程服务器程序
一.Java中的服务器程序与多线程 在Java之前,没有一种主流编程语言能够提供对高级网络编程的固有支持.在其他语言环境中,实现网络程序往往需要深入依赖于操作平台的网络API的技术中去,而Java提供 ...
- Java如何创建多线程服务器?
在Java编程中,如何创建多线程服务器? 以下示例演示如何使用ServerSocket类的MultiThreadServer(socketname)方法和Socket类的ssock.accept()方 ...
- 基于事件的 NIO 多线程服务器--转载
JDK1.4 的 NIO 有效解决了原有流式 IO 存在的线程开销的问题,在 NIO 中使用多线程,主要目的已不是为了应对每个客户端请求而分配独立的服务线程,而是通过多线程充分使用用多个 CPU 的处 ...
- TCP粘包/拆包 ByteBuf和channel 如果没有Netty? 传统的多线程服务器,这个也是Apache处理请求的模式
通俗地讲,Netty 能做什么? - 知乎 https://www.zhihu.com/question/24322387 谢邀.netty是一套在java NIO的基础上封装的便于用户开发网络应用程 ...
- java 并发性和多线程 -- 读感 (一 线程的基本概念部分)
1.目录略览 线程的基本概念:介绍线程的优点,代价,并发编程的模型.如何创建运行java 线程. 线程间通讯的机制:竞态条件与临界区,线程安全和共享资源与不可变性.java内存模型 ...
- Java 并发性和多线程
一.介绍 在过去单 CPU 时代,单任务在一个时间点只能执行单一程序.之后发展到多任务阶段,计算机能在同一时间点并行执行多任务或多进程.虽然并不是真正意义上的“同一时间点”,而是多个任务或进程共享一个 ...
- Java并发性和多线程
Java并发性和多线程介绍 java并发性和多线程介绍: 单个程序内运行多个线程,多任务并发运行 多线程优点: 高效运行,多组件并行.读->操作->写: 程序设计的简单性,遇到多问题, ...
- Java并发性和多线程介绍
java并发性和多线程介绍: 单个程序内运行多个线程,多任务并发运行 多线程优点: 高效运行,多组件并行.读->操作->写: 程序设计的简单性,遇到多问题,多开线程就好: 快速响应,异步式 ...
- UDP和多线程服务器
UDP: UDP是数据报文传输协议,这个传输协议比较野蛮,发送端不需要理会接收端是否存在,直接就发送数据,不会像TCP协议一样建立连接.如果接收端不存在的话,发送的数据就会丢失,UDP协议不会去理会数 ...
- 在java中怎样实现多线程?线程的4种状态
一.在java中怎样实现多线程? extends Thread implement Runnable 方法一:继承 Thread 类,覆盖方法 run(),我们在创建的 Thread 类的子类中重写 ...
随机推荐
- Ollama + JuiceFS:一次拉取,到处运行
今天这篇博客转载自我们的全栈工程师朱唯唯.在使用 Ollma 进行大模型加载时,她尝试使用了 JuiceFS 进行模型共享,JuiceFS 的数据预热和分布式缓存功能显著提升了加载效率,优化了性能瓶颈 ...
- 使用vue-cli4快速搭建项目环境、使用webpack4打包自己的library类库、封装vue插件并发布
快速创建 使用官方推荐的vue-cli创建项目如下: # 安装 Vue Cli npm install -g @vue/cli # 创建一个项目 vue create vanttest # 创建完成后 ...
- Spring框架漏洞总结
目录 SpEL注入攻击 Spring H2 Database Console未授权访问 Spring Security OAuth2远程命令执行漏洞(CVE-2016-4977) Spring Web ...
- [OI] 指针与迭代器
取地址与解引用 一般来说,我们有一个取地址符 & 可以返回该变量的地址. int main(){ int a; cout<<&a; } 0x6ffe1c 如果我们现在有一个 ...
- react native 环境搭建遇到问题
关于pod install 慢的要死的问题 解决方法记录 开始时显示ruby有问题,卸载cocoapods重装的 然后启动不起来,到ios目录下执行 pod install 然后太慢尝 ...
- /sys/kernel/debug/binder/目录下主要节点含义
/sys/kernel/debug/binder/目录下主要节点含义 state 显示binder设备的整体状态信息 包括进程数量.线程数量.待处理事务数量等 stats 展示binder操作的统计信 ...
- 2024年1月中国数据库排行榜: OPOT 组合续写贺新年,达梦、腾讯发力迎升势
2024年开局,墨天轮中国数据库流行度排行火热出炉,292个国产数据库齐聚榜单.整体来看,榜单前十整体变化不大,"O-P-O"格局稳固,前五位名次未发生变动.但新年伊始,各家数据库 ...
- ByConity与主流开源OLAP引擎(Clickhouse、Doris、Presto)性能对比分析
引言: 随着数据量和数据复杂性的不断增加,越来越多的企业开始使用OLAP(联机分析处理)引擎来处理大规模数据并提供即时分析结果.在选择OLAP引擎时,性能是一个非常重要的因素. 因此,本文将使用TPC ...
- iOS Masonry使用小结
一.Masonry简介 Masonry是一个轻量级的布局框架,它拥有自己的描述语法(采用更优雅的链式语法封装)来自动布局,具有很好可读性且同时支持iOS和Max OS X等. 二.Masonry的基本 ...
- js自动调用 click 事件
// 进入页面立即触发 (()=>{ // 兼容IE if(document.all) { document.getElementById("aid").click(); } ...