QQ聊天项目演示-多对多的聊天
![1167156-20181201004717214-1374261844.png](https://i-blog.csdnimg.cn/blog_migrate/a44d0d8ad641f929117f8b4000dca010.png)
流同时并发的异常;
服务器中转
2/3号只有一个连接;
![1167156-20181201004717574-100227238.png](https://i-blog.csdnimg.cn/blog_migrate/39d5601473bb270924ac650f62cfb295.png)
接收信息:while循环不停地读取;
![1167156-20181201004717797-1562669635.png](https://i-blog.csdnimg.cn/blog_migrate/9053b401f8b77bb7716ceaf1f686e86c.png)
当1号点开两个窗口时,2/3会共同去争取这个socket;
会出现流同时并发异常;
![1167156-20181201004717970-1578196418.png](https://i-blog.csdnimg.cn/blog_migrate/464004e4f784e3723df1eb47c69dd534.png)
Socket处理成static不太合理;
启动一个窗口就会占据Socket不放;
-------------
-实现真正的多人聊天,互相不出现错误
-每一个登录的账号独享一份socket
-去掉static
当一个链接达成的时候,
启动线程,
和服务器保持通讯的状态;
做一个类来管理客户端这边的线程;
-新的账号登录,开一个线程ClientConServerThread
![1167156-20181201004718946-378318065.png](https://i-blog.csdnimg.cn/blog_migrate/26875cba72e530cc8d46bb2ba419966a.png)
![1167156-20181201004719382-299252066.png](https://i-blog.csdnimg.cn/blog_migrate/ed50ea30fec913c838f4f18920d3ba0d.png)
-管理线程的类,放入到一个HashMap里面:ManageClientConServerThread;
HashMap<k,v>
k:用户账号,某个账号独享的线程
v:ClientConServerThread
![1167156-20181201004720269-473028076.png](https://i-blog.csdnimg.cn/blog_migrate/202e3753b64436ef49dc024ce57e2ffd.png)
-----------------
-当账号登录成功,创建线程,把线程放入HashMap
![1167156-20181201004720474-1103333129.png](https://i-blog.csdnimg.cn/blog_migrate/ec9c5eb263a7e5b8bd5fd8e5b360b263.png)
--------------------
Qq聊天界面没有必要成为一个线程;本身是不合理的;
![1167156-20181201004721663-1517595931.png](https://i-blog.csdnimg.cn/blog_migrate/965dbdfccd8577698b86810a5ccdf7e9.png)
通过 管理线程的类,取得了线程,通过线程取得socket,
通过socket,取得了输出流:getOutputStream;
----------------------
给每一个登录账号分配线程;
把通讯的任务交给线程类;
---------------------
-多对多聊天
经测试,后台发送消息正常;
---------------
-显示后台的消息到各自的聊天界面
QqChat.java]
/** * 这是与好友聊天的界面 * 因为客户端,要处于读取的状态,因此我们把它做成一个线程 */ package com.qq.client.view; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.Date; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.JTextField; import com.qq.client.model.QqClientConServer; import com.qq.client.tools.ManageClientConServerThread; import com.qq.common.Message; import com.qq.common.MessageType; public class QqChat extends JFrame implements ActionListener { JTextArea jta; JTextField jtf; JButton jb; JPanel jp; JScrollPane jsp; String ownerId; String friendId; // public static void main(String[] args) { // new QqChat("小四"); // } // 构造函数 public QqChat(String friendId, String ownerId) { this.ownerId = ownerId; this.friendId = friendId; jta = new JTextArea(); jtf = new JTextField(15); jb = new JButton("发送"); jb.addActionListener(this); jp = new JPanel(); jp.add(jtf); jp.add(jb); jsp = new JScrollPane(jta); this.add(jsp, "Center"); this.add(jp, "South"); this.setTitle(ownerId + " 正在与 " + friendId + " 聊天"); this.setSize(350, 300); this.setIconImage((new ImageIcon("image/qie.jpg")).getImage()); this.setLocationRelativeTo(null); this.setVisible(true); } // 写一个方法,让它显示消息 public void showMessage(Message m){ String info=m.getSender()+" 对 "+m.getGetder()+" 说:"+m.getCon()+"\r\n"; jta.append(info); } @Override public void actionPerformed(ActionEvent e) { if (e.getSource() == jb) { // 如果用户点击了发送按钮 Message m = new Message(); m.setMesType(MessageType.message_comm_mes); m.setSender(this.ownerId); m.setGetder(this.friendId); m.setCon(this.jtf.getText()); m.setSendTime(new Date().toString()); jta.append(ownerId + " 对 " + friendId + " 说:" + jtf.getText() + "\r\n"); jtf.setText(""); // 发送给服务器 try { ObjectOutputStream oos = new ObjectOutputStream( ManageClientConServerThread .getClientConServerThread(ownerId).getS() .getOutputStream()); oos.writeObject(m); } catch (Exception e1) { e1.printStackTrace(); } } } // @Override // public void run() { // while(true){ // //读取[如果读不到就等待] // try { // ObjectInputStream ois=new // ObjectInputStream(QqClientConServer.s.getInputStream()); // Message m=(Message)ois.readObject(); // //显示出来 // String info=m.getSender()+" 对 "+m.getGetder()+" 说:"+m.getCon()+"\r\n"; // jta.append(info); // } catch (Exception e) { // e.printStackTrace(); // } // } // } }
110
110
1
/**
2
* 这是与好友聊天的界面
3
* 因为客户端,要处于读取的状态,因此我们把它做成一个线程
4
*/
5
package com.qq.client.view;
6
7
import java.awt.event.ActionEvent;
8
import java.awt.event.ActionListener;
9
import java.io.IOException;
10
import java.io.ObjectInputStream;
11
import java.io.ObjectOutputStream;
12
import java.util.Date;
13
import javax.swing.ImageIcon;
14
import javax.swing.JButton;
15
import javax.swing.JFrame;
16
import javax.swing.JPanel;
17
import javax.swing.JScrollPane;
18
import javax.swing.JTextArea;
19
import javax.swing.JTextField;
20
import com.qq.client.model.QqClientConServer;
21
import com.qq.client.tools.ManageClientConServerThread;
22
import com.qq.common.Message;
23
import com.qq.common.MessageType;
24
25
public class QqChat extends JFrame implements ActionListener {
26
JTextArea jta;
27
JTextField jtf;
28
JButton jb;
29
JPanel jp;
30
JScrollPane jsp;
31
String ownerId;
32
String friendId;
33
34
// public static void main(String[] args) {
35
// new QqChat("小四");
36
// }
37
38
// 构造函数
39
public QqChat(String friendId, String ownerId) {
40
this.ownerId = ownerId;
41
this.friendId = friendId;
42
43
jta = new JTextArea();
44
jtf = new JTextField(15);
45
jb = new JButton("发送");
46
jb.addActionListener(this);
47
jp = new JPanel();
48
jp.add(jtf);
49
jp.add(jb);
50
jsp = new JScrollPane(jta);
51
52
this.add(jsp, "Center");
53
this.add(jp, "South");
54
55
this.setTitle(ownerId + " 正在与 " + friendId + " 聊天");
56
this.setSize(350, 300);
57
this.setIconImage((new ImageIcon("image/qie.jpg")).getImage());
58
this.setLocationRelativeTo(null);
59
this.setVisible(true);
60
}
61
62
// 写一个方法,让它显示消息
63
public void showMessage(Message m){
64
String info=m.getSender()+" 对 "+m.getGetder()+" 说:"+m.getCon()+"\r\n";
65
jta.append(info);
66
}
67
68
@Override
69
public void actionPerformed(ActionEvent e) {
70
if (e.getSource() == jb) {
71
// 如果用户点击了发送按钮
72
Message m = new Message();
73
m.setMesType(MessageType.message_comm_mes);
74
m.setSender(this.ownerId);
75
m.setGetder(this.friendId);
76
m.setCon(this.jtf.getText());
77
m.setSendTime(new Date().toString());
78
jta.append(ownerId + " 对 " + friendId + " 说:" + jtf.getText()
79
+ "\r\n");
80
jtf.setText("");
81
// 发送给服务器
82
try {
83
ObjectOutputStream oos = new ObjectOutputStream(
84
ManageClientConServerThread
85
.getClientConServerThread(ownerId).getS()
86
.getOutputStream());
87
oos.writeObject(m);
88
} catch (Exception e1) {
89
e1.printStackTrace();
90
}
91
}
92
}
93
94
// @Override
95
// public void run() {
96
// while(true){
97
// //读取[如果读不到就等待]
98
// try {
99
// ObjectInputStream ois=new
100
// ObjectInputStream(QqClientConServer.s.getInputStream());
101
// Message m=(Message)ois.readObject();
102
// //显示出来
103
// String info=m.getSender()+" 对 "+m.getGetder()+" 说:"+m.getCon()+"\r\n";
104
// jta.append(info);
105
// } catch (Exception e) {
106
// e.printStackTrace();
107
// }
108
// }
109
// }
110
}
*******************************************************************************
com.qq.client.model
[QqClienConServer.java]
/** * 这是客户端连接服务器的后台 * 功能:客户端与服务器进行数据交互 */ package com.qq.client.model; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.net.Socket; import com.qq.client.tools.ClientConServerThread; import com.qq.client.tools.ManageClientConServerThread; import com.qq.common.Message; import com.qq.common.User; public class QqClientConServer { public Socket s; //发送第一次请求 public boolean sendLoginInfoToServer(Object o){ boolean b=false; try { s=new Socket("127.0.0.1",9999); ObjectOutputStream oos=new ObjectOutputStream(s.getOutputStream()); oos.writeObject(o); ObjectInputStream ois=new ObjectInputStream(s.getInputStream()); Message ms=(Message)ois.readObject(); //这里就是验证用户登录的地方 if(ms.getMesType().equals("1")){ b=true; //就创建一个该qq号和服务器端保持通讯连接的线程 ClientConServerThread ccst=new ClientConServerThread(s); //启动该通讯线程 ccst.start(); ManageClientConServerThread.addClientConServerThread(((User)o).getUserId(), ccst); } } catch (Exception e) { e.printStackTrace(); } finally{ } return b; } //向服务器发送一个对象 public void SendInfoToServer(Object o){ try { Socket s=new Socket("127.0.0.1",9999); } catch (Exception e) { e.printStackTrace(); } finally{ } }
54
54
1
/**
2
* 这是客户端连接服务器的后台
3
* 功能:客户端与服务器进行数据交互
4
*/
5
package com.qq.client.model;
6
7
import java.io.ObjectInputStream;
8
import java.io.ObjectOutputStream;
9
import java.net.Socket;
10
import com.qq.client.tools.ClientConServerThread;
11
import com.qq.client.tools.ManageClientConServerThread;
12
import com.qq.common.Message;
13
import com.qq.common.User;
14
15
public class QqClientConServer {
16
public Socket s;
17
18
//发送第一次请求
19
public boolean sendLoginInfoToServer(Object o){
20
boolean b=false;
21
22
try {
23
s=new Socket("127.0.0.1",9999);
24
ObjectOutputStream oos=new ObjectOutputStream(s.getOutputStream());
25
oos.writeObject(o);
26
27
ObjectInputStream ois=new ObjectInputStream(s.getInputStream());
28
Message ms=(Message)ois.readObject();
29
//这里就是验证用户登录的地方
30
if(ms.getMesType().equals("1")){
31
b=true;
32
//就创建一个该qq号和服务器端保持通讯连接的线程
33
ClientConServerThread ccst=new ClientConServerThread(s);
34
//启动该通讯线程
35
ccst.start();
36
37
ManageClientConServerThread.addClientConServerThread(((User)o).getUserId(), ccst);
38
}
39
} catch (Exception e) {
40
e.printStackTrace();
41
} finally{
42
}
43
return b;
44
}
45
46
//向服务器发送一个对象
47
public void SendInfoToServer(Object o){
48
try {
49
Socket s=new Socket("127.0.0.1",9999);
50
} catch (Exception e) {
51
e.printStackTrace();
52
} finally{
53
}
54
}
}
*******************************************************************************
[QqClientUser.java]
/** * 用户操作逻辑类 * QqClientUser会调用QqClientConServer的 * 方法sendLoginInfoToServer向服务器发送 */ package com.qq.client.model; import com.qq.common.User; public class QqClientUser { //验证用户是否合法 public boolean checkUser(User u){ return new QqClientConServer().sendLoginInfoToServer(u); } }
15
15
1
/**
2
* 用户操作逻辑类
3
* QqClientUser会调用QqClientConServer的
4
* 方法sendLoginInfoToServer向服务器发送
5
*/
6
package com.qq.client.model;
7
8
import com.qq.common.User;
9
10
public class QqClientUser {
11
//验证用户是否合法
12
public boolean checkUser(User u){
13
return new QqClientConServer().sendLoginInfoToServer(u);
14
}
15
}
*******************************************************************************
com.qq.client.tools
[ClientConServerThread.java]
/** * 这是客户端和服务器端保持通讯的线程 */ package com.qq.client.tools; import java.io.ObjectInputStream; import java.net.Socket; import com.qq.client.view.QqChat; import com.qq.client.view.QqFriendList; import com.qq.common.Message; import com.qq.common.MessageType; public class ClientConServerThread extends Thread{ private Socket s; public Socket getS() { return s; } public void setS(Socket s) { this.s = s; } //构造函数 public ClientConServerThread(Socket s){ this.s=s; } public void run(){ while(true){ //不停的读取从服务器端发来的消息 try { ObjectInputStream ois=new ObjectInputStream(s.getInputStream()); Message m=(Message)ois.readObject(); //按消息包的类型来处理不同的包 if(m.getMesType().equals(MessageType.message_comm_mes)){ //把从服务器获得的消息,显示到该显示的聊天界面上 QqChat qqChat=ManageQqChat.getQqChat(m.getGetder()+" "+m.getSender()); //显示 qqChat.showMessage(m); }else if(m.getMesType().equals(MessageType.message_ret_onLineFriend)){ String con=m.getCon(); String []friends=con.split(" "); String getter=m.getGetder(); //修改相应的好友列表 QqFriendList qqFrindList=ManageQqFriendList.getQqFriendList(getter); //更新在线好友 if(qqFrindList!=null){ qqFrindList.updateFriend(m); } } } catch (Exception e) { e.printStackTrace(); } } } }
58
58
1
/**
2
* 这是客户端和服务器端保持通讯的线程
3
*/
4
package com.qq.client.tools;
5
6
import java.io.ObjectInputStream;
7
import java.net.Socket;
8
import com.qq.client.view.QqChat;
9
import com.qq.client.view.QqFriendList;
10
import com.qq.common.Message;
11
import com.qq.common.MessageType;
12
13
public class ClientConServerThread extends Thread{
14
private Socket s;
15
16
public Socket getS() {
17
return s;
18
}
19
20
public void setS(Socket s) {
21
this.s = s;
22
}
23
24
//构造函数
25
public ClientConServerThread(Socket s){
26
this.s=s;
27
}
28
29
public void run(){
30
while(true){
31
//不停的读取从服务器端发来的消息
32
try {
33
ObjectInputStream ois=new ObjectInputStream(s.getInputStream());
34
Message m=(Message)ois.readObject();
35
36
//按消息包的类型来处理不同的包
37
if(m.getMesType().equals(MessageType.message_comm_mes)){
38
//把从服务器获得的消息,显示到该显示的聊天界面上
39
QqChat qqChat=ManageQqChat.getQqChat(m.getGetder()+" "+m.getSender());
40
//显示
41
qqChat.showMessage(m);
42
}else if(m.getMesType().equals(MessageType.message_ret_onLineFriend)){
43
String con=m.getCon();
44
String []friends=con.split(" ");
45
String getter=m.getGetder();
46
//修改相应的好友列表
47
QqFriendList qqFrindList=ManageQqFriendList.getQqFriendList(getter);
48
//更新在线好友
49
if(qqFrindList!=null){
50
qqFrindList.updateFriend(m);
51
}
52
}
53
} catch (Exception e) {
54
e.printStackTrace();
55
}
56
}
57
}
58
}
*******************************************************************************
[ManageClientConServerThread.java]
/** * 这是一个管理客户端与服务器保持通讯的线程类 */ package com.qq.client.tools; import java.util.HashMap; public class ManageClientConServerThread { private static HashMap hm=new HashMap<String, ClientConServerThread>(); //把创建好的ClientConServerThread放入到hm中 public static void addClientConServerThread(String qqId,ClientConServerThread ccst){ hm.put(qqId, ccst); } //可以通过qqId取得该线程 public static ClientConServerThread getClientConServerThread(String qqId){ return (ClientConServerThread)hm.get(qqId); } }
x
1
/**
2
* 这是一个管理客户端与服务器保持通讯的线程类
3
*/
4
package com.qq.client.tools;
5
6
import java.util.HashMap;
7
8
public class ManageClientConServerThread {
9
private static HashMap hm=new HashMap<String, ClientConServerThread>();
10
//把创建好的ClientConServerThread放入到hm中
11
public static void addClientConServerThread(String qqId,ClientConServerThread ccst){
12
hm.put(qqId, ccst);
13
}
14
15
//可以通过qqId取得该线程
16
public static ClientConServerThread getClientConServerThread(String qqId){
17
return (ClientConServerThread)hm.get(qqId);
18
}
19
}
20