小型C/S开发框架——CSFrameWork(一)
首先,要说明该框架是建立在一个多层次的系统下,共有三层——通信层,会话层和最外层(服务器和客户端)。其中,通信层是最低层,再上一层是会话层,最后是最外层(服务器和客户端)。我用一张图来大致表达一下它们之间的关系。
以下是该框架所有的类名称:
.
双工通信三要素
所谓双工,就是“双向通信工作”模式。
在C/S网络编程模式下,服务器与一个客户端进行双向通信时,必须拥有以下三要素:
- Socket类的对象,即,通信端口封装类。由它才能创建下面的通信信道,并在结束通信时,关闭网络连接;
- DataInputStream类的对象。接收来自“对端”信息的输入通信信道;
- DataOutputStream类的对象。向“对端”发送消息的输出通信信道。
最底层——通信层的实现
通信层有以下功能:
- 建立通信信道;
- 提供发送,接收信息和关闭连接的方法;
- 检查并发现对端异常掉线,并通知上一层处理。
通信层作为最底层,它是客户端和服务器建立联系的枢纽,是共用的一套代码,是能同时给服务器会话层和客户端会话层提供方法。
我们先看Communication类的线程代码片段:
这个线程不停的运行,而且通过第40行(message = this.dis.readUTF())不间断的侦听对端发送的消息。通过Boolean类型的goon判断何时停止线程的运行,并且当对端异常掉线时异常捕获块会判断goon的值,若为true,则说明时对端异常掉线,此时会执行一个抽象类方法peerAbnormalDrop(),交给上一层(ServerConversation 和 ClientConversation)具体处理异常掉线的情况。
向对端发送信息的方法:
下面是Communication类的完整代码:
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
public abstract class Communication implements Runnable{
protected Socket socket;
protected String ip;
protected DataInputStream dis;
protected DataOutputStream dos;
protected volatile boolean goon;
Communication(Socket socket) throws IOException {
this.socket = socket;
this.ip = this.socket.getInetAddress().getHostAddress();
this.dis = new DataInputStream(this.socket.getInputStream());
this.dos = new DataOutputStream(this.socket.getOutputStream());
this.goon = true;
new Thread(this).start();
}
void send(NetMessage message) {
try {
this.dos.writeUTF(NetMessage.gson.toJson(message));
} catch (IOException e) {
//向对端发消息失败,意味着对端已关闭通信!
//关闭网络连接
close();
}
}
public abstract void dealNetMessage(NetMessage netMessage);
public abstract void peerAbnormalDrop();
@Override
public void run() {
String message = "";
while (this.goon) {
try {
message = this.dis.readUTF();
NetMessage netMessage =
NetMessage.gson.fromJson(message, NetMessage.class);
dealNetMessage(netMessage);
} catch (IOException e) {
if (this.goon == true) {
//goon为true,意味着对端异常掉线!
//调用异常掉线处理。
this.goon = false;
peerAbnormalDrop();
}
//若goon为false,意味着正常正常下线,
//则什么都不用做。
}
}
close();
}
protected void close() {
this.goon = false;
try {
if (this.dis != null) {
this.dis.close();
}
} catch (IOException e) {
} finally {
this.dis = null;
}
try {
if (this.dos != null) {
this.dos.close();
}
} catch (IOException e) {
} finally {
this.dos = null;
}
try {
if (this.socket != null && !this.socket.isClosed()) {
this.socket.close();
}
} catch (IOException e) {
} finally {
this.socket = null;
}
}
}
网络信息的规范化
客户端与客户端不仅要进行文字,图片,音频等信息的互相发送,客户端与服务器端还要进行“命令式”操作,比如,客户端下线,就要给服务器发送一个消息,告诉它该客户端要下线,进而做出一系列有关下线的操作。但这条消息应该与其他的聊天消息区别开来,即,把“会话内容”和“网络命令”严格区分开来!
为了表示“网络命令”的方便,我们使用Enum(枚举):
public enum ENetCommand {
OUT_OF_ROOM,
ID,
OFFLINE,
TO_ONE,
TO_OTHER,
TO_SP_OTHER,
FORCEDOWN,
上面的各种命令都是自定义的,代表了不同的命令,例如,TO_ONE(私聊),OFFLINE(下线)等。
现在讲讲NetMessage类,该类的对象生成服务器端和客户端要发送的信息。在发送和接收时,网络上传输的是本类对象形成的字符串,在发送前和接收后,应该转换为本类对象。(由于本类是工具的内部核心类,严禁包外接触,所以修饰符是“包修饰符”,即,无修饰。
下面是NetMessage类的完整代码:
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
public class NetMessage {
public static final Gson gson = new GsonBuilder().create();
private ENetCommand command;
private String action;
private String message;
public NetMessage() {
}
String getAction() {
return action;
}
NetMessage setAction(String action) {
this.action = action;
return this;
}
ENetCommand getCommand() {
return command;
}
NetMessage setCommand(ENetCommand command) {
this.command = command;
return this;
}
String getMessage() {
return message;
}
NetMessage setMessage(String message) {
this.message = message;
return this;
}
}
我们会看到该类定义了三个成员,command(ENetcommand类型,代表命令),action(String类型,这里不解释,以后会有用处),message(String类型,代表文字消息)。
注意NetMessage类中的这句代码public static final Gson gson = new GsonBuilder().create(),有一个Gson的类,该类的作用是实现对象和json字符串之间的转换,需要用到gson.jar。
上图代码就解释了Gson的作用:
将网络命令语句TO_ONE和文字消息“我爱你姑娘呦!”转换成一个键值对形式的字符串,相当于对发送的信息进行包装,分类的整合起来,以便传输。然而,说到传输,我们便要开始提到会话层了!
会话层是由通信层Communucation类派生出来的,我们分为服务器会话层ServerConversation类和客户端会话层ClientConversation类。会话层的作用是处理客户端与服务器端之间的各种联系,是其联系的桥梁。由于最外层的客户端和服务器端还没有写,我们先构建出会话层的空壳,在将来服务器端和客户端写好后,就可以完成会话层的一系列功能。