站内搜索: 请输入搜索关键词

当前页面: 开发资料首页Java 专题jICQ 服务器和客户端源码

jICQ 服务器和客户端源码

摘要: jICQ 服务器和客户端源码

</td> </tr> <tr> <td height="35" valign="top" class="ArticleTeitle"> <table width="100%" border="0" cellspacing="0" cellpadding="0"> <tr> <td width="287" height="86" align="center" valign="top"> </td> <td width="397" valign="top">

jICQ系统是作者自2001年4月份开始开发的一个小型软件,最初旨在提高作者自己的java应用水平,在经过一段时间完善之后,便不由自主的想使这个小小的应用更加完美,所以现在仍在不断的升级,希望有更多的java爱好者能加入这个开发行列中来,也希望熟悉java的人能更多关心java应用的发展,愿更多的java爱好者参与一起完善这个程序.

一.概述
二.系统原理
三.中文处理

一.概述:
系统是基于ICQ和OICQ系统的基本特性编制面出.
系统在事件处理上采用java2的组件监听机制.
系统图形用户界面全面采用AWT组件,未采用SWING,因为SWING的JRE太大.软件兼容jdk1.1.8及以上,兼容win98SE自带ms java平台.

</td> </tr> </table>



界面全部使用手工编制.
由于jdk1.1.8不支持图象Label,所以jICQ主画面全部由paint(){}方法完成,面事件由窗体的自定义鼠标事件驱动.

使用java.awt.image.*;图像技术,加亮图片,使郊果别据一色,并且解决了,MS的java平台图像处理系统的缺点(即图象常常只显示一部分,另外部分显示不出,用repaint()方法重绘也不能解决).

客户端与服务端采用TCP/IP连接,传输稳定,客户端与客户端采用UDP传输,并施行UDP控制,能发现丢包,并能提交服务器转发,能发现UDP重复包并实现控制.

实现广告链接(体现java网络特性).
系统参数可保存.
实现中文传输.
服务器采用java Application.
与服务器的所有通信都由Class Sender完成,Sender做完一件事后返回一个Result对象(包括一个返回码(int)及一条返回信息(String)).

类列表:
public class jICQ //系统主类含main()方法
class MainFrame extends Frame implements Runnable //被jICQ.main() 初始化并启动
class RMsg extends Thread //被MainFrame.run()初始化并启动
class SMsg extends Frame //被MainFrame.run()初始化
class LogonFrame extends Frame //被MainFrame.run()初始化
class Result //服务于Sender
class IcqID extends Thread //公用类,为每个好友产生一个实例,当IcqID.newMsg==true时自启动,能处理收到和发送信息.
class Sender extends Thread //公用类,专门与服务器进行通信,接受String型命令,以线程序方式运行,执行结束返回一个Result对象
class SubmitID extends Frame //可用来注册,修改和查看用户信息.
class Seek extends Frame //负责查找好友.被MainFrame的右键菜单调用.
class MyDialog extends Frame //产生一个对话框
class SysInfo extends Frame //为MainFrame提供用户设置变量,读取和保存jICQ.ini文件.
class HttpImage extends Thread //在MainFrame被构造时调用并启动,负责为SMsg提供广告图片getImg()和超级链接getHttp()



二.系统原理
1.主要类及主要方法,重要变量:
系统启动后调用 jICQ 中的main()方法,出更登录框,系统后台查找主机地址(服务器有多个可能域名).等待正确登录,如果号码密码验证通过,则初始化jICQ主界面,并启动主界面的后台线程.jICQ.class的使命完成,接后的工作由MainFrame完成.

MainFrame 即为主界面,继承Frame并实现线程,初始化时为整个系统初始化变量,并把所有头像图片装入headImg[]中,并处理头像图片数据变白后放入alphaHead[]数组中.

----其run()方法完成登录服务器的全过程,并启动多个其它线程([1]Sender.onlineMon()方法主要是TCP/IP连接服务器一直到程序结束,[2]RMsg.class类主要是监听UDP端口,接收来自其它用户的聊天内容),当登录完成后,成为调度线程直到结束,执行比如有聊天信息收到使主窗口图标闪烁(调用checkID()),自动更新好友的个人信息checkID(),如果出果重绘窗口请求(repaintEvt==true),则调用repaint()重绘屏幕;监视Sender.onlineMon()是否与服务器断线,是则重连;监视服务器是否发来他人用自已这个号码在登录(duplogon==true),是则出现提示对话框,结束系统.

----其paint()方法,完成主界面的图形绘制工作.它把主窗口分为七块, 为每块区域分配不同的作用:(1)上翻页;(2)头像区(调用IcqID.draw()方法);(3)下翻页;(4)添加好友;(5)消息通知;(6)修改菜单;(7)LOGO图标,并有大小图标转换功能.
----process...系统方法为事件处理方法.

----Hashtable Friend是用来存放好友名单的表,其键为jicq号码(String icqNo), 其值为个人资料的对象(IcqID icqid);存入方法为:setFriend(String key,Object value)

RMsg 继承线程,在主窗口存在时,终生存在.为接收UDP包的类,从接收到的包里区分出发送者,检索好友中是否有此人,如果无,则添加为陌生人.
SMsg 是一个Frame.主要接收用户的输入,把输入结果传输给IcqID的addInMsg()方法.

LogonFrame 这是一个启动登录对话框的窗体(可设置代理服务器),可以完成两个工作,(1)实现登录,当用户输入jICQ号码及密码后点击登录按钮,即调用启动一个Sender线程,当Sender返回代码为Result.getCode()=200时说明登录成功,此时LogonFrame调用jICQ.class的setID()方法把登录的jICQ号传给jICQ.class(以便接下来MainFrame(jICQ.icqNo)用.)登录结束.(2)即当点击注册按钮时,new 一个SubmitID的实例.

Sender 继承线程,被调用方法为(它接受的命令以字符串为参数,根据字符串的内容确定执行什么操作,类似于WEB服务器):

Sender sender=new Sender("LOGON icqNo:"+s1+" password:"+s2);
sender.start();
Result result=sender.getReturn(); //或 Result result=sender.getReturn(int timeOut);
if(result.getCode()==200){
jICQ.setID(s1);dispose();
} else{
tf_msg.setText(result.getMsg());
}

此Sender在执行结束时返回一个Result对象.
IcqID 为每个好友创建一个实例,所有的实例都被放在Hashtable MainFrame.Friend;中.每个实例包括好友的个人信息及运行状态,并有一些辅助功能,比如:(1)IcqID.draw()被MainFrame.paint()方法调用绘出自已在MainFrame窗体中的头像,(2)能够处理收到addInMsg()的信息和用发出addOutMsg()信息,先用UDP发送udpSend(),确定UDP包是否到达(run()中完成),如果在超时设置内没有接收到回应,即认为是未到达,采用服务器转发.(3)IcqID.getImg()为其它类提供自已的头像.(4)自动实现新信息到达时头像闪动(在run()方法中实现).(5)mouseEnter()和mouseExit()被MainFrame.processMouseMotionEvent()调用,实现鼠标移动时头像变白的效果.

SubmitID 主要是好友的个人信息显示提供一个界面,同时兼具注册及修改个人资料的用途.
Seek 主要完成好友的查找及添加工作,当添加时向MainFrame.friend哈希表中添加一个IcqID实例就可以了.

2.运行状态:
整个个程序在运行期间有四个线程终生存在:
MainFrame.run() //系统调度
RMsg.run() //UDP监听,并分析UDP包,确定来自哪位好友,并调用Friend中的该好友的IcqID.addInMsg()添加信息.
Sender.run() //调用onlineMon()主动与服务器连接后保持连接并接收来自服务器的信息:

(1)转发的好友信息,
(2)别人要添加自己为好友,
(3)系统消息,
(4)好友上线下线.
SysInfo.run() //当SMsg运行时,每间隔8秒钟为SMsg.ImagePanel提供广告图片和URL.
系统中IcqID也有run()方法,但是它的实例线程不是一直在运行,当有信息发出或收到时才会触发,当读了信息后,线程就结束了.

3.特定事件的处理流程:
(1)登录:
jICQ.main()LogonFrame.login()验证如果通过(jICQ此时处理哪个jICQ服务器正开通着)修改jICQ.icqNojICQ初始化MainFrame启动MainFrame线程jICQ结束,控制权转到MainFrame.run()
MainFrame.run()启动确定此机器的UDP接收端口(因此可实现多个jICQ同一台机运行)启动Sender.onlineMon()线程监听来自服务器的信息让Sender执行"GETME"命令,同时提交自已的UDP端口号,取得自己的个人资料,把好友名单用toHash()放入Friend中.把在线名单用toID()修改(IcqID)Friend.getFriend(icqNo).isOnline=true登录结束,run()开始无限循环,直到系统结束.

(2)收到UDP信息:
当收到UDP信息时udpRSocket.receive(dp)
如果是好友聊天信息("MSG")分析发送方的号码从MainFrame.Friend中查找是否是好友,不是则加为陌生人取得Friend表中的此人的IcqID,调用IcqID.addInMsg()在addInMsg()中首先判别是否是第二次收到重复信息(实际应用中可能收到两次同一条消息),如果重复则不做任何处理.是新消息设IcqID.newMsg=true,给MainFrame.repaintEvt=true,即让主窗口重绘.向发送者回复一条"UDPOK"的消息如果用户允许自动回复(MainFrame.sysinfo.autoRe==true),刚发送自动回复内容(MainFrame.sysinfo.autoReText)--完成.
如果是确认信息("UDPOK")取出MainFrame.friend表中发送方的IcqID调用IcqID.backID()去掉Vector IcqID.outMsg中的已发出内容这条数据(否则IcqID.run()中在检测到某条信息在超时后仍未收到确认信息,则会自动通过服务器转发)

(3)发送一条聊天信息:
MainFrame.processMouseEvent()或MainFrame.processKeyEvent()接收到一条发信息的指令后,构造一个SMsg()窗口,等待用户输入聊天内容接到发送指令调用好友的IcqID.sendOut()方法在IcqID.sendOut()中构造一条发送命令,并截去太长的字符串,为这个信息加上一条ID(以便接收方面军区别是否重复收到同一信息)调用addOutMsg(),向Vector IcqID.outMsg添加发出的消息,以便run()方法检查确定是否重发启动自已的run()方法结束

4.图形用户界面:
最先的jICQ是使用的jdk1.0的事件处理方法handleEvent(),后来从jICQ1.2开始便改为事件监听机制,从而使设计更易懂.
除MainFrame之外,其它的窗体都使用java.awt.*;中的组件,使用java.awt.bagGridLayer布局管理器,手工设计,便于修改.
MainFrame窗体中未使用任何组件,因为在处理大量的好友时,组件就觉得无所适从,所以全部用Graphics.draw...()方法产生,当然在不用组件时,也产生了大量的问题, 比如说, 在中文Linux系统中,就出现组件中的中文显示正常,面drawString()绘出的中文就显示为方格,很不方便.而绘出的图形再分为几个区,由processMouseEvent()处理鼠标事件.

二.中文处理
在传输入过程中,使用早期的DataInputStream.readLine()和PrintStream.println()方法,不能传输中文,但传输英文数字没问题.但是采用手工转换Char为Byte[]时,就可以解决这个问题.用下面的两个方法可以实现中文传输,但收发双方要相应使用这两个方法.
void sendString(PrintStream ps,String s) {
ps.println(s.length());
int len=s.length()*2;
byte buf[]=new byte[len];
try{
for(int i=0;i>8);buf[i*2+1]=(byte)s.charAt(i);}
ps.write(buf,0,len);
ps.flush();
bos.flush();
} catch(Exception e){System.out.println(" sendString() error:"+e);}
//System.out.println(" has sent.");
}

String receivString(DataInputStream in) {
String inline="";
int len=0;
try{
inline=in.readLine();
for(int i=0;inline.equals("");i++){inline=in.readLine();Thread.sleep(100);if(i==300)return "";}
len=new Integer(inline).intValue();
}catch(Exception e){System.out.println(" receivString() error:"+e);}
char c[]=new char[len];
byte buf[]=new byte[len*2];
try{in.readFully(buf);}
catch(IOException e){System.out.println(" receivString() error:"+e);}
for(int i=0;i {c[i]=(char)(buf[i*2]<<8);c[i]=(char)((c[i]&0xff00)|(0x00ff&buf[i*2+1]));}
String s=null;
s=new String(c);
return s;
}

</td> </tr> <tr>


↑返回目录
前一篇: 捕捉未捕获的异常
后一篇: java应用程序中发送URL中带参数的请求