Java网络编程(四)

文章目录

前面的聊天室已经可以实现多个客户端登录和群聊,服务端负责保存在线用户并把收到的消息广播出去。但这个版本还有一个比较明显的问题:所有消息默认都会发给其他在线用户,不能指定某个人单独聊天。如果直接在消息里写:

text 复制代码
@小明 你好

然后让服务端自己判断有没有 @,虽然也能做,但这种方式不太稳定。因为普通聊天内容里本身也可能出现 @,以后再增加表情、文件等消息后,服务端会越来越难判断一串数据到底代表什么。所以这次开始给聊天室重新设计一套消息协议

之前使用的协议比较简单:

text 复制代码
消息长度 + 消息内容

现在需要先告诉服务端:

text 复制代码
这是一条什么消息

然后服务端才能决定后面应该怎么读取。先规定:

text 复制代码
1 → 私聊
2 → 群聊

消息类型只需要占一个字节。


一、自定义消息协议

对于私聊来说,服务端不仅要知道消息内容,还要知道消息发给谁。所以私聊消息按照下面的顺序发送:

text 复制代码
消息类型
好友名称长度
好友名称
消息长度
消息内容

例如发送:

text 复制代码
@小明 hello

客户端先从字符串中提取:

text 复制代码
好友名称:小明
消息内容:hello

然后按协议写入 Socket。代码中首先判断第一个字符是不是 @

java 复制代码
char c1 = message.charAt(0);

if (c1 == '@') {
    // 私聊
}

接着找到用户名和消息之间的空格:

java 复制代码
int index = message.indexOf(" ");

截取好友名称:

java 复制代码
String friendName = message.substring(1, index);

再拿到真正的聊天内容:

java 复制代码
String msg = message.substring(index + 1);

例如:

text 复制代码
@小明 今天上课吗

最后拆出来就是:

text 复制代码
friendName = 小明
msg = 今天上课吗

然后按照已经约定好的格式进行发送:

java 复制代码
os.write(1);

int fn = friendName.getBytes().length;

os.write(fn);
os.write(friendName.getBytes());

int msglen = msg.getBytes().length;

os.write(msglen);
os.write(msg.getBytes());

也就是说,一条私聊消息在网络中的结构大致是:

text 复制代码
┌──────┬──────────┬────────┬──────────┬────────┐
│ 类型 │ 用户名长度 │ 用户名 │ 消息长度 │ 消息正文 │
└──────┴──────────┴────────┴──────────┴────────┘

群聊现在还没有群组 ID,所以暂时简单一些:

java 复制代码
os.write(2);

byte[] msgs = message.getBytes();

int len = msgs.length;

os.write(len);
os.write(msgs);

结构就是:

text 复制代码
消息类型 + 消息长度 + 消息正文

这次真正比较重要的不是记住这些数字,而是理解一个思路:发送端怎么写,接收端就必须按照完全相同的顺序去读。


二、服务端解析私聊和群聊

客户端把消息结构改了以后,服务端的 receive() 也必须跟着改。

原来服务端一上来就读取消息长度,现在第一步先读取消息类型:

java 复制代码
int type = is.read();

然后根据:

text 复制代码
1 或 2

决定后面应该怎么读取。如果:

java 复制代码
type == 1

说明收到的是私聊。服务端继续读取好友名称长度:

java 复制代码
int idlen = is.read();

再根据长度读取好友名称:

java 复制代码
byte[] id = new byte[idlen];

is.read(id);

String friendName = new String(id);

然后继续读取消息长度和消息正文:

java 复制代码
int msglen = is.read();

byte[] msgs = new byte[msglen];

is.read(msgs);

String msg = new String(msgs);

到这里,服务端实际上已经知道:

text 复制代码
这是一条私聊消息
目标用户是谁
真正的消息是什么

如果收到的是群聊:

java 复制代码
type == 2

就按照原来的消息长度和正文格式读取。当前代码为了方便后面的处理,把解析结果暂时拼成字符串返回:

java 复制代码
"1#" + friendName + "#" + msg

或者:

java 复制代码
"2# #"+msg

然后在消息处理线程里再次:

java 复制代码
String[] datas = msg.split("#");

取出:

java 复制代码
String type = datas[0];

String friendName = datas[1];

String msgs = datas[2];

三、私聊消息的定向转发

群聊比较简单,还是遍历所有在线用户发送,私聊就不能广播了。服务端已经有:

java 复制代码
ArrayList<ClientUser> SocketList

其中每一个 ClientUser 都保存了:

text 复制代码
account
socket

所以收到私聊以后,只需要遍历在线用户:

java 复制代码
for (int i = 0;
     i < SocketList.size();
     i++) {

    ClientUser cu1 = SocketList.get(i);

    if (cu1.account.equals(friendName)) {
        msgs = cu.account + "说:" + msgs;
        send(msgs,cu1.socket.getOutputStream());
        break;
    }
}

例如:

text 复制代码
A → @B 你好

服务端解析之后:

text 复制代码
发送者:A
目标用户:B
内容:你好

然后只找到 B 对应的 Socket:

text 复制代码
A
   \
    Server → B

C 不会收到这条消息。而普通消息仍然使用原来的广播。

相关推荐
ly76892 小时前
Java 设计模式详解:从原则到 23 种经典模式
java·开发语言·设计模式
萧瑟余晖3 小时前
Java深入解析篇三十之Java日志框架
java·开发语言
SendTomo3 小时前
跨设备文件互传新方案
javascript·网络·webrtc·html5·p2p
Suxing93 小时前
C语言基础分享:从“租房”到“拆迁”,C语言动态内存管理
java·数据结构·算法
减瓦3 小时前
别让 Java 版本选择变成生产事故
java
ikun_文3 小时前
Java集合框架Collection拓展进阶
java·java ee
啦啦啦啦啦zzzz3 小时前
moduo网络库 Acceptor TcpConnection
服务器·网络·c++·reactor·网络库
码匠许师傅4 小时前
【C++ 面试真题】聊聊 C++ 的虚函数和多态
java·c++·面试
chuan.bai4 小时前
Java RAG 实战(第 8 篇):Spring Boot RAG 查询 API
java·spring boot·贪心算法