书接上回,这次我们先不急着完成上篇文章中的ServerConversation和Server中的内容,我们先来把Client和ClientConversation中的一些简单的内容搞一下。
首先,我们的Client是需要我们这个客户端的ip地址的,需要服务器的端口号(port),同样也需要负责信息传输ClientConversation。因为我们要对这些个内容进行设置,所以我们要给出它们的set方法。
public static final String DEFAULT_IP = "127.0.0.1";
private String ip;
private int port;
private ClientConversation clientConversation;
public void setIp(String ip) {
this.ip = ip;
}
public void setPort(int port) {
this.port = port;
}
public void setClientConversation(ClientConversation clientConversation) {
this.clientConversation = clientConversation;
}
为了防止我们的ip地址与端口号为空,所以我们在构造方法中给出它们的默认值,这里我给的ip地址就是本地机,大家可以根据自己的情况自己给出合适的默认值。
public Client() {
this.ip = DEFAULT_IP;
this.port = Server.DEFAULT_PORT;
}
现在我们来完成一下我们最基本的连接服务器与客户端下线功能,看看会不会出现我们处理不了的情况。
public void connected() throws IOException {
Socket socket = new Socket(this.ip, this.port);
clientConversation = new ClientConversation(this, socket);
}
public void offline() {
if (是否确定下线) {
下线前要干啥?
this.clientConversation.offline();
下线后要干啥?
}
}
可以看到,我们的代码中出现了许多汉字,因为这些操作我们的框架就不知道到底该咋整,比如,是否确定下线这个操作编写前端的程序员可能想给一个弹窗让用户选择一下是否确定下线,但是这个弹窗,或者还有其他啥操作咱们的框架是没法预料的,这时候接口犀利的工具就该发挥它的作用了
public interface IClientAction {
void serverAbnoramlDrop(); // 服务器异常掉线
boolean ensureOffline(); // 确认下线
void beforeOffline(); // 下线前
void afterOffline(); // 下线后
void serverForcedown(); // 服务器强制宕机
void outOfRoom(); // 服务器超员
void afterConnected(); // 连接后
void toOne(String souceId, String message); //单发
void toOther(String souceId, String message);//群发
void toSpOther(String souceId, String message);//指定群发
}
有了这个接口我们可以给出一个适配器,这样就算APP层没有全部实现我们接口中的方法我们的程序也是不会出问题的,而且APP层也是可以选择性的覆盖我们这个适配器种方法,是的程序的编写更加的灵活。
public class ClientActionAdapter implements IClientAction {
@Override
public void serverAbnoramlDrop() {}
@Override
public boolean ensureOffline() {
return true;
}
@Override
public void beforeOffline() {}
@Override
public void afterOffline() {}
@Override
public void serverForcedown() {}
@Override
public void outOfRoom() {}
@Override
public void afterConnected() {}
@Override
public void toOne(String souceId, String message) {}
@Override
public void toOther(String souceId, String message) {}
@Override
public void toSpOther(String souceId, String message) {}
}
既然这个接口以及适配器我们都准备好了,那我们就把它加入到我们的Client中,并且在构造方法中给出他的初始化,还有应该给它一个get方法。
public Client() {
this.ip = DEFAULT_IP;
this.port = Server.DEFAULT_PORT;
this.clientAction = new ClientActionAdapter();
}
IClientAction getClientAction() {
return clientAction;
}
对于端口号和ip地址的设定我们也可以通过properties文件的方式进行设定:
public Client initClient(String configPath) {
Pro_Parser.load(configPath);
int temp = 0;
try {
temp = Pro_Parser.get("port", int.class);
if (temp != 0) {
this.port = temp;
}
} catch (Exception e) {
}
String serverIp = null;
try {
serverIp = Pro_Parser.get("ip", String.class);
if (serverIp != null) {
this.ip = serverIp;
}
} catch (Exception e) {
}
return this;
}
接下来我们对ClientConversation进行编写:
我们每一个客户端 都会收到一个来自服务器的专属Id,且这个Id的设置不对用户开放,而它的get方法我们是要在Client中提供的,所以这里的get方法我们给一个包权限的就行了,在Client的构造方法中我们可以看到ClientConversation的构造方法是需要有一个Client的
private volatile String id; //volatile 防止寄存器优化
private Client client;
protected ClientConversation(Client client, Socket socket) throws IOException {
super(socket);
this.client = client;
}
String getId() {
return id;
}
有了我们写好的适配器,我们的服务器异常掉线的编写就变得异常容易:
@Override
public void peerAbnormalDrop() {
// 服务器异常掉线,这个我们框架管不了,应该交给客户端的APP层,至于具体的实现形式我们不关心
this.client.getClientAction().serverAbnoramlDrop();
}
这些完成后我们就开始编写一些服务器与客户端的信息交流:
- 单发
我们要告诉服务器目标是谁(目标Id)再把我们的消息交给服务器void toOne(String target, String message) { send(new NetMessage() .setCommand(ENetCommand.TO_ONE) .setAction(target) .setMessage(message)); }
- 群发
群发就简单了,我们只需要把我们要发送的信息告诉服务器就行了void toOther(String message) { send(new NetMessage() .setCommand(ENetCommand.TO_OTHER) .setMessage(message)); }
- 指定群发
这里需要注意一下,我们的目标是一部分人,所以传进来的形参肯定是一个列表,我们的NetMessage可没有列表这种格式的信息,所以我们就需要用Gson把传进来的列表转换一下void toSpOther(List<String> targets, String message) { send(new NetMessage() .setCommand(ENetCommand.TO_SP_OTHER) .setAction(Communication.gson.toJson(targets)) .setMessage(message)); }
- 下线
这个就只需要告诉一下服务器我们下线了,然后关闭这个ClientConversation就行public void offline() { send(new NetMessage() .setCommand(ENetCommand.OFFLINE)); close(); }
这些编好后Client中的单发,群发,指定群发的操作就只需要调用这里面的方法就好了
public void toOne(String target, String message) {
this.clientConversation.toOne(target, message);
}
public void toOther(String message) {
this.clientConversation.toOther(message);
}
public void toSpOther(List<String> targets, String message) {
this.clientConversation.toSpOther(targets, message);
}
接下来,就是我们的重头戏,那就是两个dealNetMessage的编写,先来看看我们的ClientConversation向ServerConversation中发了哪些Command:
TO_ONE,TO_OTHER,TO_SP_OTHER,OFFLINE
所以,我们ServerConversation中的dealNetMessage就主要针对这几个命令进行处理:
@Override
public void dealNetMessage(NetMessage netMessage) {
ENetCommand command = netMessage.getCommand();
String action = netMessage.getAction();
String message = netMessage.getMessage();
switch (command) {
case OFFLINE:
break;
case TO_ONE:
break;
case TO_OTHER:
break;
case TO_SP_OTHER:
break;
default:
break;
}
}
首先第一步,我们先把传递过来的NetMessage进行解析,得到里面的command,action,message
对于OFFLINE的操作
- 关闭这个会话
- 从客户端池子中移除这个客户端
- 通知大家一声
case OFFLINE:
close();
this.server.getClientPool().removeClient(this);
this.server.speakOut("客户端【" + this.id + "】已下线!");
break;
但是,接下来着三个主要的信息发送操作,确实需要像编写ClientConversation与Client给出一套对应的toOne,toOther,toSpOther,其实大部分代码都差不多,但是有一点需要注意我们的服务器是完成完成转发工作的,所以在把消息发给真正的目标时还需要知道发送的源头是谁,所以需要给出一个souceId
ServerConversation中:
void toOne(String souceId, String message) {
send(new NetMessage()
.setAction(souceId)
.setCommand(ENetCommand.TO_ONE)
.setMessage(message));
}
void toOther(String souceId, String message) {
send(new NetMessage()
.setAction(souceId)
.setCommand(ENetCommand.TO_OTHER)
.setMessage(message));
}
void toSpOther(String souceId, String message) {
send(new NetMessage()
.setAction(souceId)
.setCommand(ENetCommand.TO_SP_OTHER)
.setMessage(message));
}
Sever中:
void toOne(String souceId, String target, String message) {
ServerConversation client = this.clientPool.getClient(target);
client.toOne(souceId, message);
}
void toOther(String souceId, String message) {
List<ServerConversation> clientList = this.clientPool.getClientExcept(souceId);
for (ServerConversation client : clientList) {
client.toOther(souceId, message);
}
}
void toSpOther(String souceId, List<String> targetList, String message) {
List<ServerConversation> clientList = this.clientPool.getSpOther(targetList);
for (ServerConversation client : clientList) {
client.toSpOther(souceId, message);
}
}
现在这三个命令的处理就很明了了:
case TO_ONE:
this.server.toOne(this.id, action, message);
break;
case TO_OTHER:
this.server.toOther(this.id,message);
break;
case TO_SP_OTHER:
List<String> targetLsit = Communication.gson.fromJson(action, type);
this.server.toSpOther(this.id, targetLsit, message);
break;
值得一提的是,在TO_SP_OTHER中,因为我们传进来的目标列表是经过Gson转换过,而这样会造成泛型擦除,所以在转换回来的时候需要给出提前声明泛型的类型:
private static final Type type = new TypeToken<List<String>>() {}.getType();
现在我们来看看我们的ServerConversation向ClientConversation发送的哪些command:
TO_ONE,TO_OTHER,TO_SP_OTHER,ID,OUT_OF_ROOM,FORCE_DOWN
其实这些情况对于我们框架来说是没有办法处理的,因为这牵扯到APP页面的具体实现形式,这我们无法左右,所以之前写的IClientAction就派上用场了:
public void dealNetMessage(NetMessage netMessage) {
ENetCommand command = netMessage.getCommand();
String action = netMessage.getAction();
String message = netMessage.getMessage();
switch (command) {
case FOREC_DOWN:
close();
this.client.getClientAction().serverForcedown();
break;
case OUT_OF_ROOM:
close();
this.client.getClientAction().outOfRoom();
break;
case ID:
while(this.id == null) {
this.id = message;
}
this.client.getClientAction().afterConnected();
break;
case TO_ONE:
this.client.getClientAction().toOne(action, message);
break;
case TO_OTHER:
this.client.getClientAction().toOther(action, message);
break;
case TO_SP_OTHER:
this.client.getClientAction().toSpOther (action, message);
break;
default:
break;
}
}
至此,CSFramework的核心功能就已经全部编写完成了,感谢你能不讨厌的看到这儿,喜欢的话不要忘记点个赞,收个藏。后续有时间会继续更新这个CSFramework的,因为还有一些操作能使我们这个框架变得更加自动化。
源码我会再下一篇文章中全部展示出来的,包括那个解析properties文件的源码。
再次感谢!!!