【Betty】会话层(Communication层)
在我们学习 网络 的时候,都应该对于 OSI七层网络模型
有一定的了解
由于在我们使用 socket 去进行 网络编程 的时候,底层就已经把 传输层 实现了
因此,本篇博文,就来模拟实现其中的 会话层
:
实现 思路:
话不多说,现在,就开始本篇博文的知识点的讲解吧:
在之前的博文中,本人已经讲解过 通信消息的 封装实体类 的 组成 了
那么,在本篇博文中,本人就来带同学们实现下 会话层
的逻辑:
由于本层是 会话层
所以,我们最少要实现 建立会话 、发送消息、接收消息、维护会话 、基本请求/响应的处理 以及 结束会话 这些基本功能
那么,具备了上述的思想,现在,本人来展示下实现的代码:
实现 代码
基本通信 实现 —— Communication类:
package edu.youzg.betty.communication;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import edu.youzg.betty.protocol.NetMessage;
public abstract class Communication implements Runnable {
protected Socket socket; // 网络通信 的 socket
protected DataInputStream dis; // 通信信道,输入流
protected DataOutputStream dos; // 通信信道,输出流
protected volatile boolean goon; // 会话是否维持 标志
/**
* 初始化 会话层对象,
* 创建线程,开始侦听对端数据,并在接收到数据后,调用实现类的处理方法去处理消息
* @param socket
* @throws IOException
*/
protected Communication(Socket socket) throws IOException {
this.socket = socket;
this.dis = new DataInputStream(socket.getInputStream());
this.dos = new DataOutputStream(socket.getOutputStream());
this.goon = true;
new Thread(this, "会话层").start();
}
/**
* 发送 会话层消息
* @param message
*/
protected void send(NetMessage message) {
try {
dos.writeUTF(message.toString());
} catch (IOException e) {
close();
}
}
/**
* 处理 对端发来的 会话层消息
* (抽象方法,供上层使用作扩展)
* @param message
*/
protected abstract void dealPeerMessage(NetMessage message);
/**
* 处理 对端异常掉线 事件
* (抽象方法,供上层使用作扩展)
*/
protected abstract void peerAbnormalDrop();
/**
* 侦听对端信息
* (此处 可以为 心跳检测 功能扩展)
*/
@Override
public void run() {
String message = null;
while (goon) {
try {
message = dis.readUTF();
dealPeerMessage(new NetMessage(message)); // 侦听到信息后,要将字符串类型信息,包装成我们所需要的数据类型,并加以处理
} catch (IOException e) {
if (goon == true) {
peerAbnormalDrop();
goon = false;
}
}
}
close();
}
/**
* 断开 网络通信层的会话,
* 停止 侦听线程
*/
protected void close() {
goon = false;
try {
if (dis != null) {
dis.close();
}
} catch (IOException e) {
} finally {
dis = null;
}
try {
if (dos != null) {
dos.close();
}
} catch (IOException e) {
} finally {
dos = null;
}
try {
if (socket != null && !socket.isClosed()) {
socket.close();
}
} catch (IOException e) {
} finally {
socket = null;
}
}
}
在上面的代码中,本人留有两个 抽象方法 —— dealPeerMessage() 和 peerAbnormalDrop():
那么,本人来对这两个抽象方法做下 说明:
- dealPeerMessage() :
处理 对端消息- peerAbnormalDrop() :
处理 对端异常掉线 事件
那么,为什么要将这两个方法 制定成 抽象方法 呢?
答曰:
因为
Communication类
的功能只是 实现通信 的 基本功能
因此,真正的消息、事件的处理,应该交由 真正的实现类 去实现
我们若是想要具体实现 会话层,就需要先来制定下 会话层的通信协议
:
会话层 消息格式 —— InteractiveInfo类:
在会话层,我们就不需要考虑这么多了
我们只需要知道 消息的收发方的标识 和 消息内容 即可
因此,依据上述分析,本人给出如下代码:
package edu.youzg.betty.communication;
public class InteractiveInfo {
private String sourceId; // 发送方的id
private String targetId; // 接收方的id
private String message; // 消息内容
public InteractiveInfo() {
}
protected InteractiveInfo(String sourceId, String targetId, String message) {
this.sourceId = sourceId;
this.targetId = targetId;
this.message = message;
}
public String getSourceId() {
return sourceId;
}
public void setSourceId(String sourceId) {
this.sourceId = sourceId;
}
public String getTargetId() {
return targetId;
}
public void setTargetId(String targetId) {
this.targetId = targetId;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
我们想要实现 具体会话实现类
,就必须实现 对端消息处理逻辑
为了各功能模块 职责单一,本人就将 客户端和服务端 的 消息处理逻辑 的共同点 抽取出来
来编写一个 对端消息处理器
—— PeerMessageProcessor类:
对端消息处理器 —— PeerMessageProcessor类:
那么,在这个处理器中,我们要如何实现呢?
实现思路:
由于我们从对端接收的消息,实际上是 NetMessage类型的
而我们真正想要去处理这些 NetMessage消息,就需要根据 请求/响应 类型
,去执行 相应方法
实现代码:
package edu.youzg.betty.communication;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import edu.youzg.betty.protocol.ENetCommand;
import edu.youzg.betty.protocol.NetMessage;
public class PeerMessageProcessor {
private Object object; // 执行处理操作 的 对象
private Class<?> klass; // 反射机制
public PeerMessageProcessor() {
}
void setObject(Object object) {
this.object = object;
this.klass = object.getClass();
}
/**
* 将字符串 转换为 "驼峰式"
* (对应的方法名)
* @param str 具体操作描述符
* @return 对应的方法名
*/
private String changeToLower(String str) {
String result = str.substring(0, 1);
result += str.substring(1).toLowerCase();
return result;
}
/**
* 处理 对端发来的 会话层消息
* 组装方法名,
* (在会话层中传输的参数要符合"deal+Command"的驼峰式结构)
* @param netMessage 对端发来的 会话层消息
*/
void dealNetMessage(NetMessage netMessage) {
ENetCommand command = netMessage.getCommand();
String commandName = command.name();
String[] commandNames = commandName.split("_");
String methodName = "deal";
for (String name : commandNames) {
methodName += changeToLower(name);
}
try {
Method method = klass.getDeclaredMethod(methodName, NetMessage.class);
method.invoke(object, netMessage);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
那么,现在,我们来实现下 真正的实现类 —— ServerConversation类
和 ClientConversation类
:
首先,是 执行逻辑较为简单 的 客户端会话实现类
—— ClientConversation类:
客户端会话实现类 —— ClientConversation类:
实现思路:
我们实现的 客户端会话实现类,要满足如下 要求:
具体的处理逻辑,
- 在上文中,本人实现了一个 对端消息处理器 —— PeerMessageProcessor类,
根据其 dealNetMessage()方法,具体消息处理逻辑 的 方法名 为 如下格式:$$
deal + Command
$$
- 能为 上层 至少提供如下 功能:
- 向服务端 发送请求
- 向其他客户端 单发/群发消息,
- 向 服务端 发送文本消息
- 处理 服务端异常掉线的情况
- 下线
实现代码:
package edu.youzg.betty.communication;
import java.io.IOException;
import java.net.Socket;
import edu.youzg.betty.core.Client;
import edu.youzg.betty.core.NetNode;
import edu.youzg.betty.protocol.ENetCommand;
import edu.youzg.betty.protocol.NetMessage;
import edu.youzg.util.ArgumentMaker;
public class ClientConversation extends Communication {
private Client client; // 应用层实现,客户端供开发者调用的api
private String id;
private PeerMessageProcessor peerMessageProcessor; // 对端消息处理器
public ClientConversation(Client client, Socket socket) throws IOException {
super(socket);
this.client = client;
this.peerMessageProcessor = new PeerMessageProcessor();
this.peerMessageProcessor.setObject(this); // 由于处理逻辑在本层,所以,将 处理器 中的 执行对象 设置为 this
}
/**
* 执行下线操作
* 向客户端发送下线消息,以便客户端能够关闭相应的ServerConversation,并对其它客户端做出"当前客户端下线"公告
*/
public void offline() {
send(new NetMessage()
.setCommand(ENetCommand.OFFLINE));
close();
}
/**
* 向服务端 发送请求
* @param request:请求的方法
* @param response:请求的服务器的action
* @param parameter:请求参数
*/
public void sendRequest(String request, String response, String parameter) {
send(new NetMessage()
.setCommand(ENetCommand.REQUEST)
.setAction(request + "#" + response)
.setParameter(parameter));
}
/**
* 向服务器发送消息
* @param message 文本消息
*/
public void sendMessageToServer(String message) {
send(new NetMessage()
.setCommand(ENetCommand.SEND_MESSAGE_TO_SERVER)
.setParameter(message));
}
/**
* 处理 服务端繁忙 事件
* @param netMessage
*/
public void dealServerOutOfRoom(NetMessage netMessage) {
client.getClientAction().serverOutOfRoom();
close();
}
/**
* 模拟tcp的三次握手,处理 服务端连接问题
* @param netMessage
*/
public void dealWhoAreYou(NetMessage netMessage) {
NetNode me = client.getMe();
String myInfo = me.getIp();
myInfo += "#" + myInfo.hashCode();
send(new NetMessage()
.setCommand(ENetCommand.I_AM)
.setParameter(myInfo));
}
/**
* 模拟tcp的三次握手,处理 服务端连接问题
* @param netMessage 服务端网络消息
*/
public void dealEnsureOnline(NetMessage netMessage) {
String parameter = netMessage.getParameter();
this.id = parameter;
client.getClientAction().afterConnectToServer();
}
/**
* 将 其他客户端的 私聊消息
* @param netMessage 服务端网络消息
*/
public void dealToOne(NetMessage netMessage) {
String parameter = netMessage.getParameter();
InteractiveInfo interactiveInfo = ArgumentMaker.gson.fromJson(parameter, InteractiveInfo.class);
client.getClientAction().dealToOne(interactiveInfo.getSourceId(), interactiveInfo.getMessage());
}
/**
* 处理 其他客户端的 群发消息
* @param netMessage 对端网络消息
*/
public void dealToOther(NetMessage netMessage) {
String parameter = netMessage.getParameter();
InteractiveInfo interactiveInfo = ArgumentMaker.gson.fromJson(parameter, InteractiveInfo.class);
client.getClientAction().dealToOther(interactiveInfo.getSourceId(), interactiveInfo.getMessage());
}
/**
* 处理 服务端的强制下线指令
* @param netMessage 服务端网络消息
*/
public void dealServerForceDown(NetMessage netMessage) { //处理服务器异常掉线的情况
client.getClientAction().serverForcedown();
close();
}
/**
* 处理 服务端的响应
* @param netMessage 服务端网络消息
*/
public void dealResponse(NetMessage netMessage) { //处理向服务器发送请求得到的回复
String action = netMessage.getAction();
String parameter = netMessage.getParameter();
try {
client.getActionProcesser().dealResponse(action, parameter);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 处理 对端消息
* @param message 服务端网络消息
*/
@Override
protected void dealPeerMessage(NetMessage message) {
peerMessageProcessor.dealNetMessage(message);
}
public String getId() {
return id;
}
/**
* 向服务端 发送 私聊请求
* @param targetId 目标客户端id
* @param message 私聊消息
*/
public void toOne(String targetId, String message) {
InteractiveInfo interactiveInfo = new InteractiveInfo(this.id, targetId, message);
send(new NetMessage()
.setCommand(ENetCommand.TO_ONE)
.setParameter(ArgumentMaker.gson.toJson(interactiveInfo)));
}
/**
* 向服务端 发送 公聊请求
* @param message 公聊消息
*/
public void toOther(String message) { //将信息群发给其他客户端
InteractiveInfo interactiveInfo = new InteractiveInfo(this.id, null, message);
send(new NetMessage()
.setCommand(ENetCommand.TO_OTHER)
.setParameter(ArgumentMaker.gson.toJson(interactiveInfo)));
}
/**
* 处理 服务端异常掉线现象
*/
@Override
protected void peerAbnormalDrop() {
client.getClientAction().serverAbnormalDrop();
}
}
最后是 服务端会话实现类 —— ServerConversation类:
服务端会话实现类 —— ServerConversation类:
实现思路:
和实现 客户端会话实现类 时类似,
我们实现 服务端会话实现类,需要实现如下 功能:
- 在上文中,本人实现了一个 对端消息处理器 —— PeerMessageProcessor类,
根据其 dealNetMessage()方法,具体消息处理逻辑 的 方法名 为 如下格式:$$
deal + Command
$$
- 能为 上层 至少提供如下 功能:
- 要求客户端上报身份
- 强制下线
- 服务器繁忙
- 给 某一客户端 推送消息
- 给 全部客户端 广播消息
- 处理 客户端异常掉线的情况
实现代码:
package edu.youzg.betty.communication;
import java.io.IOException;
import java.net.Socket;
import edu.youzg.betty.action.IActionProcessor;
import edu.youzg.betty.core.Server;
import edu.youzg.betty.protocol.ENetCommand;
import edu.youzg.betty.protocol.NetMessage;
import edu.youzg.util.ArgumentMaker;
public class ServerConversation extends Communication {
private static final int OK = 1;
private static final int ILLEGAL_USER = 2;
private String ip;
private String id;
private Server server; // 应用层实现,服务端供开发者调用的api
private IActionProcessor actionProcessor; // 客户端response或respond处理器
private PeerMessageProcessor peerMessageProcessor; // 对端消息处理器
public ServerConversation(Socket socket, Server server) throws IOException {
super(socket);
this.server = server;
this.peerMessageProcessor = new PeerMessageProcessor();
this.peerMessageProcessor.setObject(this); // 因为处理消息的也是本conversation,所以,peerMessageProcessor中的object是 本conversation
}
IActionProcessor getActionProcessor() {
return actionProcessor;
}
public void setActionProcessor(IActionProcessor actionProcessor) {
this.actionProcessor = actionProcessor;
}
public String getId() {
return id;
}
/**
* 模拟tcp三次握手
*/
public void whoAreYou() {
send(new NetMessage()
.setCommand(ENetCommand.WHO_ARE_YOU));
}
/**
* 服务器繁忙,向正在尝试连接的客户端 发送 服务端繁忙消息
*/
public void outOfRoom() {
send(new NetMessage()
.setCommand(ENetCommand.SERVER_OUT_OF_ROOM));
close();
}
/**
* 向指定客户端发送 私聊消息
* @param interactiveInfo 客户端会话层消息
*/
public void toOne(InteractiveInfo interactiveInfo) {
send(new NetMessage()
.setCommand(ENetCommand.TO_ONE)
.setParameter(ArgumentMaker.gson.toJson(interactiveInfo)));
}
/**
* 向指定客户端发送 公聊消息
* @param interactiveInfo 客户端会话层消息
*/
public void toOther(InteractiveInfo interactiveInfo) {
send(new NetMessage()
.setCommand(ENetCommand.TO_OTHER)
.setParameter(ArgumentMaker.gson.toJson(interactiveInfo)));
}
/**
* 强制关闭服务器,并向连接的客户端 广播强制关闭消息
*/
public void forcedown() {
send(new NetMessage()
.setCommand(ENetCommand.SERVER_FORCE_DOWN));
close();
}
/**
* 处理 客户端异常掉线的情况
*/
@Override
protected void peerAbnormalDrop() {
server.getTemporaryConversationPool().removeTempConversation(this);
server.getClientPool().removeClient(this);
server.publishMessage("客户端[" + id + "]异常掉线!");
}
/**
* 处理来自客户端的传输层消息
* @param message 客户端的传输层消息
*/
@Override
protected void dealPeerMessage(NetMessage message) {
peerMessageProcessor.dealNetMessage(message);
}
/**
* 允许客户端上线
*/
private void ensureClientOnline() {
this.id = this.ip + ":" + System.currentTimeMillis();
server.getClientPool().addClient(this);
server.publishMessage("客户端[" + this.id + "]上线!");
send(new NetMessage()
.setCommand(ENetCommand.ENSURE_ONLINE)
.setParameter(this.id));
}
/**
* 模拟三次握手的关键,检查客户端的消息
* @param message 客户端的身份验证消息
* @return 验证结果
*/
private int checkClient(String message) {
int index = message.indexOf("#");
this.ip = message.substring(0, index);
String password = message.substring(index + 1);
if (this.ip.hashCode() == Integer.valueOf(password)) {
return OK;
}
return ILLEGAL_USER;
}
/**
* 模拟tcp三次握手
* @param netMessage
*/
public void dealIAm(NetMessage netMessage) {
String parameter = netMessage.getParameter();
server.getTemporaryConversationPool().removeTempConversation(this);
int result = checkClient(parameter);
if (result == OK) {
// 生成客户端id,并将其从tempPool中移动到clientPool中,
// 并告知在线客户端:“XXX客户端登录”,同时告知客户端相关id,以便初始化该客户端的好友列表
ensureClientOnline();
} else if (result == ILLEGAL_USER) {
// 告知客户端:你是非法用户!并停止侦听该客户端,从tempPool中删除!
close();
}
}
/**
* 处理 客户端下线请求
* @param netMessage 客户端发来的传输层消息
*/
public void dealOffline(NetMessage netMessage) {
server.getClientPool().removeClient(this);
server.publishMessage("客户端[" + this.id + "]下线!");
close();
}
/**
* 处理 客户端私聊请求
* @param netMessage 客户端发来的传输层消息
*/
public void dealToOne(NetMessage netMessage) { //处理一个客户端向另一个客户端发送消息
String parameter = netMessage.getParameter();
InteractiveInfo interactiveInfo = ArgumentMaker.gson.fromJson(parameter, InteractiveInfo.class);
server.toOne(interactiveInfo);
}
/**
* 处理 客户端公聊请求
* @param netMessage 客户端发来的传输层消息
*/
public void dealToOther(NetMessage netMessage) { //处理一个客户端群发消息
String parameter = netMessage.getParameter();
InteractiveInfo interactiveInfo = ArgumentMaker.gson.fromJson(parameter, InteractiveInfo.class);
server.toOther(interactiveInfo);
}
/**
* 处理 客户端发送给服务器的消息
* @param netMessage 客户端发来的传输层消息
*/
public void dealSendMessageToServer(NetMessage netMessage) {
String parameter = netMessage.getParameter();
server.publishMessage(parameter);
}
/**
* 处理 客户端请求
* @param netMessage 客户端发来的传输层消息
*/
public void dealRequest(NetMessage netMessage) {
String action = netMessage.getAction();
int index = action.indexOf('#');
String request = action.substring(0, index);
String response = action.substring(index + 1);
String parameter = netMessage.getParameter();
try {
String responseResult = actionProcessor.dealRequest(request, parameter);
send(new NetMessage()
.setCommand(ENetCommand.RESPONSE)
.setAction(response)
.setParameter(responseResult));
} catch (Exception e) {
e.printStackTrace();
server.publishMessage(e.getMessage());
}
}
}
至此,会话层(Communication层)就基本实现了!
还是那句话,本人的实现,只是最基本的功能
如果同学们还有更好的实现思路,比如:使用nio/aio优化效率、心跳检测机制、断线重连机制 等等,
都可以在本层去优化实现,并可以在 上层(应用层) 提供开启/启动参数的api
在之后的博文中,本人会在会话层的基础上,来编写 应用层,也就是 Betty 向 使用本框架的开发者 提供的API
之后,会使用 应用层向开发者提供的API,开发一个聊天室,相信那时候,同学们就会理解到这个框架的强悍和方便之处