Chat Room:基于JAVA Socket的聊天室设计

d0304 更新功能实现
d0312 更新部分图片&UI设计部分
d0318 更新功能实现
d1222 实现添加好友功能、实现注册功能、修改大量BUG
github:https://github.com/He11oLiu/ChatRoom.git

==========================================================

这里写图片描述

即时通讯(Instant messaging,简称IM)是一个终端服务,允许两人或多人使用网路即时的传递文字讯息、档案、语音与视频交流。如QQ,微信都属于即时通讯。

本篇博客将详细记录我在JAVA上搭建一个自己的即时通讯工具的实现方法,如有错误的地方或者建议,请多多提出。

功能实现

基本技术

  • Socket 与 ServerSocket

    作为一个即时通讯工具,客户端(Client)和服务器(Server)是两个必不可少的部分。首先我们就来解决服务器和客户端连接的问题。

    Socket的英文原义是“孔”或“插座”。
    Socket用于描述IP地址和端口,是一个通信链的句柄,可以用来实现不同虚拟机或不同计算机之间的通信。
    在Internet上的主机一般运行了多个服务软件,同时提供几种服务。每种服务都打开一个Socket,并绑定到一个端口上,不同的端口对应于不同的服务。

    网络上的两个程序通过一个双向的通信连接实现数据的交换,这个连接的一端称为一个Socket。建立了一个Socket后,从这个Socket中读取I/O流,就可以实现两个程序的通讯。
    而服务器需要与多个客户端进行双向的通讯连接,并非单一的连接。于是可以通过每接收到一个Client后就开一个单独的进程,来完成与该Client的通讯步骤。这样就可以同时接受多个Client,从而实现服务器的功能。
    这里写图片描述

  • 客户端建立:
    Java提供了Socket类,用来建立客户端
    提供的构造方法如下:
    这里写图片描述
    当调用构造方法,构造一个新的Socket的时候,就是向指定的端口请求连接。
    通过以下方法,可以获取该Socket的I/O流
    这里写图片描述
    这里写图片描述
    通过close方法,关闭该Socket
    这里写图片描述

  • 服务器建立:

    Java提供了ServerSocket类,用来建立服务器。
    提供的构造方法如下:
    这里写图片描述
    可以通过Telnet来测试是否成功的建立了服务器。(win7后默认关闭,在控制面板-程序-启用或关闭windows功能下开启)

    telnet localhost <port>

    或者

    telnet 127.0.0.1 <port>

    该类下自带了
    这里写图片描述
    accept方法用来接收连接,返回一个Socket对象。接收该对象,并调用刚才提到的getInputStream()方法以及getOutputStream()方法,即可实现获取该Socket的I/O流。从该流上读取信息或者写入信息,即可达到通讯的目的。要注意的是,此时客户端的Input流对应着服务器端的Output流。
    同样ServerSocket也带了close方法,用来关闭服务器。
    这里写图片描述

  • 通讯协议设计
    这里写图片描述

通讯协议具体内容

type描述
1.注册请求信息0x01客户发送注册请求数据给服务器MsgReg
2.注册应答消息0x11客户端返回注册结果MsgRegResp
3.登陆请求信息0x02客户端发送包括JK号及密码MsgLogin
4.登录应答消息0x22服务器返回登录结果MsgLoginResp
5.添加好友信息0x05客户端向服务器发送添加好友JK号和添加好友所在列表MsgAddFriend
6.添加好友应答信息0x55服务器返回添加好友结果MsgAddFriendResp
7.好友列表0x03服务器给用户发送好友列表信息MsgTeamList
8.聊天信息0x04客户端给服务器/服务器给客户端消息MsgChatText

消息头(MsgHead)

MsgHead消息头(13)消息体
int totalLen消息总长度(4)
byte type消息类型(1)
int desk目标JK号(4)服务器的JK号:2000000000
int src发送用户JK号(4)登录发送JK号:2000000001

注册消息体(MsgReg extends MsgHead)

成员属性类型长度(总长33)
String nikeNameOctet String10昵称
String PwdOctet String10密码

注册应答消息(MsgRegResp)

成员属性类型长度(总长14)
byte statebyte1若state为0.desk为目标JK号;若state为1(或其它)错误

登录请求信息(MsgLogin)

成员属性类型长度(总长23)
String pwdOctet String10密码JK号保存到src中

登录应答信息(MsgLoginResp)

成员属性类型长度(14)
byte statebyte1若为0:登录成功;若为1:JK/pwd错误;若为2:ip错误;其它:未知错误

添加好友信息 (MsgAddFriend)

成员属性类型长度(27)
int add_IDint4所添加好友的JK号
String list_nameOctet String10添加好友至好友列表的名称

添加好友应答信息 (MsgAddFriendResp)

成员属性类型长度(14)
byte statebyte1若为0:添加好友成功;若为1:不存在该用户;若为2:已经存在该好友;若为3:创建好友列表失败

好友列表信息(MsgTeamList)

成员属性类型长度(总长:利用计算长度)
String UserNameOctet String10用户名字
int UserPicint4用户头像
byte listCountbyte1好友分组个数,表示有几组
String listNameOctet String10分组名称
byte bodyCountbyte1本组有多少个用户
int bodyPicint4好友头像
int bodyNumint4好友的JK号
String nikeNameOctet String10好友
byte bodyStatebyte1好友状态,0:在线

聊天信息(MsgChatText)

成员属性类型长度
String msgTextOctet String13可聊天的内容

发送一次信息的总流程

这里写图片描述

框架设计

界面层

ChatRoom的主要界面有:

  • 登陆界面
  • 注册界面
  • 好友列表界面
  • 添加好友界面
  • 聊天界面

业务逻辑层

  • 服务器业务逻辑层
    服务器主要提供以下功能
    服务器主线程:
功能创建服务器
描述创建服务器。
动作根据指定的port创建服务器。
输入port(端口)

服务器对应每一个连接的单独线程ServerThread
这些ServerThread利用HashMap保存到线程库,key为JK号

功能接受Client
描述接受客户端,创建单独线程,并通过输入输出流通信。
动作循环监听是否有client接入,若有,则创建单独线程对其进行操作。
相关服务器服务线程,ServerThread


功能响应登陆请求&发送好友列表
描述响应客户端发送的登陆请求。
输入根据输入流中读取的数据以及通讯协议,读取userid和pwd。
行动利用数据库查询输入的userid和pwd是否匹配
输出根据通讯协议,向输出流输出回执信息,包含最终匹配结果。
行动若登陆信息匹配,更新用户在线表。
输出若登陆信息匹配,则再根据通讯协议,向输出流发送好友列表。
相关数据库,通讯协议


功能一对一聊天
描述响应客户端的聊天请求,并将聊天信息发到指定客户端。
输入根据输入流中读取的数据以及通讯协议,获取目标userid以及发送内容。
行动根据目标userid以及发送内容,给对应客户端发送信息。
输出通过寻找对应客户端所接入的thread,给对应客户端发送内容。
相关通讯协议,serverthread


功能注册用户
描述响应客户端的注册请求,并利用数据访问层提供接口写入数据库
输入根据输入流中读取的数据以及通讯协议,获取目注册用户名以及登陆密码
行动利用数据访问层提供接口写入数据库
输出若注册成功,返回客户端住车好的JK号
相关通讯协议,serverthread


功能添加好友
描述响应客户端的添加好友,利用数据访问层提供接口。
输入客户端发送的添加好友的JK号已经好友列表名称
行动利用数据访问层提供的接口判断输入的正确性,若正确在数据库中好友列表添加内容
输出添加好友的状态。
相关通讯协议,serverthread


  • 客户端业务逻辑层
功能连接服务器
描述客户端连接服务器,确认服务器可以连接。
输入读取类中的ServerIP和port。
动作根据ServerIP和port,开启Socket,获取输入输出流。
输出能否连接到服务器


功能注册
描述输入用户名和密码
输入NikeName和Password
动作将注册请求,打包成消息,发送给服务器。
输出根据服务器返回结果显示JK号或者显示注册失败。


功能登陆服务器
描述客户端申请登陆,确认用户密码是否正确。
输入用户输入的userid和password
动作将userid和password根据通讯协议传输到服务器,并接收服务器回信。
输出用户名密码是否正确
相关通讯协议


功能获取好友列表
描述完成登陆后,从服务器获取好友列表,并显示出好友界面
动作从服务器获取好友列表,并通过给好友列表界面对象。
相关通讯协议,好友列表界面对象


功能一对一聊天
描述在好友列表中点击一个好友,开始一对一聊天,可以发送/接收信息
动作根据通讯协议,向指定用户发送信息
输入发送的目标userid以及发送内容(聊天界面传入)
动作根据通讯协议,读取信息来源用户,通过聊天界面显示内容。
输出聊天内容传给聊天界面对象
相关通讯协议,聊天界面对象


功能添加好友
描述添加好友到列表
输入好友的JK号和列表名
动作将请求打包成消息,发送给服务器,等待服务器返回结果
输出根据结果弹出结果消息框,更新好友列表。

数据访问层

本部分由黄成越同学合作完成(https://hcyue.me/)

本次数据库选用SQLite数据库,数据库结构如下。

这里写图片描述

数据访问层提供UserModel,其中包括

  • 根据JK号获取用户内容
  • 验证用户信息
  • 获取用户好友列表
  • 增加好友,删除好友

UI设计

  • Metro UI

    Metro UI是基于瑞士平面设计原则,其最初在Windows XP的Windows Media Center就中有体现,这有利于以文字为主的界面导航。2006年著名的Zune播放器开始使用类似Metro的设计风格。微软的设计师计划重新设计现有用户界面、更清爽的排版和较少的重点以便于用户使用。Zune的桌面客户端程序也使用了不同于以往Portable Media Center用户界面,其清爽排版和设计给用户耳目一新的冲击。
    Metro UI的特点:强调信息本身
    参考资料:http://www.csdn.net/article/2012-02-01/310896/1

    这里写图片描述

  • 窗体设置无边框以及拖动选项
    为了彻底改变Swing的风格,实现Metro UI的界面,将原有的边框以及按钮重写是必不可少的。
    首先,我们将窗体的边框取消

    setUndecorated(true); //设置无边框

    这行代码的作用,就是去掉了整个窗体的边框,但是同时也就去掉关闭按钮,缩小放大按钮。同时无法移动边框。为了能够移动边框,我们在窗体上添加一个监听器,代码如下。

    addMouseListener(new MouseAdapter() {
        public void mousePressed(MouseEvent e) {
            isDraging = true;
            wx = e.getX();
            wy = e.getY();
        }
    
        public void mouseReleased(MouseEvent e) {
            isDraging = false;
        }
    }); //确定鼠标按下的位置
    
    addMouseMotionListener(new MouseMotionAdapter() {
        public void mouseDragged(MouseEvent e) {
            if (isDraging) {
                int left = getLocation().x;
                int top = getLocation().y;
                setLocation(left + e.getX() - wx, top + e.getY() - wy);
            }
        }//更改窗体的位置
    });
  • 关闭、缩小、选择按钮的重写

    因为选择了无边框,则原来的关闭以及缩小按钮都已经不能使用了,于是可以继承JButton,自己重写一个关闭,缩小按钮。同时为了配合Metro UI,选择按钮也需要重写注意实现鼠标移进、移出、按下的颜色改变(可以添加其他效果)。
    各个组件实现代码见GitHub。

    这里写图片描述

  • 好友列表的实现

    这里写图片描述
    要实现好友列表,第一个想到的就是树状结构。但是要实现相对漂亮的UI,利用树状结果不太好实现。

    经过多次尝试,最终确定用JScrollPane来完成这个可能需要拖动的好友列表。但是利用JScrollPane后,不宜布局,若利用JPanel来制作好友,其大小不好固定。最终重写滚动条,具体实现见代码。


版权声明:本文为He11o_Liu原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。