Java聊天器
本文最后更新于:5 个月前
项目地址
https://github.com/Chao-Yin-Github/javaChartDemo
项目介绍
-
开发环境
-
Manjaro 18.1.4 Juhraya
-
JDK:1.8.0_212
-
intellij-idea 2019.3
-
-
本项目基于Java NIO开发,使用到了以下技术:
-
Java NIO
使用ServerSocketChannel和SocketChannel,完成客户端和服务端socket通道的建立,来实现数据传输
-
使用slf4j日志框架进行日志管理和程序信息输出
-
使用lombok简化Java Bean的Setter和Getter方法、构造方法、toString等模板化方法的撰写
-
自定义数据传输协议(Transmission.java)**
-
-
技术栈分析
-
NIO
-
背景
NIO是jdk1.4才引入的,有人称作Non-Blocking-IO(非阻塞IO),还有人喜欢把他成为New-IO。
我认为各自都有理,首先Non-Blocking-IO就不用说了,因为NIO提供了非阻塞的IO模式,这和传统的BIO(Blocking-IO)有了明显区别。
那为什么New-IO也是可以的呢,因为NIO是面向缓冲的(Buffer oriented),基于通道(Channel)(双工,既能读也能写)的IO操作方法,这样就和老式的BIO面向流(Stream oriented)(单方向,只能读或者只能写)的操作,有了本质的区别,所以把它称作New-IO也不无道理。
-
-
使用自定义协议传输数据(Transmission.java)
-
背景:
当我在基本分别完成传输字符串和传输文件的功能时,遇到了一个问题,NIO的Server是单线程的,那么就不能通过一般的方式解决传输文件和文本字符串的问题。
因为如果是多线程的服务端,可以在有一个新的连接到服务端时,服务端就开启一个线程专门处理这个客户端的连接,多个客户端之间不会混乱。
这样,我这个用户1可以先发送一个标识符来标识一下,我接下来将要发送文本字符串还是文件。如果有客户2同时发送别的信息,服务端的这两个线程之间是不会受到任何影响的。
但是通过NIO写的服务端是单线程处理连接的,这样,一个用户1发送一个标识符表示接下来会发送一个字符串,但是这时,又有另外一个用户把文件发送过来,服务端是没法分辨出到底是哪一个客户端发送的标识符,也无法分辨出是哪一个客户端发送的数据,这样服务端就会混乱掉。
-
解决办法
-
方法一:
最容易想到的是:
既然服务端无法标识用户,那么客户端就可以规定一个长度一定的标识,然后后面接着数据一起发送给服务端,那么服务端就可以直接把标识信息和数据一起读出来。
我传输文件用的是FileChannel,然后再使用transferFrom和transferTo方法来实现传到SocketChannel里面和从SocketChannel里面读取数据到FileChannel。
这样转化其实是可行的,但是我觉得有些麻烦也不够优雅。
-
方法二:
和方法一一样,都是把标识信息和数据一起传输,但是方式略有不同。
对于发送方的客户端,我们可以把信息和数据全部封装成一个类,然后我们就可以把这个类序列化成字符串,再把字符串转化为字节数组用SocketChannel传输。
而服务端则将接收的字节数组反序列化成对象,从中提取需要的信息,再序列化传输给另一个客户端。
至于另一个接收方的客户端,则同理,将获得的字节数组反序列化成对象,就可以拿到数据进行处理获取相应的内容了。
-
-
选择方法二的理由
我认为方法二比方法一更好的理由是:如果自定义一个类传输数据,我们就可以把这个类称作为一种数据传输协议。
因为像http传输协议就是一个基于tcp/ip的传输协议,它定义了请求/响应行(Request/Response Line),请求/响应头(Request/Response Header),空行和请求/响应数据(Request/Response Body),所有使用http协议都要有它所规定的这些结构。
而这个Transmission类其实也是一样的道理,声明了数据的类型(TransmissionType),也保存了数据内容(content)本身,还有消息目的方的信息(destinationNumber)等等信息。
由此,我可以大胆把这个Transmission类称作为我自己的一个聊天器协议!
-
Transmission.java
@Data @AllArgsConstructor @NoArgsConstructor public class Transmission implements Serializable { /** * 数据内容 */ private String content; /** * 文件类型 */ private TransmissionType transmissionType; /** * 发送目的客户编号 */ private int destinationNumber; /** * 原客户编号 */ private int sourceNumber; }
-
TransmissionType.java
public enum TransmissionType implements Serializable { /** * 文件类型 */ FILE, /** * 文本字符串 */ STRING, /** * 信息 */ MESSAGE, /** * id标识 */ UUID }
-
那这个Transmission类如何传输文本字符串和文件这两种数据呢?
对于文本字符串自不必说,把要发送的字符串存到content字段里即可,接收方可以直接获取content字段内容。
至于传输文件,我是先把文件转化成字节数组,再用Base64加密以防字符集乱码,再转化成字符串存到content里。
接收数据时,则跟发送数据时的操作正好相反,先把读到的字节数组转化成Transmission类,再拿到content字段内容之后,先用Base64解密,再转化为文件保存下来。
-
-
-
可能时由于网络速度的限制加上NIO的特性,这个在本地测试没问题,可是一放到服务器上就出现超过一定大小文件就发送失败的问题。可能和服务器的网速太慢还有NIO非阻塞的特性两者综合作用的结果。因为网速较慢,所以有时服务端接收到一半就认为没有数据了,而客户端的数据还没有发送上来,而服务端又是NIO非阻塞的特性,所以很可能数据读到一半就停止了,开始转化信息,但是数据不完整就会出错。这个由于时间问题还没有解决,以后有空的时候还是想找找其他方法解决一下。
本博客所有文章除特别声明外,均采用 知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议 。转载请注明出处!