源代码下载链接:http://download.csdn.net/detail/sky453589103/9514686
如果有什么问题,欢迎留言。
自定义异常的目的是为了更好的表示出错的原因,能够针对不同的异常执行不同的处理。
异常的自定义是简单的,只是简单的继承了Exception类。下面给出所有聊天程序的异常类的基类的ChatException的定义:
package SimpleChat;
public class ChatException extends Exception{
/**
*
*/
private static final long serialVersionUID = 1L;
public ChatException() {}
public ChatException(String desc) {
super(desc);
}
}
聊天程序的所有异常都应该继承这个类。这个的实现是很简单的,稍微懂点Java的都能看懂。
消息格式的定义也是简单的。
为什么不用现有的http协议来通信?
因为http协议对于这个程序来说,有很多东西是冗余的,因此为了提高利用率,这个我自定义了一个参考http协议的消息格式。
举一个请求消息的例子:
from:wapoonx
to:xwp
command:send
hello
以上就是一个发送消息的请求,from字段表明这个消息是从谁发送过来的。to这个字段表明要发送给谁,也就是接收方是谁。command这个字段表明要执行的命令。以一个空行为标志。隔开正文和头部字段。在这个例子中正文是hello。服务器会根据command字段,从请求报文中选取自己想要的信息。
举一个响应消息的例子:
code:0
description:success
(注意这里是空的正文)
以上是一个响应消息的格式,code这个字段代表的是一个响应码,这个响应码代表着服务器执行响应的客户请求的结果,0代表成功,其他代表出错。description是响应消息的描述。不同的描述在客户端可能有不同的处理。
下面给出请求报文的类的定义:
package SimpleChat;
import java.util.Map;
import java.util.TreeMap;
public class RequestMessage {
private String from = "";
private String to = "";
private String command = "";
private String rawContext = "";
public RequestMessage() {
}
public RequestMessage(String raw) {
parse(raw);
}
public String getFrom() {
return from;
}
public String getTo() {
return to;
}
public String getCommand() {
return command;
}
public String getContext() {
return rawContext;
}
public void setFrom(String f) {
from = f;
}
public void setTo(String to) {
this.to = to;
}
public void setCommand(String cmd) {
this.command = cmd;
}
public void setContext(String con) {
rawContext = con;
}
public String Format() {
String res = "from:" + from+ "\r\n";
res += "to:" + to + "\r\n";
res += "command:" + command + "\r\n\r\n";
res += rawContext;
return res;
}
private void parse(String raw) {
String[] temp = raw.split("\r\n\r\n");
String[] fields = temp[0].split("\r\n");
if (temp == null || fields == null) {
return ;
}
for (int i = 0; i < fields.length; ++i) {
if (fields[i].indexOf("from:") == 0) {
from = fields[i].substring("from:".length());
}
else if (fields[i].indexOf("to:") == 0) {
to = fields[i].substring("to:".length());
}
else if (fields[i].indexOf("command:") == 0) {
command = fields[i].substring("command:".length());
}
}
if (temp.length == 2) {
rawContext = temp[1];
}
}
public static Map<String, String> parseContext(String rawContext) throws ChatMessageException {
if (rawContext == null || rawContext.equals("")) {
return new TreeMap<String, String>();
}
Map<String, String> context = new TreeMap<String, String>();
String[] temp = rawContext.split("\r\n");
for (int i = 0; i < temp.length; ++i) {
String[] res = temp[i].split(":");
if (res.length == 2) {
context.put(res[0], res[1]);
} else {
throw new ChatMessageException("response message is invaild. context's format error");
}
}
return context;
}
public Map<String, String> parseContext() throws ChatMessageException {
return parseContext(this.rawContext);
}
}
上面这个类的理解难点在于parse函数。其实也很好懂。就是在上面举得请求消息报文中的字段和正文分析出来。报文的头部会有很多个字段,然后每个字段会被有一个对应的值,可能为空。parse函数解析请求报文有下面几个步骤:
1.使用String的splite函数,将正文和头部分割开。因为正文和头部之间值有连续的两个CRLF(\r\n)的。整个报文中也只有这个地方会出现连续的两个CRLF。
2.头部中的每个字段都是要满足下面形式:字段之间用一个CRLF隔开,字段中的形式是fieldname:value,因此很容易的就可以再用splite函数处理头部字段。
3.正文的内容会根据请求报文中的command字段的不同,具有不同的含义。这里的parseContext函数只是提供了最基本的一种解析方式。这里的解析基于下面的假设:每行只有一个键值对(就是类似头部字段中的内容那样)。在这个假设下,解析正文,并且把正文放到Map容器中。返回给调用者。
当然,请求报文还有格式化自己的功能,因此有一个Format函数,来将请求报文的内容格式化,方便以流的形式输出给服务器端。需要注意的是,客户端和服务器端的请求报文类是相同的。响应报文类也是完全相同的。响应报文类的作用也和请求报文相似。因此不再赘述。