摘要:
与一般的ASP聊天室不同,这种聊天服务器是完全独立的服务端程序。当它运行的时候先监听端口,一旦用户通过浏览器访问,便模拟WEB服务器通过HTTP和用户通讯。由于使用了Java技术,所以安全性以及速度上比起一般的ASP聊天室优势明显。
以下列出了eChat聊天服务器的技术特点:
1)
跨平台:可以稳定运行在Liunx下
2)
多线程:提高了聊天服务器的整体性能
3)
Server Push:使客户与服务器处于不间断状态,实现了“无刷新”聊天
一、
主要类的说明
在介绍具体模型之前,我要把涉及到的几个主要类先说明下:
1)
ChatServer
整个程序的主线程,由它来控制其它线程的开始
2)
TextChatServer
文本聊天线程,以后可以扩展语音聊天等
3)
Room
房间类,保存了所有和房间有关的资料(包括房间名、房间端口、用户列表等)
4)
RoomListenThread
:
房间监听线程,接受所有连接此房间的客户Socket
5)
SysMsgThread
系统消息发送线程,通过它向房间广播系统消息(登陆信息或广告)
6)
ClientThread
客户连接线程,每个用户都会和服务器建立一个Socket连接
7)
ClientManageThread
客户跟踪线程,用来监视客户的连接情况(因为read方法会引起阻塞,所以必须为每个用户分配一个单独线程)
8)
User
用户类,保存登陆用户的所以信息
9)Actions
我定义了7种Action,这个类是具体的实现(具体的看第三章)
10)Config.xml
系统配置文件,包括每个房间的详细配置(范例如下)
<配置信息>
<房间>
<房间名称>
eChat
聊天室
</房间名称>
<管理员>
James </管理员>
<讨论主题>
山上的朋友,你们好!</讨论主题>
<端口>
3166 </端口>
<人数限制>
999 </人数限制>
</房间>
</配置信息>
|
二、
线程模型
线程模型设计如下:

程序运行的流程如下:
1)
聊天服务器主线程从Config.xml中读取所有房间的信息
2)
根据配置文件中的信息分别建立Room对象,并把信息传送给Room对象
3)
根据Room对象中的port属性,建立房间监听线程和系统消息线程
4)
当用户连接服务器时,房间监听线程accept后与客户建立Socket连接,并建立客户连接线程(每个用户都有一个单独的连接线程)
5)
随后要为每个用户建立一个用户跟踪线程来判断是否在线(详细原因看第五章)
这样一个完整的线程模型就建立了,而客户端与服务端此后就通过Socket进行通讯。
三、
Action
模型
客户端和服务端之间的交互比较复杂,在这里我定义了7种action并在Actions类中给出了具体的实现。Actions的定义如下:
1)
Index
:
处理
index
动作
[
登陆区
]
2)
Main:
处理
main
动作
[
框架区
]
3)
Talk:
处理
talk
动作
[
输入区
]
4)
list:
处理
list
动作
[
用户在线列表
]
5)
Login:
验证用户并登陆
6)
Send:
处理发送请求
7)
outInfo:
处理
outInfo
动作
每个Actions对应请求页面如下:


客户端与服务端之间通讯是基于HTTP协议,客户端发出连接请求后。服务端返回HTTP消息头。然后,客户端就会发出GET/POST请求。服务端接收到客户端的参数后再根据actions的不同进行响应,并在action中判断是否断开Socket连接。代码片断如下:
//
返回参数
Params
HttpParser requestParam = new HttpParser (_clientSocket);
HashMap _param = requestParam.GetParams ();
//
创建
Actions
对象
Actions action = new Actions (_out, _param, _room);
if ( requestParam.action != null ){
//System.out.println("
acton
:"+requestParam.action);
if ( requestParam.action.equals ("") ){
action.index ();
} else if ( requestParam.action.equals ("main") ){
action.main ();
} else if ( requestParam.action.equals ("login") ){
action.login ();
} else if ( requestParam.action.equals ("talk") ){
action.talk ();
} else if ( requestParam.action.equals ("send") ){
action.send ();
} else if ( requestParam.action.equals ("chat") ){
action.chat ();
} else if ( requestParam.action.equals ("list") ){
action.list ();
} else if ( requestParam.action.equals ("outinfo") ){
//
把这个一直不断开的连接加入
User
对象
action.outInfo (_clientSocket);
return; //
不断开连接
} else{
//
未知
Action
就转入
index
action.index ();
}
}
_out.flush ();
//
断开连接
_clientSocket.close ();
|
这就是一个完整的动作处理模型
四、
Server Push
技术
需要说明下:我实现聊天服务器是用Server Push技术来保证“无刷新”聊天。这种源自Linux下的技术到底是什么?我简单说明下:
Server Push技术的聊天室聊天室基本原理是,不使用HTTP服务器程序,由自己的Socket程序监听服务器的80端口,根据html规范,在接收到浏览器的请求以后,模仿www服务器的响应,将聊天内容发回浏览器。在浏览器看来就象浏览一个巨大的页面一样始终处于页面接收状态。也就是说,我们不再使用CGI等方式来处理聊天的内容,而采用我们自己的程序来处理所有的事务。实际上它就是一个专门的聊天服务器。
说这么多,实际上核心的地方就是:用户请求WEB页面的Socket始终不断开,就像一个巨大的页面始终处于接受状态,这样服务器就可以不间断的向客户端发送聊天信息。下面看看我的具体实现。
我的Actions模型中处理Server Push的是outinfo动作,请注意这里:
}
else if ( requestParam.action.equals ("outinfo") ){
//
把这个一直不断开的连接加入
User
对象
action.outInfo (_clientSocket);
return
;
//
不断开连接
|
我在关闭Socket连接之前就return了,所以这个请求的Socket始终保持接受状态。我可以源源不断地写入信息,这就是Server Push技术的一种应用。
五、
判断用户断线
判断用户是否短线是个比较棘手的问题。(我一直没有找到更好的方法,只能用老土方法了)
当用户正常关闭浏览器或者把页面转到其他地方时,客户端和服务端一直保持连接的Socket实际上并没有断,只是不能够进行写操作。所以可以用以下代码判断:
try
{
BufferedInputStream _in;
_in = new BufferedInputStream (clientSocket.getInputStream ());
// read()
方法会引起阻塞
,
所以必须为每个用户分配一个单独线程
(
会影响效率
)
_in.read ();
}
catch ( IOException e ){
DelUser (clientSocket);
}
|
当read方法发生异常时可以捕捉到,然后把用户删除掉。头疼的是read()方法是阻塞的(可以用Java.NIO解决)如果用一个线程来管理显然不合适,所以我给每个用户线程又配了个跟踪线程,你要阻塞就去阻塞吧只要不影响别人就行~~
六、
结束
这里我写的还比较简单,如果您有兴趣可以去我的网站和我交流。
聊天服务器的源代码也可以在http://www.chenshen.com下载。
关于作者:
沈晨,James,高级程序员,SCJP
www.chenshen.com
James@njut.edu.cn