【Betty】会话层(Communication层)

shadowLogo

在我们学习 网络 的时候,都应该对于 OSI七层网络模型 有一定的了解
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类:

实现思路:

我们实现的 客户端会话实现类,要满足如下 要求
具体的处理逻辑,

  1. 在上文中,本人实现了一个 对端消息处理器 —— PeerMessageProcessor类
    根据其 dealNetMessage()方法具体消息处理逻辑方法名如下格式

$$
deal + Command
$$

  1. 能为 上层 至少提供如下 功能
  • 向服务端 发送请求
  • 向其他客户端 单发/群发消息,
  • 向 服务端 发送文本消息
  • 处理 服务端异常掉线的情况
  • 下线

实现代码:

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类:

实现思路:

和实现 客户端会话实现类 时类似,
我们实现 服务端会话实现类,需要实现如下 功能

  1. 在上文中,本人实现了一个 对端消息处理器 —— PeerMessageProcessor类
    根据其 dealNetMessage()方法具体消息处理逻辑方法名如下格式

$$
deal + Command
$$

  1. 能为 上层 至少提供如下 功能
  • 要求客户端上报身份
  • 强制下线
  • 服务器繁忙
  • 给 某一客户端 推送消息
  • 给 全部客户端 广播消息
  • 处理 客户端异常掉线的情况

实现代码:

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,开发一个聊天室,相信那时候,同学们就会理解到这个框架的强悍和方便之处

posted @ 2020-04-22 00:27  在下右转,有何贵干  阅读(325)  评论(0编辑  收藏  举报