电脑技术学习

基于JAVA的ICQ系统的设计于实现

dn001
内容: 出自:www.ibm.com.cn 何刚 2002年10月09日 23:01
分析ICQ系统,并尝试用Java编写。
一.序言
ICQ是英文"I seek you "的简称,中文意思是我找你。ICQ最大的功能就是即时信息交流 ,只要记得对方的号码,上网时可以呼他,无论他在哪里,只要他上网打开ICQ,人们就 可以随时交流。ICQ源于以色列特拉维夫的Mirabils公司。该公司成立于1996年7月, 也就是在这个时候,互联网上最出名,下载使用人数最多的免费软件ICQ诞生了。可能是 其不断增加的用户和广阔的前景以及广泛的应用前景和巨大的市场潜力,Mirabils的ICQ最 终被美国在线AOL收购。由于ICQ的成功,推动了ICQ的本土化,就中文的ICQ而言,现在已经越来越多,比如著名的深圳腾迅公司推出的OICQ(现在由于版权问题,已改名为QQ2001),还有由TOM.COM推出的Tomq等,这些软件技术都很好,而且简单易用,成为 中国网民最喜欢的通信软件。

但是这些公司都只提供软件的客户端程序免费下载,而不提供其服务器程序,因此对于未与互联网连接的私有网络,这些软件就用不上了。当然网上也有免费的类似ICQ的服务器提供下载,但是好多都不提供源程序,即使有,其说明也很简单,我很想知道它是怎么回事,所以我就试着做了。

二.设计

1.为什么选择JAVA?
Java是Sun Microsystem公司的James Gosling开发的编程语言。它以C++为基础,但是却是一个全新的软件开发语言。Java是一个简单,面象对象,分布式,解释性,强壮,安全,与系统无关,可移植,高性能,多线程和动态的语言-------这是 Sun给Java的定义。

Sun公司的口号就是"网络就是计算机",Java能使所有东西从桌面计算平稳的转变为基于网络的计算,它是专门为此而建立的,并显然是为了完成这个任务而来的。使用Java,我们可以相对轻松的一天编写一个有条理的网络程序。今天,Java的网络功能正在飞跃发展,不断有新的特性增加到这个有价值的基础上,JavaSoft实验室正在不断努力使Java更加完善。

2.数据库设计
系统可以采用任何一种流行的,Java支持的数据库,本系统采用了Microsoft公司的SQL Server2000作为后台数据库。通过对现在流行的一些Icq的参考,建立数据库,名为javaicq,数据库共建立两个表,一个是用户的基本信息,包括呢称,Jicq号码等。一个是用户的好友表,包括用户自己的号码和好友的号码。

(1)用户的基本信息表(表名icq) 序号 字段名 含义 数据类型 NULL
1 Icqno 用户的号码 Int No
2 Nickname 用户的呢称 Char No
3 Password 用户的密码 Char No
4 Status 用户在线否 Bit No
5 Ip 用户的IP地址 Char Yes
6 Info 用户的资料 Varchar Yes
7 Pic 用户的头像号 Int Yes
8 Sex 用户性别 Char Yes
9 Email 用户的email Char Yes
10 Place 用户的籍贯 Char yes
其中Icqno字段为自动增加。(其他还可以添加诸如电话号码等字段作为更多选择)

(2)用户的好友表(表名friend) 序号 字段名 含义 数据类型 NULL
1 Icqno 用户的号码 Int No
2 Friend 好友的号码 Int No


3. 系统模式及程序(具体程序参看源程序)
系统采用客户/服务器摸式(如图)



服务器程序:
服务器与客户间通过套接口Socket(TCP)连接。在java中使用套接口相当简单,Java API为处理套接口的通信提供了一个类java.net.Socket.,使得编写网络应用程序相对容易.服务器采用多线程以满足多用户的请求,通过JDBC与后台数据库连接,并通过创建一个ServerSocket对象来监听来自客户的连接请求,默认端口为8080,然后无限循环调用accept()方法接受客户程序的连接

服务器程序代码如下:(部分)
import java.io.*;
import java.net.*;
import java.sql.*;
import java.util.Vector;
class ServerThread extends Thread{//继承线程
private Socket socket;//定义套接口
private BufferedReader in;//定义输入流
private PrintWriter out;//定义输出流
int no;//定义申请的jicq号码
public ServerThread(Socket s) throws IOException {//线程构造函数
socket=s;//取得传递参数
in=new BufferedReader(new InputStreamReader(socket.getInputStream()));//创建输入流
out=new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())),true);//创建输出流
start();//启动线程
}

public void run(){//线程监听函数
try{ while(true){
String str=in.readLine();//取得输入字符串
if(str.equals("end"))break;//如果是结束就关闭连接
else if(str
//否者告诉客户失败
else out.println("false");r.close();c.close();}
else{ out.println("false");
System.out.println("false");
r.close();
c.close();}
}catch (Exception e){e.printStackTrace();}
socket.close();
}//end login
//登录结束
//以下为处理客户的新建请求
else if(str
out.println(no);
out.println("ok");
c2.close();
//完毕
}catch (Exception e){e.printStackTrace();out.println("false");}
socket.close();
}//end new
//新建用户结束
//以下处理用户查找好友
else if(str//while end
out.println("over");
////////GET ICQNO
int d,x;
boolean y;
//以下返回用户的jicq号码,头像号,及是否在线
ResultSet iset=st.executeQuery("select icqno,pic,status from icq");
while(iset
else {out.println("0");}
//System.out.println(d);
}
// end send jicqno
iset.close();
/////////icqno end
c3.close();result.close();
}catch (Exception e){e.printStackTrace();System.out.println("false");}
//socket.close();
}//end find
//查找好友结束
//以下处理用户登录时读取其好友资料
else if(str
//read friend info
//以下告诉客户其好友的呢称,号码,ip地址,状态,头像,个人资料等信息
out.println(friendno.size());
for(int i=0;iout.println(r5.getInt("pic"));
out.println(r5.getString("email"));
out.println(r5.getString("info"));
} //while
r5.close();
}//for
//发送完毕
out.println("over");
System.out.println("over");
c4.close();r4.close();
}catch (Exception e){e.printStackTrace();System.out.println("false");}
//socket.close();
}//end friend
//读取好友信息完毕
//以下处理用户添加好友
else if(strcatch (Exception e){e.printStackTrace();System.out.println("false");}

//socket.close();
System.out.println("over addfriend");
}//end addfriend
//用户添加好友结束
//add new friend who add me
//以下处理其他用户如果加我,我就加他
else if(str
out.println(r5.getInt("pic"));
out.println(r5.getString("email"));
out.println(r5.getString("info"));
} //while
out.println("over");
r5.close();
c6.close();
}catch (Exception e){e.printStackTrace();System.out.println("false");}
System.out.println("over addnewfriend");
}//end addfriend
//结束处理其他用户如果加我,我就加他
//delete friend
//以下执行用户删除好友
else if(strcatch (Exception e){e.printStackTrace();System.out.println("del false");}
}//end delete friend
//执行用户删除好友结束
//以下处理用户退出程序
else if(strcatch (Exception e){e.printStackTrace();System.out.println("logout false");}
}//logout end
//处理用户退出程序结束
//get who add me as friend
//以下处理那些人加了我为好友,以便上线通知他们
else if(str//end while
//然后告诉这些好友的ip地址,然后发给用户以便告诉其他客户我上线了
for(int i=0;ir.close();
}//for
out.println("over");
System.out.println("over");
c9.close();r6.close();
}catch (Exception e){e.printStackTrace();System.out.println("false");}
}//end get who add me as friend
//处理上线结束
System.out.println("Echo ing :"+str);
} System.out.println("Close...");
}catch(IOException e){}//捕或异常
finally {try{socket.close();}
catch(IOException e){}
}
}
}
public class Server{//主服务器类
public static void main(String args[])throws IOException{
ServerSocket s=new ServerSocket(8080);//在8080端口创建套接口
System.out.println("Server start.."+s);
try{
while(true){Socket socket=s.accept();//无限监听客户的请求
System.out.println("Connectino accept:"+socket);
try{new ServerThread(socket);//创建新线程
}catch(IOException e){socket.close();}
}
}finally{s.close();}//捕或异常
}
}//服务器程序结束



客户程序如下(部分)
客户通过Socket(InetAddress,port)建立与服务器的连接。服务器与客户都通过构造BufferedReader,PrintWriter来建立输入输出流,然后双方通过该输入输出流来相互传递信息,一旦收到客户方的连接请求,服务器accept()方法返回一个新建的Socket对象。客户端然后向服务器发送消息,比如注册,登录,查找好友等,服务器收到来自客户的请求后,针对不同的消息处理请求, 虽然UDP不可靠但是对于icq可靠性并不太重要,而且UDP快速,所以客户间发送信息通过UDP。用户登录时通过类DatagramPacket和DatagramSocket创建UDP包括其本地接受端口以及发送端口,默认端口为5000和5001,通过取得的好友的IP地址来向好友发送消息(send(DatagramPacket)和接受消息(receive(DatagramPacket))。当用户通过UDP收到消息后,可以通过DatagramPacket的方法InetAddress getAddress()得到对方的ip地址,通过对好友列表比较以判断是谁并提示用户收到某某的消息,然后用户选择该用户查看消息,如果好友列表没有该人就显示收到陌生人的消息。用户可以按陌生人按钮查看消息。

用户注册。当服务器收到用户的注册请求,便开始接受客户传第的信息,诸如客户的呢称啦,性别,籍贯,头像,个人资料等,接受完毕后,便通过JdbcOdbc与后台数据库连接,然后向数据库添加记录,如果成功,便向客户返回其Jicq号码,并在数据库中注册用户的IP地址,然后更新其Status为1即用户在线。客户收到服务器返回的信息后,便打开主程序窗口,并同时开始创建UDP以便在用户之间建立联系。
******部分程序如下:
void jButton1_mouseClicked(MouseEvent e) {
try{
Socket socket=new Socket(InetAddress.getByName(sername),serverport);//连接服务器
BufferedReader in=new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out=new PrintWriter(new BufferedWriter(
new OutputStreamWriter(socket.getOutputStream())),true);
out.println("new");//告诉服务器我要注册
out.println(nickname.getText().trim());//告诉服务器我的呢称,密码,email,资料
out.println(password.getPassword());//以及头像号等信息
out.println(email.getText().trim());
out.println(info.getText().trim());
out.println(place.getSelectedItem());
out.println(headpic.getSelectedIndex());//head picindex
int no;
no=Integer.parseInt(in.readLine());
//System.out.print(no);

String str=" ";
str=in.readLine().trim();//从服务器取得状态
if(str.equals("false"))
JOptionPane.showMessageDialog(this,"对不起,出错了:- (","ok",JOptionPane.INFORMATION_MESSAGE);//失败就警告
else{//成功就打开主程序
JOptionPane.showMessageDialog(this,"your javaicq this.dispose();
MainWin f2=new MainWin(no,sername,serverport);
f2.setVisible(true);}
//System.out.println("n");
//}while(!str.equals("ok"));
// socket.close();
}catch(IOException e1){}
}
}




用户登录。在客户端,用户输入其jicq号码和密码,然后建立与服务器的连接,告诉服务器我要登录,服务器收到后,开始通过JdbcOdbc读取数据库,然后与用户输入的信息比较,如果相同就向客户返回成功消息并将其Status字段设为1表示上线了以及注册其IP地址,否则返回错误,如果客户收到成功信息就打开主窗口,否则提示出错。如果成功,便打开主程序窗口,并同时开始创建UDP以便在用户之间建立联系。然后客户向服务器请求读取好友名单,服务器收到该请求,开始读取数据库中的friend表,得到好友的号码后,再在icq表中读取好友资料,然后向客户端发送这些信息,客户收到后就在主窗口显示好友,比如头像,呢称。并且建立几个矢量(Vector)用以存储好友的呢称,jicq号码,头像编号,ip地址等信息。
部分程序如下:(程序流程图与注册差不多,略)
void login_mouseClicked(MouseEvent e) {
try{Socket socket=new Socket(InetAddress.getByName(server),serport);//与服务器连接
BufferedReader in=new BufferedReader(new InputStreamReader(socket.getInputStream()));//创建//输入流
PrintWriter out=new PrintWriter(new BufferedWriter(
new OutputStreamWriter(socket.getOutputStream())),true); //创建输出流
out.println("login");//告诉服务器我要登录
out.println(jicq.getText()); //告诉服务器我的号码
out.println(password.getPassword()); //告诉服务器我的密码
String str=" ";
//do{
str=in.readLine().trim();//取得服务器发过来的消息
if(str.equals("false"))
JOptionPane.showMessageDialog(this,"对不起,出错了:-(","ok",JOptionPane.INFORMATION_MESSAGE);//失败
else{
this.dispose();
int g=Integer.parseInt(jicq.getText());
MainWin f2=new MainWin(g,server,serport);//如果成功就打开主程序
f2.setVisible(true);
}
//System.out.println("n");
//}while(!str.equals("ok"));
}catch(IOException e1){}
}



用户添加好友。客户登录后,按查找按钮后,开始向服务器发出查找请求,服务器读取数据库表icq并向客户返回其结果,客户收到后在查找窗口中显示,如果用户选择了一个好友,就向服务器发送添加好友请求,服务器收到后就向数据库表friend中添加自己的号码以及好友的号码,并从icq表中读取其基本信息返回给客户端,然后客户收到并在主窗口显示该好友。并且通过UDP通知该客户,对方收到该消息后,可以选择添加该用户为好友或者不。(程序流程图与程序略,参看源程序)
用户删除好友。用户在其好友列表中选择要删除的好友并按删除,然后向服务器发送删除请求,服务器收到该请求后,连接数据库表friend删除用户及该好友的记录,如果成功就向客户返回成功消息,客户收到后在其好友列表中删除该好友。(程序流程图与程序略,参看源程序)
用户发送和接收消息.用户通过在好友列表里的好友的ip地址,通过UDP与其他用户进行信息交流, (程序流程图与程序略,参看源程序src.zip)及部分程序运行结果界面图pic.zip
三.程序界面
程序界面主要参考腾迅公司的QQ2000,在实现时采用的Java的Swing图形包。开发工具用的是Borland公司的Jbuilder4。

四.程序的使用
源程序参见 src.zip.
部分程序运行结果界面图pic.zip.

服务器端:(Windows平台)首先应有java环境jdk,建议采用jdk1.3。然后应有一个数据库系统,建立数据库javaicq,并且建立表icq和表friend。然后在控制面板中的ODBC数据源中设置数据源javaicq建立与数据库的连接。然后运行javac Server.java ,java Server及可。
客户端:只要有有java环境jdk就可以建议jdk1.3。然后运行javac New.java ,java New及可。
五.补充:
我在其他机器上测试了一下,发先有些问题如下,请大家改进,指导,谢谢!

请把Register.java 中的void newuser_mouseClicked(MouseEvent e)函数改为以下,不然程序会一直连接计算机名称为hg的服务器
void newuser_mouseClicked(MouseEvent e) {//新建用户按纽
this.dispose();
server=servername.getText().toString().trim();
serport=Integer.parseInt(serverport.getText().trim());
JDialog d=new Register(server,serport);//打开新建窗口
d.pack();
d.setLocationRelativeTo(this);
d.setSize(400,400);
d.show();
}


如果在Sql server 里用到了用户登录(比如用户名为sa,密码为1w3e),请把所有的
Connection c=DriverManager.getConnection("jdbc:odbc:javaicq","","");
改为
Connection c=DriverManager.getConnection("jdbc:odbc:javaicq","sa","1w3e");

在客户端程序运行时的图片为jpg格式,单数为在线,双数为不在线,比如:1.jpg(在线),2.jpg(不在线)...可以用Acdsee把oicq里的头像另存为jpg拿来用.
另外由于我以前只有在局域网里用,因为人少,所以好多数据库里的数据为手工输入的,而且在客户查找好友时也不是在线的,而是所有数据库里的,这里需要改进。
在客户添加好友后(而且添加好友窗口不能关闭),需要按更新按钮才能在主窗口里显示,这里也需要改进。
另外还可以在数据库里添加如身份验证字段,及建个表来存客户的留言如果好友不在线时....
数据库的建立参考:(sql.txt)
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[icq]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
drop table [dbo].[icq]
GO
if exists (select * from dbo.sysobjects where id = object_id(N'[dbo].[friend]') and OBJECTPROPERTY(id, N'IsUserTable') = 1)
drop table [dbo].[friend]
GO
CREATE TABLE [dbo].[icq] (
[icqno] [int] IDENTITY (1, 1) NOT NULL ,
[nickname] [char] (30) COLLATE Chinese_PRC_CI_AS NULL ,
[password] [char] (30) COLLATE Chinese_PRC_CI_AS NULL ,
[status] [bit] NULL ,
[ip] [char] (30) COLLATE Chinese_PRC_CI_AS NULL ,
[info] [char] (60) COLLATE Chinese_PRC_CI_AS NULL ,
[pic] [int] NULL ,
[sex] [char] (10) COLLATE Chinese_PRC_CI_AS NULL ,
[email] [char] (30) COLLATE Chinese_PRC_CI_AS NULL ,
[place] [char] (40) COLLATE Chinese_PRC_CI_AS NULL
) ON PRIMARY
GO
CREATE TABLE [dbo].[friend] (
[icqno] [int] NULL ,
[friend] [int] NULL
) ON PRIMARY
GO
ALTER TABLE [dbo].[icq] WITH NOCHECK ADD
CONSTRAINT [PK_icq] PRIMARY KEY CLUSTERED
(
[icqno]
) ON PRIMARY
GO


六.参考资料:

Java2图形设计 卷二:Swing (美) David M.Geary
Java2 编程思想 (美) Bruce Eckel
UNIX网络编程(第一卷)(美) W.Richard Stevens

另外,该程序在端口上选取的是固定的一个端口,所以在同一机器只能运行一次,如果单机调试可以修改客户程序的udp端口(我的5000目录是个例子,将int udpPORT=5001,int sendPort=5000改为int udpPORT=5000,int sendPort=5001)

关于作者
何刚,毕业与于重庆三峡学院计算机系,现在在德阳一个公司工作。我很喜欢java,只是现在没有用它了。我的javaicq也是我的毕业设计,当时考虑的不全面,程序和oicq还有很大差别,比如头像不会闪动呀,不过还是有点像,我将源程序全部提供,希望大家帮助我完善,谢谢!Email: he_gum@chinaren.com
Java, java, J2SE, j2se, J2EE, j2ee, J2ME, j2me, ejb, ejb3, JBOSS, jboss, spring, hibernate, jdo, struts, webwork, ajax, AJAX, mysql, MySQL, Oracle, Weblogic, Websphere, scjp, scjd

标签: