Java 网络通信编程(3):消息协议设计与应用
在前两期文章中,我们在发送消息前加上“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.总结
以上仅是对消息协议应用的简易展示,体现其结构简单高效、扩展性强、逻辑清晰解耦的优点,要投入实际应用的话还要进一步深入和优化。下一步我们将通过图形化编程更加直观便捷地获取账号密码,完善登录注册功能,并且将其细化,对账号已注册、账号不存在、密码错误以及账号已登录等多种异常情况加以区分,提供更细致具体的提示词。
更多推荐




所有评论(0)