一款可在局域网内进行文字和语音聊天且能够发送文件(可断点续传)的简易版QQ。
能进行用户管理,所有用户必须登录到服务器,由服务器维护在线信息 IM功能:用户登录后能够进行实时多方点到点的短信息通信,如聊天 能选择要求服务器进行转发服务 能保存通信记录到数据库 能进行双方文件传输,能显示进度 支持断点重传,检查时需有功能随时中断传送,并在下次启动时能显示重传状态 数据包加密 实时语音双向传输功能 界面设计要求布局合理,信息清晰
软硬件:win10操作系统+MySQL5.7+IDEA
3.1 总体结构框架
图 1 总体结构框架
LoginFrame.java 图形用户界面是完全地模仿QQ登录界面来做的,仿真度高。

当用户输入用户名和密码,并点击“按钮”时,会触发事件监听,触发SocketClient线程启动,在SocketClient连接服务端的Ip和1248端口:建立Socket连接,等候利用Socket创建输入流和输出流,输出流将用户名和密码(用“&&”连接)写入,通过Socket通道传给服务端,服务端接收请求后进行处理,并根据用户输入的用户名和密码再数据库中查找,
若用户名和密码都存在且匹配,返回"login successfully"给loginMsg对象,loginMsg对象返回1给LoginFrame对象,并弹出“登录成功”的对话框,提示用户登录成功;
若用户名存在但密码不匹配,返回"password incorrect"给loginMsg对象,loginMsg对象返回-1给LoginFrame对象,并弹出“密码错误”的对话框,提示用户密码错误,并将登录界面LoginFrame对象的密码栏清空;
若用户名不存在,返回"username not exist"给loginMsg对象,loginMsg对象返回0给LoginFrame对象,并弹出“用户名不存在”的对话框,提示用户密码错误,并将登录界面LoginFrame对象的用户名栏和密码栏清空;
若服务器存在异常,则返回未知字符串给loginMsg对象,loginMsg对象返回886给LoginFrame对象,并弹出“服务器错误”的对话框,提示用户服务器有异常,并将登录界面LoginFrame对象的用户名栏和密码栏清空;
考虑到用户体验,用户注册我们考虑了采用Web端的形式来实现。前端页面用到LayUI框架以及JSP作为数据的响应界面
用户填写表单,并点击提交按钮,则将注册请求发送给后台的Servelt(RegisterServlet.java) RegisterServlet处理请求数据,并执行查询数据库的操作,若数据库查询不到用户输入的用户名,则证明该用户名不存在,即可以注册,并将用户名和密码写入到数据库中,然后跳转到注册成功页面,提示用户注册成功。 若数据库查询到用户输入的用户名已存在,即无法注册,然后跳转到注册失败页面,提示用户注册失败,然后提示用户回到注册页面重新注册。
主界面为用户登录成功后主要操作界面,其分为功能框、好友列表框、聊天记录框、聊天信息展示框、用户信息展示框、搜索框、文本输入框、加好友框。 具体如下图:
图 2 功能框
图 3 好友列表框
图 4聊 天记录框
图 5 聊天信息展示框
图 6 用户信息展示框:
图 7 搜索框
图 8文本输入框
图 9加好友框
功能框:主要有四个JLabel组成,从上到下分别为登录用户头像标签、消息标签、用户标签、加好友标签;点击消息标签,页面会切换到聊天记录框、聊天信息展示框、文本输入框;点击用户标签,页面会切换到好友列表框、用户信息展示框;点击加好友标签,则会弹出加好友对话框。 好友列表框:好友列表框主要展示用户目前所拥有的好友,点击里面任意一个好友,则会切换出好友信息展示框。 聊天记录框:聊天记录框主要展示目前与好友聊天的记录,上面可展示最新消息的内容与时间,接收到好友信息时还会声音和小圆点提醒。点击任意一条记录,则会切换到聊天信息展示框、文本输入框。 聊天信息展示框:该界面主要展示与好友谈话时的信息以及收发的文件。 用户信息展示框:用户信息框主要展示用户的头像和一些信息,点击发消息则会切换到聊天信息展示框、文本输入框。 搜索框:搜索框主要用来搜索聊天记录,正确输入朋友名字,点击搜索后则会切换到聊天信息展示框、文本输入框。 文本输入框:文本输入框主要用来发送信息、发送文件以及发送语音功能。 加好友框:加好友框主要用来添加好友。
开启接收消息ReceiveMsg线程:首先连接服务器的发送消息的端口Socket,连接成功成功后此线程就会不会接受服务器推送过来的消息,消息为其它朋友发给我的文本消息或者文件消息,客户端接收到后再把消息展示到聊天信息展示框。 开启发消息SendMsg线程:首先当用户需要发送消息都要开启该线程,连接服务器接收消息的端口Socket,连接成功后,则把该消息发给服务端,让服务器转发给好友。
开启发送请求的SendMsg线程:当用户使用该功能时,客户端会实例化该线程,把要添加的好友信息发送给服务器,服务器通过判断,会返回不同信息给客户端,分别是:你们已经是好友、user not exist、请求成功;当收到请求成功时则代表已经向该好友发送添加请求。 接收好友请求的ReceiveNewFriend线程:该线程会连接服务器推送好友的端口Socket,接收别人的好友请求,如果有好友请求,则会弹出一个对话框,用户可以选择拒绝或同意。 接收请求反馈的SendMsg线程:该线程连接服务器的发送消息的端口Socket,会接收好友请求反馈信息,添加成功或添加失败。
用户登录成功后,实例化ClientMainFrame,并连接到服务端ip为121.199.76.157和端口为2222的Socket,实时接收服务端传来的消息。连接服务端成功时,会将用户名通过Socket传递给服务端,服务端中以客户端发过来的用户名和键、以客户端所连接的Socket对象为值存入到其中的HashMap中,作为会话保存; 当用户点击要进行语音通话的对象时,获取到其用户名,并将“voice&&用户名&&目标用户名”通过Socket发送给服务端,服务端解析字符串,然后利用HashMap来查找各自的Socket,然后将“voice&&请求者用户名”发送给另外一个客户端,该客户端若点击同意,则自身实例化为一个服务端,并将自己的ip和端口(随机数生成)通过服务器发送给请求方,并等待请求方的连接; 请求方一旦连接上被请求的服务端,则两者建立连接,调用各自的麦克风,进行语音聊天。 结束聊天时,点击屏幕右下角的红色按钮即可
开启获取所有头像的getfriends线程:若本地没有用户头像,则客户端会开启该线程连接服务器发送图片端口,获取自己的所有好友的头像,并在主界面的好友列表框展示出来。 开启获取单个头像的get_user_photo线程:若本地没有某个用户的头像,则客户端会开启该线程连接服务器发送图片端口,获取某个用户的头像,此线程用户登录时获取自己的头像自己添加好友成功时获取好友的头像。 使用getfriends_file方法:该方法用于从本地获取相关用户的头像。
开启登陆的ServerSocket的线程:实例化一个ServerSocket(login_port),设置一个登陆用的socket端口,循环执行ServerSocket.accept(),创建一个可缓存线程池。有客户端连接此端口的socket时,便将下面的登陆检测线程加入线程池,由线程池去管理线程。 登陆检测线程:等待客户端发送用户名和密码,通过socket获取到用户名和密码后,检测是否存在数据库server,不存在则创建。检测数据库server是否存在用户信息表server_users,不存在则创建。检测用户信息表中有没有此用户名,没有则向客户端发送”user not exist”指令并结束线程,有此用户名则检测密码是否正确,不正确则向客户端发送”password incrrect”指令并结束线程,密码正确则发送”login successfully”指令,即登陆成功,再更新用户信息表中该用户的ip地址。检测用户的代收消息表、好友列表和聊天记录表是否存在,不存在则创建,检测用户的缓存文件夹是否存在,不存在则创建,然后检测用户是否有头像,没有则设置一个默认头像。
开启接收文本消息的ServerSocket的线程:实例化一个ServerSocket(recMsg_port),设置一个接收文本消息用的socket端口,循环执行ServerSocket.accept(),创建一个可缓存线程池。有客户端连接此端口的socket时,便将下面的接收文本消息线程加入线程池,由线程池去管理线程。 接收文本消息线程:先通过socket的ip地址去用户信息表中获取此socket的用户名user。等待客户端发送文本数据,获取到客户端要发送的目标用户名target、消息类型type和要发送的信息msg,就往目标的代收消息表中插入此条记录:【发送者:user,类型:type,内容:msg,时间:date】。其中消息类型有:text文本消息类型、addfriend请求加好友类型。
开启接收文件消息的ServerSocket的线程:实例化一个ServerSocket(recFile_port),设置一个接收文件消息用的socket端口,循环执行ServerSocket.accept(),创建一个可缓存线程池。有客户端连接此端口的socket时,便将下面的接收文件消息线程加入线程池,由线程池去管理线程。 接收文件消息的线程:先通过socket的ip地址去用户信息表中获取此socket的用户名user。等待客户端发送指令,接收到客户端发送指令的内容包括:发送的目标用户,文件名,文件大小。断点续传算法:判断在此用户user的缓存文件夹中是否存在此文件,不存在则创建,如存在,则判断缓存文件夹中已存在的文件大小是否小于指令里的文件大小,如果小于则说明此文件没传完,需要断点续传,则将已存在的文件大小发送给客户端,客户端跳过这个长度去继续发送,服务端这边则接收字节流继续往这个已存在但没传完的文件写入数据,直到两个文件大小相等了则结束接收,或者客户端主动停止了文件的传输(通过一段时间没有数据传入来判断客户端停止了传输数据)。如果文件传输完成,则向目标target的代收消息表中插入此条记录:【发送者:user,类型:file,内容:文件名,时间:date,大小:文件大小】。
开启推送文本消息ServerSocket的线程:实例化一个ServerSocket(pushMSg_port),设置一个推送文本消息用的socket端口,循环执行ServerSocket.accept(),创建一个可缓存线程池。有客户端连接此端口的socket时,便将下面的推送文本消息线程加入线程池,由线程池去管理线程。 推送文本消息线程:先通过socket的ip地址去用户信息表中获取此socket的用户名user。再用一个循环去检测此用户user的代收信息表中有没有记录,有记录则代表有消息要传给此用户的客户端。有记录则一条一条取出,逐条处理,从记录中取出信息包括:【发送者,类型,内容,时间,大小】,将其中各项信息拼接,直接用socket发送拼接后的文本信息,然后在代收表中删除此记录。其中文件消息也是这里推送,但是不传文件数据,只传文件名和大小,具体传文件数据是要客户端点击聊天面板中这个文件后才连接推送文件模块来传的。
开启推送文件ServerSocket的线程:实例化一个ServerSocket(pushFile_port),设置一个推送文件用的socket端口,循环执行ServerSocket.accept(),创建一个可缓存线程池。有客户端连接此端口的socket时,便将下面的推送文件线程加入线程池,由线程池去管理线程。 推送文件线程:等待客户端发来指令,指令内容包括:【目标用户名,文件名,断点续传要跳过的文件长度】,接收到指令,通过文件名在目标用户的缓存文件夹中找到该文件,判断指令中断点续传要跳过的文件长度是否小于该文件长度,如果等于则不需要传。小于则跳过断点续传要跳过的长度,接着传给客户端。
开启推送好友列表ServerSocket的线程:实例化一个ServerSocket(pushFriends_port),设置一个推送好友列表用的socket端口,循环执行ServerSocket.accept(),创建一个可缓存线程池。有客户端连接此端口的socket时,便将下面的推送好友列表线程加入线程池,由线程池去管理线程。 推送好友列表线程:先通过socket的ip地址去用户信息表中获取此socket的用户名user。等待客户端发来指令,指令有获取所有好友的头像或者单个好友的头像,客户端接收到头像后通过头像文件的文件名来获取好友用户名。传所有头像:先去user的好友列表中获取所有好友的用户名,逐个调用发送头像方法发送。发送头像方法:打开好友的缓存文件夹获取到好友的头像文件,把文件名,文件大小发送给客户端,然后以字节流的形式发送。传单个头像直接调用发送头像方法发送。
开启推送加好友请求ServerSocket的线程: 实例化一个ServerSocket(pushNewFriend_port),设置一个推送加好友请求用的socket端口,循环执行ServerSocket.accept(),创建一个可缓存线程池。有客户端连接此端口的socket时,便将下面的推送加好友请求线程加入线程池,由线程池去管理线程。 推送加好友请求线程:先通过socket的ip地址去用户信息表中获取此socket的用户名user。然后去检查此用户user的代收表中有没有类型为addfriend类型的记录,有则取出来,获取到源用户名即是谁请求加的user,将源用户名发送给客户端,客户端处理之后返回指令,指令包括:同意添加,拒绝添加。若是拒绝添加则直接在user的代收表中删除此记录。若是同意则往双方的好友列表插入对方的用户名,然后再源用户即请求方的代收表中插入一条加好友反馈信息,格式:【发送者:user,类型:add_return,内容:add successfully,时间:date】,然后源用户及请求方会在推送文本消息模块接收到此反馈消息。
聊天面板负责展示最近的聊天记录,包括用户发送给好友的文本消息,文件消息,好友给用户发送的文本消息,文件消息。聊天面板采用盒式布局,用户发送的消息靠右显示,好友发送的消息靠左显示。其中文件消息显示文件发送或下载进度,并且需要加监听器。用户发送给好友的文件消息加发送文件监听器,好友发给用户的文件消息加下载文件监听器。 发送文件监听器:判断此文件有没有线程在发送,没有则创建发送文件线程去发送此文件。若有线程,则判断是否正在传,如果是正在传则暂停传输,如果没有正在传则判断进度条的值是否小于100%,如果小于100%则继续传,如果等于100%则调用文件管理器打开此文件。 下载文件监听器:判断此文件有没有线程在下载,若没有线程,则创建下载文件线程下载。若有线程在下载,判断文件是下载状态还是暂停状态,若是下载状态则变成暂停状态,若是暂停状态,则判断此文件有没有下载完,若没有下载完则继续下载,下载完则调用文件管理器打开此文件。 发送文件线程:连接服务器的发送文件模块,向此模块发送指令:目标用户名,文件名,文件大小。然后等待服务器返回要跳过的断点续传的长度,然后跳过此长度来继续以字节流形式传给服务器。 下载文件线程:连接服务器的推送文件模块,向此模块发送指令;目标用户名,文件名,服务器要跳过的文件长度。然后接收字节流,写入文件。
与其他服务端一样,先等待客户端Socket的连接,当有客户端连接时,以客户端发过来的用户名和键、以客户端所连接的Socket对象为值存入到其中的HashMap中,作为会话保存;然后负责解析客户端的请求并实现相应的功能。
图 10 登录界面 注:运行登录界面,输入用户名以及密码,点击“登录”。检测到该用户不存在,单击“点击注册”进行用户注册。
图 11 用户注册页面
图 12 注册完成,返回登录界面,这次可以登录,说明登录注册功能测试通过!
聊天功能主要有添加好友、发送字符、文件、图片以及语音通话,现一一进行测试
图 13 主界面
添加方界面
当被添加用户不存在时,提示用户“user not exit”,如下
图 14 添加用户
被添加方界面
图 15 被添加用户
图 16 添加成功后,双方互为好友,添加好友功能测试通过!
图 17 文字聊天功能 测试通过!
图 18 发送(接收)接收文件功能测试
接收方(发送方)在收到(发送)文件或者图片时,可以选择接收(发送)或者不接受(发送),中途可以暂停接收(发送)
图 19 暂停文件/图片发送(接收) 测试通过!
Zhongger点击,接收按钮双方建立语音服务,开启语音通话,点击挂断按钮后双方结束通话,测试通过。
图 20 语音功能测试
以上是此项目的所有功能展示,可惜代码的耦合度太高了,不好维护,不过至少,这个项目获得了我们计算机网络课程设计的最高得分!





















