基于JAVA的即时通信软件

SMASHBOX

发布日期: 2018-11-03 13:32:16 浏览量: 3048
评分:
star star star star star star star star star_border star_border
*转载请注明来自write-bug.com

一.设计任务书

1.1 设计任务

本文设计的是一个简单的即时通信软件,利用 Java Socket 进行点到点通信,其工作机制模仿即时通信软件的基本功能,已实现的功能有:

  • 客户端登录
  • 客户端退出
  • 群组成员之间传输文字或图片信息

该软件分为客户端与服务器端,客户端负责与服务器建立连接,且执行收发消息的操作,服务器端负责等待客户端连接并保存用户的昵称与客户端 Socket 的输出流的对应关系。

1.2 技术指标

本程序使用的是 TCP 协议实现的即时通信软件,程序是基于 Java 语言开发的,主要用到的技术有:

  • Socket 编程
  • 自定义协议

如果使用普通的方法来标记一条消息的结束,如换行符,那么程序就不易扩展,只能发送纯文本消息,所以需要自己定义一种消息的格式,并且我们还需要提供发送消息与解析消息的方法。

服务器端创建一个 ServerSocket,循环等待客户端的连接,每当有客户端连接,就获取到客户端的 Socket 对象,并将该对象交付给一个服务器端线程处理,服务器端线程会不断从 Socket 的输入流中解析出消息类型、长度及消息内容,然后根据类型执行不同的操作。

客户端与服务器建立连接,同时开启一个客户端线程接收服务器端发送的消息,客户端登录是向服务器发送一条登录命令,客户端向服务器发送一条消息首先需要包装成定义的消息格式,然后再发送给服务器。

不管是发送消息还是发送命令其实本质都是一条消息,向服务器发送的消息都必须按照定义的格式来。

1.3 论证结果

经论证,这个任务是可行的。TCP 协议的实现细节 Java Socket 已经帮我们做好了,我们需要做的是定义一个协议工具类,实现发送消息与接收消息的方法,然后客户端与服务器端都利用这两个方法来进行消息的发送与解析。

二.实现原理

2.1 基于 TCP 协议的即时通信

TCP 协议是一种端到端协议,当一台计算机要与远程的另一台计算机连接时,TCP 协议会让他们建立一个用于发送和接收数据的虚拟链路。TCP 要负责收集数据信息包,并将其按照适当的次序放好传送,接收端收到后再正确的还原,TCP协议使用了重发机制,当一个通信实体发送一个消息到另一个通信实体后,需要接收到另一个通信实体的确认消息,如果没有收到确认消息,则会重发消息。所以 TCP 协议保证了数据包在传输中不发生错误。通信示意图如图 1 所示。

在通信实体 1 与通信实体 2 建立虚拟链路前,必须有一方主动来接收其他通信实体的连接请求,作出“主动”的通信实体称为服务器,发出连接请求的通信实体称为客户机。

2.2 自定义协议的定义

2.2.1 通信原理

客户端与服务器端相互通信,首先要建立 Socket 连接,连接建立好后双方都会拿到一个 Socket 对象,通过 Socket 对象拿到输入、输出流可以实现写、读的功能。服务器端接收到客户端的连接,将客户端的 Socket 对象交付给一个线程,该线程负责维护该客户端,在线程体中需要使用死循环不断的获取客户端发给服务器的消息。

2.2.2 存在的问题

那么问题来了:怎么标志客户端发送的消息的结尾?如果不对结尾标志,服务器端将不知道客户端本次客户端发送的消息到哪里。

2.2.3 文本消息的解决办法

对文本消息的一般做法是将‘\n’作为结尾标记,操作过程如下:

  • 客户端与服务器端建立连接,服务器端将客户端的 Socket 加入集合中保存,并将客户端的 Socket 交付给一个服务器端线程处理,服务器线程初始化套接字、输入输出流,然后一直循环等待客户端发送消息

  • 客户端向服务器发送消息“Hello World!\n”,服务器线程获取到客户端发送的消息,然后使用输入流读取一行消息,读取到的消息是“Hello World!”,然后遍历服务器端的那个集合,获取到集合中每个 Socket 的输出流,向每个输出流中写入消息。

以上是一般意义上群聊的实现原理:客户端向服务器发送消息,服务器获取到消息后转发给群组中的所有成员。

2.2.4 依然存在的问题

  • 问题一:如果发送的是图片、文档应该怎么标记消息的结束?
  • 问题二:在实际应用中,客户端向服务器端发送消息并不像刚才的例子那么简单,还需要登录、注销、登录成功等命令,怎么来区别这些命令?

2.2.5 自定义协议的内容

为解决以上问题,我们规定:消息的发送与解析都必须使用以下格式:

由表 1 知,数据分为三个部分

  • type:1 字节,表示发送的消息类型,所以可以表示 65535 种消息类型。
  • totalLen:4 字节,整型数据,表示发送的消息的总长度,包含 type、totalLen的长度以及消息内容的长度,totalLen 占用 4 字节,所以最大可以发送 2G 的数据。
  • bytes[]:字节数组,表示发送的消息的内容,大小没有限制。

2.2.6 使用自定义协议

制定消息的规范后以上两个问题都会迎刃而解了,客户端向服务器端发送消息的过程如下:

例 1:发送纯文本

客户端:

  • 客户端的视图与用户交互获取到用户要发送的文本内容“你好啊”
  • 客户端将获取到的文本内容转化为字节数组 bytes
  • 客户端将消息包装成自定义的消息格式,如表 2 所示

  • 客户端往输出流中写入消息

服务器端:

  • 服务器端线程一直等待接收客户端的消息
  • 服务器端线程获取到客户端发送的消息,按照格式解析出消息的类型、长度以及消息内容
  • 服务器端线程获取到的消息类型是文本类型 TYPE_TEXT,那么需要遍历服务器端的集合,获取到集合中每个 Socket 的输出流,使用这个输出流对消息转发,在转发前同样需要包装成定义的消息格式。

例 2:登录功能

客户端:

  • 客户端与服务器端建立连接之后,将用户输入的昵称包装成一条消息,如表 3 所示,消息类型是 TYPE_LOAD,字节数组 bytes 是用户昵称。

  • 在建立连接的同时客户端会开启一个线程等待接收服务器端的消息。
  • 将消息发给服务器端。
  • 客户端线程如果收到服务器端反馈的信息,就将信息告知用户。

服务器端:

  • 接收到消息后获取到消息类型为 TYPE_LOAD,服务器端就可以知道这条消息是登录请求,然后 bytes[]数组里的数据就是用户昵称,将用户昵称、该客户端的 Socket 对象的输出流先保存到 Map 中,然后将该 Map 保存到集合中。
  • 服务器端线程对客户端的登录请求处理完成后,向客户端反馈一条标记着是否操作成功的消息,类型是 TYPE_LOADSUCCESS 和 TYPE_LOADFAIL,bytes 数组是服务器端反馈的消息。

如果登录成功,服务器反馈的消息格式如表 4 所示。

如果登录失败,服务器反馈的消息格式如表 5。

2.2.7 小结

其实不管客户端发送的消息是哪种类型,客户端只需要负责将要发送的消息转化为字节数组,然后对数据包装后发送给服务器。对于一些非命令类的消息,服务器接收到消息后只需要根据数据类型对数据进行解析、包装、转发即可。对于一些命令类的消息,如登录,退出等功能,则需要服务器端执行相应的操作,服务器端不需要对消息转发,可能需要对一些命令给予反馈。

通过自定义协议可以解决上述的两个问题,并给出了客户端与服务器端使用自定义协议发送消息的两个详细步骤

2.3 自定义协议的实现

这个自定义协议就是自己定义的一个发送消息、解析消息的规范,无论是发消息还是收消息都必须按照这个规范来,实现这个协议无非需要考虑三个问题:

  • 问题一:如何发送消息?
  • 问题二:如何解析消息?
  • 问题三:如何表示解析消息后的结果?

我们只需要定义两个类,协议工具类 Protocol 负责消息的发送与解析,消息结果类 Result 封装了一个消息的三个部分:type、totalLen、bytes,协议工具类对消息解析后会返回一个 Result 对象表示一次解析的结果。所以这两个类结合起来使用就可以解决以上三个问题。

2.3.1 协议工具类的实现

协议工具类 Protocol 负责消息的发送与解析,内部需要定义消息的格式,协议工具类的设计如图 2 所示。

2.3.1.1 消息类型

消息类型是协议工具类的静态的公共的整型常量,这样的设置为程序后期的扩展提供了方便,提供的消息类型如表 6 所示。

2.3.1.2 发送消息

发送消息就是按照定义的格式往输出流中写入数据,我们首先要做的是包装数据定义方法签名,如表 7 所示。

  • 包装数据

一条数据有三部分,消息类型、消息内容已经通过参数获取到了,消息的长度还要程序计算:消息长度=消息的内容的长度+5 字节。

  1. int totalLen = 1 + 4 + bytes.length;
  • 按格式写入输出流,先写入消息类型,然后写入消息的总长度,最后再写入消息的内容。
  1. dos.writeByte(type);
  2. dos.writeInt(totalLen);
  3. dos.write(bytes);
  4. dos.flush();

2.3.1.3 解析消息

解析消息是指将从输入流中读取到一条消息,然后按照格式转化为一个结果对象 Result。
定义方法签名如表 8 所示。

  • 消息提取

从输入流中依次读取三部分:type、totalLen、bytes[],dis 是方法的参数,调用方法时需要传入输入流

  1. byte type = dis.readByte();
  2. int totalLen = dis.readInt();
  3. byte[] bytes = new byte[totalLen - 4 - 1];
  4. dis.readFully(bytes);
  • 结果返回

将提取出来的数据的三个部分封装成一个结果对象作为方法的返回值,注意第一个参数:type & 0xFF,因为 type 是字节,需要与 0xFF 进行“与”运算得到一个整型值。

  1. return new Result(type & 0xFF, totalLen, bytes);

2.3.2 结果类的实现

结果类 Result 封装一条消息的三个部分,主要提供了 setter、getter 方法来设置或者获取消息的三个组成部分,结果类的设计如图 3 所示。

2.3.2.1 消息格式

Result 类定义了消息的格式,消息的组成如表 9 所示。

2.3.2.2 方法

Result 类的方法签名如表 10 所示。

2.4 服务器端的实现

2.4.1 服务器类

Server 类负责等待客户端连接并将连接上的客户端交付给服务器线程类。Server 类的设计如图 4 所示。

clients 维护一个 List 集合,集合中每个元素都是 Map,每个 Map 中都有两个 item,保存着客户端的昵称和对应的输出流。

main 方法中要实现的是等待客户端连接,使用 ServerSocket,有客户端连接时开启一个线程来处理。代码如下:

  1. ServerSocket serverSocket = new ServerSocket(30000);
  2. while (true) {
  3. Socket socket = serverSocket.accept();
  4. new Thread(new ServerThread(socket)).start();
  5. }

2.4.2 服务器线程类

ServerThread 类负责接收客户端的消息并对消息进行相应的处理,ServerThread 类的设计如图 5 所示。

2.4.2.1 变量

ServerThread 类的变量以及其含义如表 11 所示。

2.4.2.2 方法签名

ServerThread 类的方法签名以及含义如表 12 所示。

2.5 客户端的实现

2.5.1 客户端界面

界面的元素有:登录输入框、聊天内容文本域、消息输入文本域、发送按钮。客户端界面初始化时会调用 Client 的方法执行客户端与服务器的连接请求,连接成功后客户端与服务器端会形成一个虚拟链路,当用户输入用户名后回车,客户端通过该虚拟链路向服务器端发送一条登录命令。View 类的设计如图 6 所示。

2.5.2 客户端

客户端 Client 负责处理客户端连接、客户端发送消息的任务,Client 类的设计如图 7 所示。

2.5.2.1 建立连接

  1. socket = new Socket(address, port);
  2. dos = new DataOutputStream(socket.getOutputStream());
  3. // 监听服务器发回的消息
  4. new ClientThread(socket).start();

2.5.2.2 登录

  1. public void load(String user) {
  2. Protocol.send(Protocol.TYPE_LOAD,user.getBytes(), dos);
  3. }

2.5.2.3 发送消息

  1. public void sendMsg(String msg) {
  2. Protocol.send(Protocol.TYPE_TEXT, msg.getBytes(), dos);
  3. }

2.5.2.4 退出

  1. public void logout(){
  2. Protocol.send(Protocol.TYPE_LOGOUT, "logout".getBytes(),dos);
  3. }

客户端线程负责接收服务器端发的消息,其对消息的处理方法与服务器端线程的处理方法类似,都是先解析消息,然后根据消息类型执行相应的操作。

2.5.2 客户端线程

客户端线程主要负责接收消息,并对接收到的消息进行显示。ClientThread类的设计如图 8 所示。

ClientThread 类维护的是客户端的套接字以及输入流,那三个方法的作用和服务器端线程类似,这里不再细说。

三.实验结果

3.1 运行结果

  • 服务器端启动后是没有运行界面的,运行结果如图 9 所示。

  • 客户端启动后初始界面如图 10 所示。

  • 输入用户名后回车登录,登录后如图 11 所示。

  • 两个客户端互发消息,如图 12 所示。

  • 单个客户端退出

3.2 主要问题及故障分析

3.2.1 主要问题

  • 不知道如何标记一条消息的结尾
  • 界面问题

3.2.2 故障分析

对于第一个问题:如果只是发送一条文本消息的话,是没有这个问题的。但是为了使程序拥有更好的扩展性,使其可以发送图片以及文档,这个问题还是值得思考的。定义一种消息的格式,无论是发送还是接收消息都按照这个标准来,这个就是我们定义的协议工具类的作用。

对于第二个问题:本程序是基于 Java 语言开发的,AWT 和 SWING 是 Java 语言开发 GUI 的工具包。SWING 和 AWT 写界面都不是很方便,所以本程序的界面有点粗糙。

3.3 设计结论

由于之前写过这类的程序,所以在程序层次上的实现并不难,本次实验不仅巩固了编写程序的功底,还加深了对 Socket 通信底层理论的理解,可以说是收获非常大。至此,本论文已经接近尾声,所研究的是一个简单的即时通信软件的实现过程以及实现原理。

四.附录一:实验相关

4.1 实验数据

客户端与客户端 2 发送的消息数据,[]内部表示的是发送方的昵称,昵称外部是发送的消息内容,具体实验数据如下:

  • [客户端 1]我是客户端 1
  • [客户端 1]你好啊!
  • [客户端 2]我是客户端 2

4.2 系统软硬件环境

4.2.1 硬件环境

  • 系统:Window7 旗舰版
  • 系统类型:64 位操作系统
  • 处理器:i5-4210U
  • 安装内存:4.0GB

4.2.2 软件环境

已安装 JRE、JDK 并配置好环境变量

4.3 使用说明

本程序分为客户端与服务器端,首先需要启动服务器端,然后可以打开多个客户端,客户端打开后可以进行聊天。

4.4 参考资料

[1] 李刚,疯狂 Java 讲义第 3 版,北京:电子工业出版社,2014.7

[2] James F.Kurose,Keith W.Ross,计算机网络-自顶向下方法上册(第 5 版),北京:高等教育出版社

上传的附件 cloud_download 基于JAVA的即时通信软件.zip ( 1.50mb, 560次下载 )
error_outline 下载需要2点积分

发送私信

我这种人,别爱别信别打动

8
文章数
7
评论数
最近文章
eject