这是我自己写的一个C/S架构的类似MSN的聊天工具,当然功能和稳定性都差远了。下面就直入正题(由于篇幅过长,所以分两篇)
一、功能
多人在线绘图,即时显示,群聊,私聊,在线隐身转换,点歌,更改本地字体,剪切复制等很基本的功能,但都不是很完善。有兴趣的朋友可以看一下,对照这些功能可以再完善一下。
二、主要代码介绍
主要部分是ServerThread.java,ServerStartWindow.java,ClientThread.java,ClientFrame.其中ServerThread.java是服务器端用来处理多用户请求的一个线程,而ServerStartWindow.java负责连接这些线程。ClientThread.java是客户端的一个线程,负责接收服务器传来的信息,ClientFrame是客户端的界面,同时还肩负着向服务器发送信息的任务。下面我着重介绍一下这几个类。
ServerThread.java
先看一下构造函数
public ServerThread(Socket s, Vector v) {
this.socket = s;
this.threads = v;
try {
oos = new ObjectOutputStream(socket.getOutputStream());
ois = new ObjectInputStream(socket.getInputStream());
new Thread(new Runnable() {
public void run() {
boolean first=true;
while (true) {
try {
write(checkDatabase.getOnlineUser());
if(first){
Thread.sleep(1000);
first=false;
}else{
Thread.sleep(5000);
}
} catch (Exception e) {
}
}
}
}).start();
} catch (IOException e) {
e.printStackTrace();
}
}
这个构造函数并没什么特别,就是获得一个连接客户端的Socket,并且得到一个Vector
然后是这个线程类最重要的run方法
public void run() {
Object o = this.read(); //从客户端读入对象
String flag = (String) o; //强转为String,flag是用来判断客户端这次操作主要目的是登录还是注册
if (flag.substring(0, 4).equals("name")) { //用户登录
sname = flag.substring(4);
this.write(this.currentImage);
System.out.println("have write currentImage"); //像这种System.out.println的都是调试程序用的
o = null;
while (true) {
o = this.read();
System.out.println("have read");
if (o == null) {
System.out.println("read null");
}
if (o instanceof String) {
String msg = (String) o;
if (msg.trim().equals("quit")) {
checkDatabase.setUserOffline(sname);
for (int i = 0; i < threads.size(); i++) {
ServerThread st1 = threads.elementAt(i);
st1.write("**********************" + sname + " leave ***********************\n");
st1.write(checkDatabase.getOnlineUser()); //write()方法是用来向客户端写入的
System.out.println("quit");
}
threads.remove(this); //从线程向量中移除这个线程
break;
} else if (msg.equals("iwanthide")) { //用户想隐身
checkDatabase.setUserOffline(sname);
for (int i = 0; i < threads.size(); i++) {
ServerThread st1 = threads.elementAt(i);
st1.write(checkDatabase.getOnlineUser());
}
} else if (msg.equals("iwantshow")) {
checkDatabase.setUserOnline(sname);
} else if (msg.equals("iwantcheck")) { //用户想要检索数据库的数据
System.out.println("have receiver check message....");
String[] checkMess = {"checkmessage", checkDatabase.getMessage(this.getName())};
this.write(checkMess);
System.out.println("hafve writer check mess");
} else if (msg.length() >= 7) {
if (msg.substring(0, 7).equals("private")) {
String requestName = msg.substring(7);
for (int k = 0; k < threads.size(); k++) {
ServerThread str = threads.elementAt(k);
if (str.getName().equals(requestName)) {
String[] sRe = {"request", this.getName()};
str.write(sRe);
break;
}
}
}else if(msg.substring(0,9).equals("sendMusic")){
String musicName=msg.substring(9,14);
System.out.println(musicName);
String sendName=msg.substring(14);
String[] music={"sendMusic",musicName};
for(int i=0;i
我写的很长,主要是这是整个系统的关键所在,就是这个线程负责整体的协调调度。开始先读入一个对象Object(由于不知道客户端会发来什么信息),后面就是对这些信息分类并分别处理。具体作用已在注释里写的很清楚了。
ServerStartWindow.java
主要代码
static void showFrame() {
String msg = "";
ServerFrame sf = new ServerFrame(); //一个简单的服务器窗口,没什么实际作用
sf.setVisible(true);//关闭窗口时退出程序
ServerSocket socket = null;
Vector threads = new Vector(); //这就是ServerThread.java中用到的向量
msg += "listen\n";
sf.jTextPaneServerMessage.setText(msg);
try {
socket = new ServerSocket(8888);
} catch (Exception e) {
msg += "server failed....\n";
sf.jTextPaneServerMessage.setText(msg);
return;
}
CheckDatabase checkDatabase=new CheckDatabase();
sf.setJList(checkDatabase.getAllUser());
try {
int ID = 0;
while (true) {
Socket s = socket.accept();
msg += "accepted\n";
sf.jTextPaneServerMessage.setText(msg);
ServerThread st = new ServerThread(s, threads);
st.setID(ID++);
threads.addElement(st);
new Thread(st).start();
msg += "listen again....\n";
sf.jTextPaneServerMessage.setText(msg);
}
} catch (Exception e) {
e.printStackTrace();
}
}
这段代码的作用就是不断的监听客户端的请求,每当有一个客户连接时,就为他新建一个ServerThread,并将其添加到向量中。
好了,服务器端的代码主要就这些了,另外还有连接数据库的。本人属于刚入门级的,所以写的代码很烂,但还是把代码发上来供大家批评指正。我还是喜欢开源啊!
下面介绍客户端较重要的两个类
ClientThread.java
先看一下它的构造函数
public ClientThread(ObjectInputStream ois,ClientFrame cf){
this.ois=ois;
this.cf=cf;
}
其中的ois参数是负责接收服务器端发来的信息的,而cf是客户端另一个重要类。很简单。下面是run方法
public void run(){
String s = "";
try {
while (true) {
Object o = cf.read();
Thread tt;
if (o instanceof String) {
String msg = (String) o;
if (msg.equals("deny")) { //私聊惨遭拒绝
JOptionPane.showMessageDialog(cf, "you have been denied");
} else if (msg.equals("successsave")) {
JOptionPane.showMessageDialog(cf, "you have successfully save your message");
} else {
s += (String) o + "\n";
cf.jTextPaneChatArea.setText(s);
}
} else if (o instanceof javax.swing.ImageIcon) { //如果是收到的图像,则画在绘图板上
javax.swing.ImageIcon i = (javax.swing.ImageIcon) o;
Image image = i.getImage();
ToolkitImage ti = (ToolkitImage) image;
cf.drawComponent.drawPanel.setImage(ti.getBufferedImage());
cf.repaint();
} else if (o instanceof Vector) { //在线用户列表
cf.setJList((Vector) (o));
} else if (o instanceof InetAddress) { // 这里是接受IP地址,然后与其连接建立私聊通道
final Object oo = o;
tt = new Thread(new Runnable() {
public void run() {
PrivateClient pr = new PrivateClient((InetAddress) oo);
}
});
tt.start();
} else if (o instanceof String[]) { //这是有人来请求与自己私聊
String[] msg = (String[]) o;
if (msg[0].equals("request")) {
PrivateDialog pd = new PrivateDialog(msg[1], cf);
if (pd.getAccept()) {
String[] sent = {"iaccept", msg[1]};
cf.write(sent);
final String nameOfClient = msg[1];
tt = new Thread(new Runnable() {
public void run() {
PrivateServer pr = new PrivateServer(nameOfClient, cf.getMyName());
}
});
tt.start();
}
}else if(msg[0].equals("sendMusic")){
URL file1 = getClass().getResource(msg[1]);
this.cf.audioClip=java.applet.Applet.newAudioClip(file1);
JOptionPane.showMessageDialog(this.cf,"some one have pick you a music,enjoy it!");
}
else if (msg[0].equals("checkmessage")) {
String sMess = cf.jTextPaneChatArea.getText() + "****这是你上次的聊天记录****\n" + msg[1] + "\n****聊天记录结束****\n";
cf.jTextPaneChatArea.setText(sMess);
} else {
String[] sent = {"ideny", msg[1]};
cf.write(sent);
}
}
}
} catch (Exception e) {
s += "Error";
cf.jTextPaneChatArea.setText(s);
}
}
这段代码也很简单,其实就是接受信息然后分类处理。
ClientFrame.java
这是客户端的界面,同时也负责很多业务,所以是最大的一个类,下面看一下主要代码
public void actionPerformed(ActionEvent e) {
if (e.getSource() == this.jButtonSent) {
s = "speak to " + this.sendTo + ": " + this.jTextPaneUser.getText() + " <<<<<<<<<<<<<<<<<<<<<" + FormatDate.nowDate();
t = new Thread(this.runnable);
t.start();
try {
Thread.sleep(300);
t.stop();
} catch (Exception ex) {
}
} else if (e.getSource() == this.buttonFont) {
FontDialog fd = new FontDialog(this.jTextPaneChatArea.getFont(), this);
fd.setVisible(true);
this.jTextPaneChatArea.setFont(fd.getChoosedFont());
this.jTextPaneUser.setFont(fd.getChoosedFont());
} else if (e.getSource() == this.buttonPrivate) {
this.sendTo = (String) (this.jListUserList.getSelectedValue());
if (this.sendTo == null) {
JOptionPane.showMessageDialog(this, "no person is selected");
} else if (this.sendTo.equals("ALLPERSON")) {
JOptionPane.showMessageDialog(this, "you cannot private chat with allperson");
} else {
s = "private" + this.jListUserList.getSelectedValue();
t = new Thread(this.runnable);
t.start();
try {
Thread.sleep(300);
t.stop();
} catch (Exception ex) {
}
}
} else if (e.getActionCommand().equals("hide")) {
s = "iwanthide";
this.buttonHideShow.setLabel("show");
t = new Thread(this.runnable);
t.start();
try {
Thread.sleep(300);
t.stop();
} catch (Exception ex) {
}
} else if (e.getActionCommand().equals("show")) {
s = "iwantshow";
this.buttonHideShow.setLabel("hide");
t = new Thread(this.runnable);
t.start();
try {
Thread.sleep(300);
t.stop();
} catch (Exception ex) {
}
} else if (e.getSource() == this.buttonCheck) {
s = "iwantcheck";
t = new Thread(this.runnable);
t.start();
try {
Thread.sleep(300);
t.stop();
} catch (Exception ex) {
}
} else if (e.getSource() == this.buttonSave) {
s = "iwantsave" + this.jTextPaneChatArea.getText();
t = new Thread(this.runnable);
t.start();
try {
Thread.sleep(300);
t.stop();
} catch (Exception ex) {
}
} else if (e.getSource() == this.buttonSaveLocal) {
if (fileChooser.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) {
final String fileName = fileChooser.getSelectedFile().getAbsolutePath();
Thread tt = new Thread(new Runnable() {
public void run() {
try {
PrintWriter pw = new PrintWriter(new FileOutputStream(fileName));
pw.write(jTextPaneChatArea.getText());
pw.close();
JOptionPane.showMessageDialog(ClientFrame.this, "you have successfully save your message to local file");
} catch (Exception e) {
e.printStackTrace();
}
}
});
tt.start();
try {
Thread.sleep(300);
tt.stop();
} catch (Exception ex) {
}
}
} else if (e.getSource() == this.buttonListenMusic) {
if (this.audioClip == null) {
JOptionPane.showMessageDialog(this, "util now you haven't receive music");
} else {
if (this.firstAudio) {
audioThread.start();
this.firstAudio = false;
} else {
try {
audioThread.stop();
Thread.sleep(100);
audioThread = new Thread(new Runnable() {
public void run() {
new AudioPlayDialog(audioClip);
}
});
audioThread.start();
} catch (Exception et) {
et.printStackTrace();
}
}
}
} else if (e.getSource() == this.buttonSendMusic) {
String name = (String) this.jListUserList.getSelectedValue();
if (name == null) {
JOptionPane.showMessageDialog(this, "you haven't choosen any person!");
} else {
ChooseMusicDialog mDialog = new ChooseMusicDialog(this);
String music = mDialog.getMusic();
if (music == null) {
} else {
s = "sendMusic" + music + name;
t = new Thread(this.runnable);
t.start();
try {
Thread.sleep(300);
t.stop();
} catch (Exception ex) {
}
}
}
}
}
上面就是能干点事的代码,很简单的处理事件,然后发送信息给ServerThread。
好了,到这主要部分就说完了。只是今天有点失眠,就写了篇文章,把之前做过的一个小东西拿出来献丑了,有很多的bug。还望高手指点啊。附件是我的源码,没经过优化,有点乱。用netbeans6.0以上版本可以成功导入,以下版本会出现乱码。
转载于wolfplanet.iteye.com