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

相关推荐
吴声子夜歌19 分钟前
Guava——散列
java·guava
用户31268748772020 分钟前
AQS 到底怎么排队的?CLH 队列、state 与条件变量全链路拆解
java
Json____26 分钟前
springboot开发高校选课管理系统:从零构建前后端分离的全栈实践
java·spring boot·后端·毕业设计·毕设·wwwoop.com
xcl092536 分钟前
民宿预约系统开发实战:从需求分析到上线部署全流程解析
java·spring boot
蒸蒸yyyyzwd39 分钟前
cpp选手秋招学习笔记day24-cs144项目总结
运维·服务器·网络
步行cgn1 小时前
为何以继承方式引入SpringBoot
java·spring boot·后端
CoderYanger1 小时前
A.每日一题:1140. 石子游戏 II
java·程序人生·算法·leetcode·游戏·职场和发展·深度优先
禾高网络1 小时前
养老系统开发:背景、架构与养老行业展望
java·大数据·人工智能·小程序·架构
数据知道1 小时前
静态分析入门——PE 结构、字符串、导入表快速定性
网络·安全·web安全·网络安全
CCYe、1 小时前
新模型上线、旧模型下线:企业AI网关如何管住模型版本
java·网络·数据库·人工智能