Java通信-仿QQ聊天项目

前后历时一个多月的Java实现聊天通信项目-仿QQ聊天室基本告一段落,期间面对了很多问题,也有不同的解决方案,重写了几次核心代码,等等问题。现在在项目的结束之时,给自己做一个总结,算是一个回顾,算是一次提升,也是一次新的开始。


Github链接

https://github.com/SocraHat/ChatRoom.git

图形化界面

本次通信项目是基于图形化界面的聊天室,使用了基本的JFrame窗体,加上了各种JTextField、JButton、JLabel、JList组件,完成了注册登陆窗体、好友列表窗体、聊天窗体等,温习了前面学习的java中的swing组件的使用。


事件监听

想要图形化界面对做出的选择进行响应,事件监听是不可缺少的,对于一些基本的按钮、列表选择、鼠标双击等事件可以进行监听,然后程序做出不同的处理。可以说图形化界面的实际应用是和事件监听紧密相连的。


类的封装

之所以提到使用自己封装的类,是因为java三大特性之一的封装可以带给我们使用时很多便利,在本项目中,重点提一个MySocket类和一个ReceiveMessage类。

MySocket类:

MySocket类是包含了服务器端使用的socket对象的类,使用这个MySocket类可以使服务器在处理注册登陆、聊天、事件申请处理等不同要求时提供完备详细的信息,以及便捷的操作,比如一个socket肯定对应一个id,一个密码,一个昵称,一个自己的好友列表等信息。

ReceiveMessage类:

该类是针对每一个客户端处理来自服务器发来的各种信息的封装类,这里使用了多线程,阻塞式接收服务器的信息,根据通信协议处理不同的信息,有的是聊天信息,有的是申请服务的返回信息。在这个封装类中,都需要做出不同的处理结果。具体的内容在通信协议中会提到。


多线程

java通信若想实现一对多或者是多多的聊天,多线程是必不可少的,无论是服务器连接多个客户端,还是处理多个客户端发送来的信息,不同客户端接收服务器发来的信息等,都需要一个或多个线程来进行阻塞式接收信息。像以前总结的一样,多线程就是提供一个程序可以同时处理多个任务的功能,在聊天时,我们就需要一边发送信息,一边还可以接收信息。同样,服务器端更是需要不断的接收、处理、发送信息。
Java 线程


I/O操作

本项目使用I/O操作都是基本的InputStream和OutputStream流处理操作,使用了基本的read和write方法,都是读写字节数组。在每次写前,都会先发送一个字节数组的长度,接收端就根据这个长度申请一个字节数组,来接收接下来的字节数组。本人在项目早期的时候,使用的是readUTF和writeUTF方法,因为不能处理后面更多的传输内容(文件等),导致了一次大的程序重写,这是十分令人沮丧的。
Java I/O


通信协议

关于通信协议,这是在通信项目中最最最重要的一个点,可以说这是整个项目的骨架,所有功能的实现都离不开通信协议的制定,无论是注册、登陆、群聊私聊、发送文件等操作,都需要依托通信协议,来将客户端与服务器与客户端串联起来,实现不同的功能。
Java通信-仿QQ聊天项目_第1张图片

注册

用户注册需要先连接服务器,这时会使用暂时的socket对象,然后客户端把用户id、用户昵称、用户密码,按照“id|昵称(retsiger)”的形式,转化成字节数组发送到 服务器,服务器会读取字节数组转换成的字符串,按照“”将字符串拆分,一旦发现了“(retsiger)”即可判断出用户申请注册,接下来调用方法,确定用户是否已经注册,允许注册后会将记录存储到一个txt文件中,方便登陆,然后返回一个合法值以供客户端确认。

登陆

用户需要使用已经注册的id和密码进行登陆,首先仍旧是先连接服务器,然后向服务器发送一条“id*密码”的内容,服务器会查询HashMap以确定是否已经注册,或者是否已经登陆,然后会返回一个值给客户端,由客户端的ReceiveMessage对象接收后,做出正确的处理,允许登陆或者是拒绝登陆。这里要提一下,本项目有自动刷新id列表的功能,每当有新用户登陆服务器后,都会由服务器发送一条“id*id_list*DISDNEIRFHSULF”给所有客户端,由ReceiveMessage接收后,根据后面的“DISDNEIRFHSULF”来将id_list中所有的好友id拆分,重新填充到JList中,并刷新JList,实现了自动刷新id列表的功能。

刷新id

用户点击“刷新id列表”,客户端会发送一条“id*DISDNEIRFHSULF*DISDNEIRFHSULF”,服务器接收后将字符串进行拆分,读取到“DISDNEIRFHSULF”,便知道了客户端申请刷新id列表,服务器调用方法,将该id对应的MySocket中存储的id列表,按照“id1|id2|id3…”规则组成字符串,再按照“id*id_list*DISDNEIRFHSULF”发送到客户端,由客户端的ReceiveMessage进行处理,刷新好友列表。

公聊私聊

针对公聊会发送“id*聊天内容 * TNEILCLLA”到服务器,私聊则会发送“id*聊天内容TargetID” 到服务器,服务器会根据“”拆分后的第三部分内容来进行处理,如果是群聊,则向所有客户端发送群聊信息,如果是私聊,则会遍历所有的MySocket对象,根据id查找出目标targetID,然后发送聊天内容。

添加好友

客户端点击添加好友,会向服务器发送“id*请求添加的id*DNEIRFDDALLAC”,服务器接收会便会给目标客户端发送申请信息,目标客户端可以选择接收或者拒绝,如果接收,则刷新两者的好友列表,如果拒绝则提示申请者。

文件传输

同添加好友类似,客户端点击发送文件,选中文件后,向服务器发送“id* 目标对象id*ELIFDNESYLPPA”,服务器向目标客户端发送请求,目标客户端可以选择拒绝或者接收,如果接收,则将文件读取成字节数组开始传送到服务器,服务器将文件再传送到目标客户端,传送的内容包含了文件名。

通信协议总结:

纵然看起来似乎可以完成很多功能,但还是十分遗憾,这是一个失败的通信协议,原因就是,这个通信协议的结构是“id*内容/特殊协议内容*协议内容”,结构分为三个部分,客户端id,用来区分发起申请的客户端;内容部分是一些聊天的内容或者是一些重复的特殊协议;协议内容是采用了大写倒序的方式组成的协议关键词。
这种1+1+1结构是建立在字符串的基础上,将各个部分进行拆分,绝大部分功能的实现是没有问题的,个人也是觉得比较简便,便一直在使用,但是在传输文件时发生了致命的缺陷,我们知道,文件的传输需要传输字节数组,而我们在将字节数组转换成字符串,再由字符串转换回来时,字节数组中的内容已经发生了不可逆的变化!!!。本人经过很多次实验,无论是强制字符编码还是其他的转换方式,只要是字节数组转换成字符串,再由字符串转换成字节数组时,都会因为文件中某些编码问题或者是未知的问题,导致文件的传输失败(使用二进制方式打开可以看出某些内容已更改)。
字符编码
当然了,一般的文本文件或者是程序文件不回发生问题,但是图片、视频等文件就无法避免失败了。所以本人认为,正确的通信协议应该是建立在n+1+n的基础上,使用字节数组实现,第一个n是要发送字节数组的数量,第二个1是协议关键词,第三个n是要发送的n个字节数组,无论是服务器还是客户端,都会根据第二个1来进行不同的处理,根据第一个n来接收发送的n个字节数组。


TCP/UDP

本项目使用的是TCP/IP通信协议,使用了Socket/ServerSocket。我们都知道TCP是可靠传输,UDP是不可靠传输,两者各有优缺点,以传输内容准确为重使用TCP通信,以传输速度为重使用UDP通信。在java中若使用UDP通信则需要使用DatagramSocket和DategramPacket。


版本迭代

本人认为,在一个版本的基础上修改进化程序是一个良好的习惯,保存了上一个版本的副本,在出现重大失误时,起码有了一个还原的工作点,无论是做总结,记录日志还是工作还原,都是十分具有帮助的,本项目重写了两次,如果不是依托于版本迭代,花费的工作量将是十分巨大的。


总结

总的来说,虽然项目不够完美,但是也锻炼巩固了自己的JavaSE的基础,虽然出现了很多问题,导致程序的重写,但是通过自己钻研解决问题,也获取了很多知识,让自己印象更加深刻。一句话,纸上得来终觉浅,绝知此事要躬行。

你可能感兴趣的:(Socket通信)