在前两期文章中,我们在发送消息前加上“g:”或“ id:”来分别区分群聊和私聊消息,这其实可以视为一种简单的消息协议,即客户端与服务器的交互需要遵循的统一规则。没有规范的消息协议,数据传输会出现解析混乱、功能无法扩展等问题。今天,我们就在原基础上拓展,为通信程序添加登录注册功能,借此阐述自定义消息协议的设计原因、设计思想以及简易实战应用。

1.设计原因

        网络传输的底层数据是字节流,单纯的字节流无法区分其用途。对于一段字节数据,服务器无法判断它是登录请求还是聊天消息。通过设计消息协议,我们可以为不同功能的数据添加标识头,让接收方快速识别数据用途,进而执行对应的处理逻辑。而且不同功能对应的数据格式不同,例如登录需要传输 “用户名 + 密码”,聊天需要传输 “目标对象 + 消息内容”。通过协议规范数据格式,能确保收发双方的解析逻辑一致。

        除了解决数据识别和传输的问题,消息协议也便于我们后续进行功能拓展。我们的通信程序可以不只局限于文字消息,如果未来需要新增文件传输、表情包发送等功能,我们仍可以采用“消息头 + 消息内容”的结构加以区分,不需要大幅修改通信框架。

2.设计思路

        首先我们来定义消息头,这是消息的核心。利用接口支持 public stati final 常量的特性,定义 MessageType 接口并为登录、注册、聊天等功能分配唯一的字节型标识,这样客户端和服务器就能直接引用相同的常量值,避免硬编码导致的标识不一致问题。这能让客户端和服务器直接通过MessageType.LOGIN 这种方式调用,无需创建实例,实现全局共享,又能通过 final 关键字保证这些消息类型标识一旦定义就无法被修改。

public interface MessageType {
    public static final byte LOGIN = 1;  //登录
    public static final byte REGISTER = 2; //注册
    public static final byte PRIVATE = 3;  //私聊
    public static final byte GROUP = 4;   //广播
    public static final byte CHAT = 5;
    public static final byte YES = 6;
    public static final byte NO = 7;
}

        客户端和服务端需要定义相同的 MessageType 接口,确保两端的标识完全一致。

        接着设计 “消息头 + 消息内容” 的协议结构,用 1 个字节的消息头标识功能类型,用字符串形式的承载具体消息内容。并且约定不同消息头对应不同的内容格式,比如登录消息内容为 “用户名:密码”,聊天消息内容为 “目标对象:消息内容”。最后根据消息协议调整之前的收发消息方法。以客户端为例。

 new Thread(new Runnable() {
            public void run() {
                while (true) {

                    try {
                        //读取消息头
                        int head = is.read();
                        switch (head) {
                            case MessageType.LOGIN:
                                //读取返回结果
                                int b = is.read();
                                if(b == MessageType.YES){
                                    System.out.println("登录成功!");
                                }else {
                                    System.out.println("登录失败!");
                                }

                                break;
                            case MessageType.REGISTER:
                                break;
                            case MessageType.CHAT:
                                break;
                        }

                        String msg = readMsg();


                        System.out.println(msg);
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }

                }
            }
        }).start();

        String username = "21",password="20";

        os.write(MessageType.LOGIN);
        sendMsg(username+":"+password);

        while (true) {
            //发送消息
            Scanner scan = new Scanner(System.in);
            //发送消息:指定用户:消息内容
            String line = scan.nextLine();
            os.write(MessageType.CHAT);
            sendMsg(line);
        }
    }

3.实战应用

        我们通过一个简单的登录功能来初步应用我们的消息协议。

        客户端启动后,在 startClient 方法中,先向服务器写入 MessageType.LOGIN 消息头,再调用 sendMsg 方法发送“用户名:密码格式”的消息内容。

String username = "21",password="20";
os.write(MessageType.LOGIN);
sendMsg(username+":"+password);

        服务器的 ServerThread 线程在 run 方法中,先读取消息头,识别为登录请求后,调用handleLogin 方法解析消息内容。服务器将解析后的用户名和密码与本地 userList 中的用户信息比对,判断登录是否成功。

public void handleLogin(String msg)throws Exception{
    String[] msgArr = msg.split(":");
    for(User user : userList){
        if(user.getUsername().equals(msgArr[0]) && user.getUsername().equals(msgArr[1]) ){
            os.write(MessageType.LOGIN);
            os.write(MessageType.YES);
            return;
        }
    }
    os.write(MessageType.LOGIN);
    os.write(MessageType.NO);
}

        校验通过时,服务器发送LOGIN消息头 +YES字节;校验失败时,发送LOGIN消息头 +NO字节。客户端通过独立线程读取服务器响应,根据读取到的YES或NO字节,输出对应的提示信息。

int head = is.read();
switch (head) {
    case MessageType.LOGIN:
        int b = is.read();
        if(b == MessageType.YES){
            System.out.println("登录成功!");
        }else {
            System.out.println("登录失败!");
        }
        break;
}

4.总结

        以上仅是对消息协议应用的简易展示,体现其结构简单高效、扩展性强、逻辑清晰解耦的优点,要投入实际应用的话还要进一步深入和优化。下一步我们将通过图形化编程更加直观便捷地获取账号密码,完善登录注册功能,并且将其细化,对账号已注册、账号不存在、密码错误以及账号已登录等多种异常情况加以区分,提供更细致具体的提示词。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐