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 不会收到这条消息。而普通消息仍然使用原来的广播。

相关推荐
Escalating_xu1 小时前
【Linux 网络编程】五种 I/O 模型与非阻塞 I/O:从“等数据”到正确处理 EAGAIN
java·linux·网络
程序猿乐锅1 小时前
万字详解Single-flight(单飞模式)
java·开发语言·分布式·后端·学习·single-flight
Java后端的Ai之路1 小时前
【AIAgent开发】-Agent工具封装成CLI(纯干货!!!)
java·javascript·人工智能·agent·cli
AntsData2 小时前
用 12K+ 条亚马逊评论信号,定位机械键盘下一版该改什么
java·大数据·计算机外设
默|笙2 小时前
【Linux网络】传输层协议TCP(3)滑动窗口、流量控制
linux·网络·tcp/ip
code2cat2 小时前
【随笔】连续批处理:长短请求怎样在迭代之间补位
java·人工智能·后端·大语言模型·vllm
最强小杰2 小时前
DeepSeek V4 Flash + Codex CLI 接入教程:原生 Responses API 适配,从配置到跑通收藏这篇就够了(2026)
java·前端·python·ai
Flynn Liang2 小时前
Spring AI 2.0 深度解析:ChatClient、Advisor 链与 MCP——Java 工程师的 AI 落地最优解
java·人工智能·spring·大模型·架构设计·spring ai
灿飞2 小时前
Kotlin与Java虚拟机六:基础与类型系统:一切皆对象,但对象有代价
java·kotlin
计算机毕设定制辅导-无忧学长3 小时前
《基于微信小程序健康习惯激励系统设计与实现》
java·spring boot·mysql·微信小程序·uniapp·健康习惯激励系统