Q132284591 发表于 2017-2-10 08:23:59

一套基于http的聊天c/s结构工具(除了网页tomcat还能做什么)

在我的认识当中以前一直有一种误区认为:tomcat=web。在我看过了soap协议之后,忽然有了灵感,为什么不能用tomcat来做一个聊天软件的服务器呢?
这个具体的设计如下:
在http协议中嵌入xml(仿照soap)利用xstream把pojo转化成xml然后在服务器、客户端之间传输。如果考虑扩展性,甚至可以把客户端采用c++或者其他语言来编写,当然,其中的解析http\解析xml会比较痛苦,不知道c++有没有类似commons-httpclient\xstream的开源包。总之目前实现的客户端采用的是swt技术。
下面的几个类是一对pojo也就是通讯协议。在客户端、服务器内部使用。需要传输的时候利用xstream转化为xml传输。
协议方面主要有2组:heartbeat\upload。
HeartBeatRequest就是心跳请求,用于请求下在传送给该用户的消息,HeartBeatResponse就是返回类。uploadRequest是发送给其他用户。UploadResponse就是发送后的反馈。
因为http是无状态连接,所以每次请求都需要验证,所以任何XXXRequest都继承自Request类,其中包含验证信息,和发信人地址。protocol的代码就不贴了,太恶心了都是pojo要是想看就自己看代码吧。
下面是一个util类,用作把pojo转化为xml并且嵌入到http协议中并发送到固定url并且接受反馈xml并转化为pojo供client端使用具体代码如下:

public class MessagePoster<REQ_OBJ, RESP_OBJ> {
private String url = "http://localhost:8080/HttpChat/Server";
private String propLoc = "com/cxz/httpchat/util/class.properties";
// The XStream instance is thread-safe.
public XStream xstream = null;
public MessagePoster() {
initXStream();
}
public void setUrl(String url) {
this.url = url;
}
private void initXStream() {
xstream = new XStream();
Properties properties = new Properties();
try {
properties.load(MessagePoster.class.getClassLoader()
.getResourceAsStream(propLoc));
Set keys = properties.keySet();
for (Object key : keys) {
xstream.alias((String) key, Class.forName((String) properties
.get(key)));
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public RESP_OBJ postXML(REQ_OBJ requestObj) throws HttpException,
IOException {
RESP_OBJ responseObj = null;
PostMethod method = null;
try {
String xml = xstream.toXML(requestObj);
HttpClient client = new HttpClient();
method = new PostMethod(url);
RequestEntity entity;
entity = new StringRequestEntity(xml, "text/xml", "utf-8");
method.setRequestEntity(entity);
client.executeMethod(method);
responseObj = (RESP_OBJ) xstream.fromXML(method
.getResponseBodyAsStream());
} catch(StreamException e){
e.printStackTrace();
} finally {
method.releaseConnection();
}
return responseObj;
}
}

MessagePoster::initXStream()是初始化xstream的一个函数,具体从配置文件当中读取。具体配置文件如下:
HeartBeatRequest=com.cxz.httpchat.message.HeartBeatRequest
HeartBeatResponse=com.cxz.httpchat.message.HeartBeatResponse
UploadRequest=com.cxz.httpchat.message.UploadRequest
UploadResponse=com.cxz.httpchat.message.UploadResponse
Message=com.cxz.httpchat.message.Message

TimedQueue是该项目的另一个核心类,主要是一个消息队列,采用lru算法维护生命周期。
public class TimedQueue implements Queue<Message> {
private long lastAccess;
private int cycleSeconds;
private static final int UNIT_CONVERSION = 1000;
private Queue<Message> queue = new ConcurrentLinkedQueue<Message>();
public List<Message> toList(){
resetLastAccess();
List<Message> messages = new ArrayList<Message>();
while(!queue.isEmpty()){
messages.add(queue.remove());
}
return messages;
}
public boolean isOutOfDate(){
System.out.println(System.currentTimeMillis() - lastAccess);
return System.currentTimeMillis() >= lastAccess + cycleSeconds * UNIT_CONVERSION;
}
public boolean add(Message e) {
resetLastAccess();
queue.add(e);
return true;
}
public Message poll() {
resetLastAccess();
return queue.poll();
}

最后一个核心类就是:SelfCleaner他会定期清理队列中的无用成员,说白了就是长期没有响应的消息。
package com.cxz.httpchat.util;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import com.cxz.httpchat.message.Message;
import com.cxz.httpchat.util.TimedQueue;
public class SelfCleaner implements Runnable{
private Map<Integer, TimedQueue> map = new ConcurrentHashMap<Integer, TimedQueue>();
private Thread gcThread;
private static SelfCleaner instance;
private static final int POLL_SECONDS = 60;
private boolean runFlag = true;
private static final int UNIT_CONVERSION = 1000;
public static synchronized SelfCleaner getInstance() {
if (instance == null) {
instance = new SelfCleaner();
}
return instance;
}
private SelfCleaner(){
}
public void stopGc(){
runFlag = false;
gcThread.interrupt();
}
public void forceGc() {
//Interrupted the sleeping gc Thread
gcThread.interrupt();
}
public boolean isStopped() {
return runFlag;
}
public void start() {
gcThread = new Thread(this, "gcThread");
gcThread.start();
}
public void run() {
cleanTheHashMap();
while(runFlag){
try {
Thread.sleep(POLL_SECONDS * UNIT_CONVERSION);
} catch (InterruptedException e) {
// This thread has been woken up by a interruption.
System.out.println("Hey, man, get up from the bed and gc.");
}
}
}
public void remove(Integer id) {
map.remove(id);
}
public List<Message> getMessage(Integer id){
if(map.get(id) == null){
List<Message> list = new ArrayList<Message>();
return list;
} else {
return map.get(id).toList();
}
}
public void add(Integer id, Message message) {
TimedQueue queue = map.get(id);
if (queue == null) {// If the user does not exists in the queue
TimedQueue temp = new TimedQueue();
temp.add(message);
map.put(id, temp);
} else {
queue.add(message);
}
}
private void cleanTheHashMap() {
Set<Integer> set = map.keySet();
for (Integer id : set) {
TimedQueue tq = map.get(id);
if (tq.isOutOfDate()) {
map.remove(id);
}
}
}
}

最后一个类就是服务器类,很短,但很精悍。
package com.cxz.httpchat.server;
import java.io.IOException;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.cxz.httpchat.message.HeartBeatRequest;
import com.cxz.httpchat.message.HeartBeatResponse;
import com.cxz.httpchat.message.Message;
import com.cxz.httpchat.message.UploadRequest;
import com.cxz.httpchat.message.UploadResponse;
import com.cxz.httpchat.util.MessagePoster;
import com.cxz.httpchat.util.SelfCleaner;
import com.thoughtworks.xstream.XStream;
/**
* Servlet implementation class for Servlet: Server
*
*/
public class Server extends javax.servlet.http.HttpServlet implements
javax.servlet.Servlet {
/* (non-Java-doc)
* @see javax.servlet.http.HttpServlet#HttpServlet()
*/
private XStream xstream = new MessagePoster().xstream;
SelfCleaner map = SelfCleaner.getInstance();
public Server() {
super();
map.start();
}

/* (non-Java-doc)
* @see javax.servlet.http.HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
}
/* (non-Java-doc)
* @see javax.servlet.http.HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
Object req = xstream.fromXML(request.getInputStream());
Object resp = processRequest(req);
if(resp != null){
xstream.toXML(resp, response.getOutputStream());
} else {// Check the null
}
}
private Object processRequest(Object req){
if(req.getClass() == UploadRequest.class){
UploadRequest upload = (UploadRequest)req;
return processUpload(upload);
} else if (req.getClass() == HeartBeatRequest.class) {
HeartBeatRequest heartBeat = (HeartBeatRequest)req;
return processHeartBeat(heartBeat);
} else {
return null;
}
}
/**
* Just for fun
* @param upload
* @return
*/
private UploadResponse processUpload(UploadRequest upload) {
//Just for tracking the message flow
Message message = new Message();
message.setFrom(upload.getId());
message.setDate(upload.getDate());
message.setContent(upload.getContent());
map.add(upload.getTo(), message);
UploadResponse resp = null;
if(true/*upload.getId().equals(new Integer(1))&&upload.getPwd().equals("19841230")*/){
resp = new UploadResponse();
resp.setFlag(true);
} else {
resp = new UploadResponse();
resp.setFlag(false);
}
return resp;
}
/**
* Just for fun
* @param heartBeat
* @return
*/
private HeartBeatResponse processHeartBeat(HeartBeatRequest heartBeat) {
//Just for tracking the message flow
HeartBeatResponse resp = new HeartBeatResponse();
//need to do some login process
if(true/*heartBeat.getId().equals(new Integer(1))&&heartBeat.getPwd().equals("19841230")*/){
resp.setFlag(true);
List<Message> list = map.getMessage(heartBeat.getId());
resp.setMessages(list);
} else {
resp.setFlag(false);
}
return resp;
}
}
写完了。好累。

最后写下怎么用吧:在ChatClient中修改usrId、distId成员来确定发送者和接受者。设成相反的两个数字就可以,当然,客户端要启动2次。验证功能目前是关闭的。password无所谓了。
MessagePoster中的url可能需要修改一下,如果你部署的时候改变路径了。

就这么多了
页: [1]
查看完整版本: 一套基于http的聊天c/s结构工具(除了网页tomcat还能做什么)