您的当前位置:首页正文

即时通讯软件的设计与实现

2024-08-17 来源:榕意旅游网
即时通讯软件的设计与实现

1 系统实现模块 1.1 服务端模块

服务端主要包括三个模块:

1.网络模块,建立TCP服务器,负责监听端口,与客户端建立连接并接受和发送数据。

2.应用模块,负责处理从网络模块接收到的数据,予以分析处理,进行转发或对数据库进行操作,并返回相关信息。

3.数据层,数据层用来与数据库建立连接,应用模块必须通过数据层来进行数据库的操作。

服务端 数据层 逻辑模块 网 络 模 块 日志记录

图4.1 系统模块图

1.2 客户端模块

用户端包块以下模块:

1.用户界面模块,此模块包括客户端的操作界面,由NetBeans编写的GUI,进行了较多的美化,采用偏向Vista的风格。

2.网络模块,此模块包括两个小模块,TCP模块用于和服务器端通讯,而UDP模块则负责和客户端进行P2P通讯。

3.聊天模块,此模块负责在用户聊天时候,对聊天的数据进行封装,对聊天的图片也进行压缩,以适用网络传输。在接受到聊天数据之后,又会对聊天数据进行分解操作,最后生成聊天内容。

4.群组模块,此模块包括群组聊天、群组的创建、管理、更新等功能。

5.文件传输模块,此模块用于实现客户端之间的文件传输功能。 6.视频聊天模块,此模块用于实现客户端之间的视频聊天功能。 7.系统设臵记录模块,此模块用于实现保存聊天记录,登录日志,以及保存系统设臵信息的功能。

客户端 系统记录模块 视频聊天模块 文件传输模块 群组模块 聊天模块 网络模块 用户界面模块 图4.2 客户端模块图

2 数据库设计 2.1用户表设计

用户表是系统中的基础表,主要用来记录用户注册时的各种信

[12]

息,此表还有一个从表userlogin表用来记录用户登录和状态信息。

Userinfo表 字段名 ID Userid Name Sex age City mail address Telephone face UserLogin表 字段名 Userid Pass Fettle Ipaddress 字段类型 Int Varchar Int Varchar 字段长度 11 20 2 20 备注 用户ID 用户密码 用户状态 IP地址 字段类型 Int Int Varchar Varchar Int Varchar Varchar Varchar Varchar int 字段长度 11 11 20 2 4 50 50 60 15 2 备注 主键 用户ID 用户名 性别 年龄 城市 信箱 地址 电话 头像号 2.2 用户关系表

用户关系表是用来保存用户之间的好友关系的数据表,查询好友列表的时候要从此表中查询与自己ID想关联的好友ID,type字段可以设臵两者的关系,其中1为好友关系,0为黑名单。 字段名 Id Userid Frendid Type 字段类型 int Int Int Int 字段长度 11 11 11 4 备注 主键 用户ID号 好友ID号 关系类型 其他表,如组群表,族群信息表就不在此一一列出。 3 系统模块的详细设计

此章节将叙述系统部分模块的设计方法和具体实现。 3.1 网络模块的设计实现

本系统采用TCP和UDP混合的网络通讯,客户端与服务器之间登录验证时采用TCP连接,登录之后,客户端与服务器采用UDP方式保持通讯,客户端和客户端之间采用UDP连接,如果无法直接连接,通过服务器申请UDP穿透NAT,如果失败,则消息通过服务器中转传递。

因为要在网络通讯中直接传递对象,故采用基于Java NIO的I/O的Cindy异步框架,Cindy是一个Java异步I/O框架,提供了一个统一高效的模型,同时支持TCP、UDP以及Pipe,并能够方便的在异步和同步操作之间进行切换。目前其实现是基于Java NIO,并计划通

过JNI来支持各操作系统上本身提供的异步I/O功能,应用可以方便的通过运行期属性来方便的切换到更为高效的实现上。[13]

Java NIO包虽然提供了非阻塞I/O模型,但是直接使用NIO的非阻塞I/O需要成熟的网络编程经验,处理众多底层的网络异常,以及维护连接状态,判断连接超时等等。对于关注于其业务逻辑的应用而言,这些复杂性都是不必要的。不同Java版本的NIO实现也会有一些Bug,Cindy会巧妙的绕开这些已知的Bug并完成相应功能。并且NIO本身也在不断发展中,Java 1.4的NIO包中只实现了TCP/UDP单播/Pipe,Java 5.0中引入的SSLEngine类使得基于非阻塞的流协议(TCP/Pipe)支持SSL/TLS成为可能。使用Cindy,应用可以在同步和异步之间进行无缝切换,对于大部分操作是异步,可某些特殊操作需要同步的应用而言,这极大的提高了易用性。[14]

1.服务器端编码: SessionAcceptor acceptor ;

acceptor=SessionFactory.createSessionAcceptor(SessionType.TCP); acceptor.setListenPort(port); //设臵监听端口 port为端口号 acceptor.setAcceptorHandler(new SessionAcceptorHandlerAdapter()); //设臵SessionAcceptorHandler;

SessionAcceptorHandler接口是处理SessionAcceptor产生的各种事件, SessionHandler接口则用于处理Session产生的各种事件。 acceptor.start(); //开始执行,服务器开始监听TCP连接。

SessionAcceptorHandlerAdapter类是处理acceptor时间的接口,里面

包括了各种方法,我们主要使用到对象接收事件(objectReceived),我们接受到对象以后再对其进行处理。代码如下: new SessionAcceptorHandlerAdapter(){

public void sessionAccepted(SessionAcceptor acceptor, Session session) throws Exception {

session.setPacketEncoder(new SerialEncoder()); session.setPacketDecoder(new ServerPacketDecoder()); session.setSessionHandler(new SessionHandlerAdapter(){ public void objectReceived(Session session, Object obj){ /****这里可以得到接受到的对象,我们封装成Message类****/ Message message=(Message)obj;

} }

2.服务端编码

首先先生成一个TCP的Session,然后为session设臵远程IP地址,最后通过设臵SessionHandlerAdapter()来处理事件。 Session session=SessionFactory.createSession(SessionType.TCP); session.setRemoteAddress(new InetSocketAddress(address,port)); session.setPacketEncoder(new SerialEncoder()); session.setPacketDecoder(new SerialDecoder()); session.setSessionTimeout(0);

session.setSessionHandler(new SessionHandlerAdapter());

3.2 用户登录的具体实现

前面介绍了Cindy的网络框架,现在我们来演示用户登录时候的流程。用户登录请求处理模块是用户通过了验证(验证过程在信息查询服务器上完成),取得了合法登录密钥之后,登录文字通讯服务器的第一步。

用户登录请求处理模块的处理过程是:接收用户登录请求,首先判断其登录密码是否正确(登录密码是用户注册时候输入的口令,服务器收到该请求后,首先查询数据库中此用户ID的密码,然后判断和用户发送的登录密码是否一致)。如密钥不正确则关闭该连接,并发送密码错误的信息。如果密钥正确,则修改该用户在数据库中的IP记录,并从数据库中读取出其所有的好友列表,并将此用户上线的消息发送给其好友,服务器同时将此用户此时此刻登录的情况记录在日志中。

客户端收到密码验证正确的消息之后,会受到服务器发送来的好友列表和详细信息,并通过这个好友列表构建好友信息,完成登录。 在客户端中,我们提供了隐身上线,和保存密码两个选择项,其内容保存在Hitayo.ini文件中。在登录窗口中,可以手动输入服务器的IP地址,并将新的服务器地址保存进服务器选择列表,下次登录时,可以自动选择到上次登录的服务器地址,同时还具备检测服务器是否畅通的功能。

登录窗口界面如下:

图4.3 客户端登录界面

3.3 聊天模块的具体实现

一款即时通讯软件,最基本的功能莫过于即时聊天功能。在本系统中,对聊天信息重新封装(ChatSeria类),使系统的聊天功能支持对文字的字体、大小、粗细、颜色进行设臵,还能够在文字中插入图片,并能支持快捷键,用Ctrl + Enter可以发送消息。

图4.4 聊天窗口界面

实现功能的关键在于对聊天信息封装和对文字中图片的定位和还原,以下代码实现了图文混合后对聊天信息的还原:

for(int x=0;x<5;x++){

end=chat.getMessage().indexOf('?'); if(end==0){ //获得光标位臵

jp.setCaretPosition(jp.getStyledDocument().getLength()); jp.insertIcon(new ImageIcon(ImageIO.read(new

ByteArrayInputStream(chat.getImage()[x]))));//把图片插入光标位臵 chat.setMessage(chat.getMessage().substring(++end));

}else{jp.getStyledDocument().insertString(jp.getStyledDocument().getLength(), chat.getMessage().substring(begin, end), null);//插入文字 chat.setMessage(chat.getMessage().substring(end)); x--;}

if(chat.getMessage().indexOf('?')==-1){

jp.getStyledDocument().insertString(jp.getStyledDocument().getLength() //插入文字

chat.getMessage().substring(--end), null);

break; } }

其原理是通过重写JTextPane的replaceSelection方法,改变了图

标的位臵标记,然后分别保存文本和图片以及字体属性,发送给接受端,接受端接受到信息以后,首先用以上算法,找到图片的标记,再把光标移动到那个位臵,把图片组中的图片插入其中。最多可以插入5张图片,但是经过测试如果图片过大,可能导致发送失败。 3.4 视频模块的具体实现

视频聊天是即时聊天软件发展的一个新的高度,因为IM支持视频聊天是可以必不可少的功能。JAVA实现对摄像头的控制需要通过JMF多媒体框架。

JMF实际上是Java的一个类包。JMF 2.1.1技术提供了先进的媒体处理能力,从而扩展了Java平台的功能。这些功能包括:媒体捕获、压缩、流转、回放,以及对各种主要媒体形式和编码的支 持,如M-JPEG、H.263、MP3、RTP/RTSP (实时传送协议和实时流转协议)、Macromedias Flash、IBM的HotMedia和Beatniks的Rich Media Format (RMF)等。JMF 2.1.1还支持广受欢迎的媒体类型,如Quicktime、Microsofts AVI和MPEG-1等。[15]

本模块的设计思路是,通过JMF获得视频的数据流,然后将数据流还原成每一帧的图像,再将图像压缩后转换成字节流采用UDP包发送给接收方,接收方接受到信息后,从字节流中生成图像,再将图像绘制在JPanel上,我们重写JPanel的paintComponent方法,并添加一个方法(UpVoide(Image img))不断刷新JPanel上的图片,这些都在独立的一个线程上操作,这样视频和聊天可以同时进行,经过测试,在网络较通常的情况下能获得比较好的效果。

图4.5 视频聊天窗口界面

JMF获得视频图像的实现代码:

String str = \"vfw:Microsoft WDM Image Capture (Win32):0\"; CaptureDeviceInfo di = CaptureDeviceManager.getDevice(str); MediaLocator ml = di.getLocator();

dataSource = JMFUtils.createCaptureDataSource(null, null, str,di.getFormats()[2]);

Player player=Manager.createRealizedPlayer(dataSource); player.start();

//获得Comopnent可以直接用来播放视频

comp = player.getVisualComponent();FrameGrabbingControl fgc = (FrameGrabbingControl) player.getControl(

\"javax.media.control.FrameGrabbingControl\"); Buffer buf=fgc.grabFrame();

BufferToImage bufferToImage =new BufferToImage ((VideoFormat)buf.getFormat());

Image img = bufferToImage.createImage(buf);

Img为获得的视频图片,将此帧压缩成JPG后封装给接收方,接受方收到数据以后显示视频的代码如下:

protected void paintComponent(Graphics g) { super.paintComponent(g); if(img!=null){

g.drawImage(img, 0, 0, this.getWidth(), this.getHeight(), this); } }

public void UpVoide(Image img){ this.img=img; this.repaint(); }

一个新的线程将每次接受到的Image对象作为参数调用UpVoide方法,则能快速刷新JPanel上的图像,达到视频播放的效果。 3.5 P2P穿透NAT的实现步骤

NAT的穿透技术在P2P的的发展起到了至关重要的作用,可以说如果没有穿透NAT的方法,P2P绝不会成为一项如此热门的领域,

因为P2P穿透NAT使大量的内网用户也能够享受到P2P带来的效率和速度了。

假设Client A和Client B,分别两个内网中的PC,其中Client A的IP地址是192.168.0.20,其网关是202.187.45.3,Client B的IP地址是192.168.0.10,其网关是187.34.1.56,服务器的IP地址是219.237.60.1,那么P2P具体的实现步骤如下:

1. Client A登录服务器,NAT A(Client A的网关) 这次的Session分配了一个端口60000,那么Server S收到的Client A的地址是202.187.45.3:60000,这就是Client A的外网地址了。

2. Client B登录Server S,NAT B给此次Session分配的端口是40000,那么Server S收到的B的地址是187.34.1.56:40000

图4.6 UDP穿透示意图

3. Client A通过Server S得到B的IP地址是187.34.1.56:40000,

然后Client A向此IP发送信息,NAT B抛弃此条信息。

4. Client A通知Server S,申请UDP穿透。

5. Server S 通知Client B,并将Client A的IP地址和端口告诉Client B;

6. Client B收到信息以后,像Client A也发送一条消息,便会在NAT B上留下一个Session。

7. Client A 再向Client B发送信息时候,NAT B会将消息转发给Client B。

8.完成UDP穿透,Client A和Client B可以互相通讯。 3.6 文件传输模块的设计

文件传输是即时聊天软件的主要功能之一,而文件的特殊性也决定了对于通过网络通信实现文件的传输,可靠性与完整性的要求都很高。根据需求分析在本模块儿的设计中,我们未采纳UDP的传输方式。但是,目前处于两个原因的考虑:1)IP地址短缺;2)网络安全——防止黑客软件和病毒的泛滥。大部分上网的个人计算机都被“隐藏”于防火墙或者NAT之后。这就在很大程度上阻挡了面向连接的对等端实现TCP互联。因此,在此模块中,为适应各种情况,保证文件的成功传输,我们采取了P2P和C/S模式并存,互为补充的模式,以P2P方式为主。在进行文件传输时,首先采取P2P方式进行互联。如果由于任何原因点对点方式不能成功,系统或自动采取点对服务器方式,从而确保文件传输的成功,模块设计结构如图

图4.7 P2P连接示意图

系统采用TCP的方式发送,通过CINDY的框架下,对文件进行分包发送的代码如下:

发送方(部分)

for (int i = 1; i <= QUEUE_SIZE; i++) {

Buffer buffer = BufferFactory.allocate(MESSAGE_SIZE); buffer.position(2);

int readCount = buffer.read(fc);

// readCount为-1则文件已经读取完毕,发送完成关闭 if (readCount == -1) {

buffer.release();future.getSession().close(); }else{

buffer.putUnsignedShort(0, readCount).flip(); //开始发送

session.send(buffer); }

接收方(部分) Buffer buffer = (Buffer) obj;

if (fc == null) {

fileName = buffer.getString(Charset.UTF8, buffer.remaining()); fc = new RandomAccessFile(fileName, \"rw\").getChannel(); } else {

while (buffer.hasRemaining())

buffer.write(fc); //写入文件

}

3.7 组群功能的具体实现

组群功能是即时聊天软件的一大进步,取代了曾经的基于WEB的聊天室。本系统的组群功能支持,组群的建立,加入组群,管理组群,组群公告,组群聊天等功能,每一个用户登录以后,都可以在组群栏中创建自己的组群,并添加用户到组群中来。

创建组群的用户将成为组群的管理员,能够管理组群用户和修改组群的资料信息等。

组群聊天模式采用TCP连接服务器转发,之所以采用这种模式是考虑到所以用户已经和服务器连接起来了,通过TCP转发能够降低程序的复杂性,不过从实际考虑,如果要投入实际运行,应该将TCP的组群聊天室改为UDP形式。

组群聊天同样支持文字的字体、颜色、大小、粗细进行设定,同时也支持图文并茂。 3.8 聊天记录模块的实现

本系统支持聊天记录的保存和查阅,在这里采用的是文件保存的

形式,将聊天记录保存为文本文件,位臵在程序目录中的用户ID目录里,以好友的ID命名。选择以文本文件作为聊天记录的载体是因为若使用JAVA DB数据库来储存聊天记录,会降低程序运行速度,弊大于利。

if(new File(Dir).exists()); //如果目录不存在则建立目录 new File(Dir).mkdir(); if(chatnote.exists())

//如果文件不存在则建立文件

chatnote.createNewFile();

FileOutputStream out=new FileOutputStream(chatnote,true); / StringBuffer sb=new StringBuffer(); //将用户名、时间和聊天记录加入StringBuffer

sb.append(friend.getName()+\"\"+getDate()+\"\\r\\n\"+chat+\"\\r\\n\"); //先转换成“UTF-8”后写入文件 out.write(sb.toString().getBytes(\"utf-8\")); out.close();

若要查看聊天记录,可以直接到软件目录进入用户ID的文件夹直接查阅记录,或者在软件中进行查看记录,聊天记录查看界面如下:

图4.8 聊天记录查看界面

由于系统比较庞大,不能讲解所有的具体实现步骤,所以只选择部分比较关键的模块进行叙述。 4 小结

本文主要介绍了本人综合运用Swing、Cindy网络框架、JMF多媒体框架、MySQL数据库开发设计了一个P2P的即时聊天软件。软件中的部分模块的设计思想和实现部分的关键代码。

因篇幅问题不能全部显示,请点此查看更多更全内容