socket-demo的实现 (2)

服务端分两次读取到了两个数据包,第一次读取到了D1包的部分内容D1_1,第二次读取到了D1包的剩余内容D1_2和D2包的整包。如果此时服务端TCP接收滑窗非常小,而数据包D1和D2比较大,很有可能会发生第五种可能,即服务端分多次才能将D1和D2包接收完全,期间发生多次拆包。

2. 解决思路

由于底层的TCP无法理解上层的业务数据,所以在底层是无法保证数据包不被拆分和重组的,这个问题只能通过上层的应用协议栈设计来解决,根据业界的主流协议的解决方案,可以归纳如下:

消息定长,例如每个报文的大小为固定长度200字节,如果不够,空位补空格;

在包尾增加回车换行符进行分割,例如FTP协议;

将消息分为消息头和消息体,消息头中包含表示消息总长度(或者消息体长度)的字段,通常设计思路为消息头的第一个字段使用int32来表示消息的总长度;

更复杂的应用层协议。

3. demo方案

作为socket长连接的demo,使用了上述的解决思路2,即在包尾增加回车换行符进行数据的分割,同时整体数据使用约定的Json体进行作为消息的传输格式。

使用换行符进行数据分割,可如下进行数据的单行读取:

BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream())); String message; while ((message = reader.readLine()) != null) { //.... }

可如下进行数据的单行写入:

PrintWriter writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true); writer.println(message);

Json消息格式如下:

服务端接收消息实体类

@Data public class ServerReceiveDto implements Serializable { private static final long serialVersionUID = 6600253865619639317L; /** * 功能码 0 心跳 1 登陆 2 登出 3 发送消息 */ private Integer functionCode; /** * 用户id */ private String userId; /** * 这边假设是string的消息体 */ private String message; }

服务端发送消息实体类

@Data public class ServerSendDto implements Serializable { private static final long serialVersionUID = -7453297551797390215L; /** * 状态码 20000 成功,否则有errorMessage */ private Integer statusCode; private String message; /** * 功能码 */ private Integer functionCode; /** * 错误消息 */ private String errorMessage; }

客户端发送消息实体类

@Data public class ClientSendDto implements Serializable { private static final long serialVersionUID = 97085384412852967L; /** * 功能码 0 心跳 1 登陆 2 登出 3 发送消息 */ private Integer functionCode; /** * 用户id */ private String userId; /** * 这边假设是string的消息体 */ private String message; } 客户端或服务端掉线检测功能 1. 实现思路

通过自定义心跳包来实现掉线检测功能,具体思路如下:

客户端连接上服务端后,在服务端会维护一个在线客户端列表。客户端每隔一段时间,向服务端发送一个心跳包,服务端受收到包以后,会更新客户端最近一次在线时间。一旦服务端超过规定时间没有接收到客户端发来的包,则视为掉线。

2. 代码实现

维护一个客户端map,其中key代表用户的唯一id(用户唯一id的身份验证下面会说明),value代表用户对应的一个实体

/** * 存储当前由用户信息活跃的的socket线程 */ private ConcurrentMap<String, Connection> existSocketMap = new ConcurrentHashMap<>();

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wssgdd.html