模拟QQ在线聊天

首先,要好好构思,先造出界面,如图:



界面中要注意块的分布

界面出来后,要考虑服务器与客户端之间通讯的格式(协议)总结出一定的格式,再以此格式相互之间进行通讯,以下 代码只做了简单的防护,相互之间的通讯要同时。

我总结的协议如下:

在服务器端 用一个HashMap<userName,socket> 维护所有用户相关的信息,从而能够保证和所有的用户进行通讯。
客户端的动作:
(1)连接(登录):发送userName    服务器的对应动作:1)界面显示,2)通知其他用户关于你登录的信息, 3)把其他在线用户的userName通知当前用户 4)开启一个线程专门为当前线程服务
(2)退出(注销):
(3)发送消息
※※发送通讯内容之后,对方如何知道是干什么,通过消息协议来实现:
客户端向服务器发的消息格式设计:
命令关键字@#接收方@#消息内容@#发送方
1)连接:userName      ----握手的线程serverSocket专门接收该消息,其它的由服务器新开的与客户进行通讯的socket来接收
2)退出:[email protected]#全部@#[email protected]#userName
3)发送: on @# JList.getSelectedValue() @# tfdMsg.getText() @# tfdUserName.getText()
服务器向客户端发的消息格式设计:
命令关键字@#发送方@#消息内容
登录:
   1) [email protected]#server @# 用户[userName]登录了  (给客户端显示用的)
   2) [email protected]#server @# userName (给客户端维护在线用户列表用的)
退出:
   1) msg   @#server @# 用户[userName]退出了  (给客户端显示用的)
   2) [email protected]#server @# userName (给客户端维护在线用户列表用的)
发送:

   msg   @#消息发送者( msgs[3] ) @# 消息内容 (msgs[2])


//////////////////////////////////////////////////////////////////////////////////////////////////////////////

在服务器端,需要一个池来进行简单的客户存储,以下代码使用的是HashMap

代码如下:服务器的代码:

import java.awt.BorderLayout;

import java.awt.Dimension;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Scanner;
import java.util.Set;


import javax.swing.DefaultListModel;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.KeyStroke;
import javax.swing.border.TitledBorder;


public class ServerForm extends JFrame {
private final int PORT = 9090;


private JList list; // 在线用户列表--表现层
private DefaultListModel lm; // 在线用户列表--数据层
private Map<String, Socket> userMap = new HashMap<String, Socket>();
private JTextArea allMsg = new JTextArea(); // 消息显示主窗口


public ServerForm() {
setTitle("sina服务器");
setDefaultCloseOperation(EXIT_ON_CLOSE);


// 1东 在线用户列表
lm = new DefaultListModel();
lm.addElement("全部");
list = new JList(lm);
list.setVisibleRowCount(5);
JScrollPane jsc = new JScrollPane(list);
jsc.setBorder(new TitledBorder("在线"));
jsc.setPreferredSize(new Dimension(100, this.getHeight()));
getContentPane().add(jsc, BorderLayout.EAST);


// 2中 信息主窗口
allMsg.setEditable(false);
getContentPane().add(new JScrollPane(allMsg));


// 菜单
JMenuBar menubar = new JMenuBar();
setJMenuBar(menubar);
JMenu menu = new JMenu("控制(C)");
menubar.add(menu);
menu.setMnemonic('C'); // 设置菜单助记符
JMenuItem itemRun = new JMenuItem("开启");
itemRun.setActionCommand("run");
itemRun.setAccelerator(KeyStroke.getKeyStroke('R', KeyEvent.CTRL_MASK));// 设置快捷键:
// Ctrl+R
menu.add(itemRun);
menu.addSeparator();
JMenuItem itemExit = new JMenuItem("退出");
itemExit.setActionCommand("exit");
itemExit.setAccelerator(KeyStroke.getKeyStroke('E', KeyEvent.CTRL_MASK));// 设置快捷键:
// Ctrl+E
menu.add(itemExit);


// 监听器
ActionListener al = new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (e.getActionCommand().equals("run")) {
System.out.println("1111");
startServer();


} else if (e.getActionCommand().equals("exit")) {
System.exit(0);
}


}


};
itemRun.addActionListener(al);
itemExit.addActionListener(al);


int winWidth = 500;
int winHeight = 400;
Toolkit toolkit = Toolkit.getDefaultToolkit();
int width = (int) toolkit.getScreenSize().getWidth();
int height = (int) toolkit.getScreenSize().getHeight();
setBounds(width / 2 - winWidth / 2, height / 2 - winHeight / 2,
winWidth, winHeight);


setVisible(true);
}


private void startServer() {
try {
ServerSocket server = new ServerSocket(PORT);
allMsg.append("启动服务器:" + server);


new ServerThread(server).start();


} catch (IOException e) {
e.printStackTrace();
}


}


class ServerThread extends Thread {
private ServerSocket server;


public ServerThread(ServerSocket server) {
this.server = server;
}


@Override
public void run() {
while (true) {
try {
Socket clientServer = server.accept();// 阻塞
Scanner sc = new Scanner(clientServer.getInputStream());
// 读取客户端发来的userName:通知消息窗口和用户列表
if (sc.hasNext()) {
String userName = sc.nextLine();
// 消息窗口
allMsg.append("\r\n用户【" + userName + "】登录了");
// 用户列表
lm.addElement(userName);// 数据层
list.validate();// 表现层


// 添加到Map中
userMap.put(userName, clientServer);
new ClientThread(clientServer).start();


// 通知所有其他的线程本线程的上线消息
msgAll(userName);
// 通知自己已上线的其他线程信息
msgSelf(userName, clientServer);


}


} catch (IOException e) {
e.printStackTrace();
}


}
}


private void msgSelf(String userName, Socket clientServer) {
Iterator<String> it = userMap.keySet().iterator();
while (it.hasNext()) {
String name = it.next();
if (!name.equals(userName)) {


try {
PrintWriter pw = new PrintWriter(
clientServer.getOutputStream(), true);


String msg = "[email protected]#[email protected]#" + name;// [email protected]#server
// @#
// userName
// (给客户端维护在线用户列表用的)
pw.println(msg);
pw.flush();
} catch (IOException e) {
e.printStackTrace();
}


}
}
}


private void msgAll(String userName) {
Set<Entry<String, Socket>> set = userMap.entrySet();
Iterator<Entry<String, Socket>> it = set.iterator();
while (it.hasNext()) {
Entry<String, Socket> en = it.next();
String name = en.getKey();
Socket s = en.getValue();


try {
// 客户端内容显示的:
PrintWriter pw = new PrintWriter(s.getOutputStream(), true);
String msg = "[email protected]#[email protected]# 用户[" + userName + "]登录了 ";// [email protected]#server
// @#
// 用户[userName]登录了
// (给客户端显示用的)
pw.println(msg);
pw.flush();


// 客户端列表显示的:
if (!name.equals(userName)) {
msg = "[email protected]#[email protected]#" + userName;// [email protected]#server
// @# userName
// (给客户端维护在线用户列表用的)
pw.println(msg);
pw.flush();
}


} catch (IOException e) {
e.printStackTrace();
}


}


}
}


/**

* @author 服务器为接受到的每一个线程开的一个线程;

*/
class ClientThread extends Thread {
private Socket clientServer = null;


public ClientThread(Socket clientServer) {
this.clientServer = clientServer;
}


@Override
public void run() {


try {
Scanner sc = new Scanner(clientServer.getInputStream());
while (sc.hasNext()) {
String str = sc.nextLine();
String msgs[] = str.split("@#");


if (msgs.length != 4) {
JOptionPane.showMessageDialog(null, "消息中存在非法字符@#");
return;
}
// 发送: on @# JList.getSelectedValue() @# tfdMsg.getText() @#
// tfdUserName.getText()
if ("on".equals(msgs[0])) {
sendMsgToSb(msgs);
} else if ("exit".equals(msgs[0])) {// [email protected]#全部@#[email protected]#userName


allMsg.append("\r\n用户[" + msgs[3] + "]退出了...");
lm.removeElement(msgs[3]);
list.validate();


userMap.remove(msgs[3]);
sendExitMsgToAll(msgs);


}


}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}


}


private void sendExitMsgToAll(String[] msgs) {
Iterator<String> it = userMap.keySet().iterator();
while (it.hasNext()) {
String name = it.next();
Socket s = userMap.get(name);
PrintWriter pw;
try {
pw = new PrintWriter(s.getOutputStream(), true);
String str = "[email protected]#[email protected]#用户[" + msgs[3] + "退出了";// msg
// @#server
// @#
// 用户[userName]退出了
// (给客户端显示用的)


pw.println(str);
pw.flush();
String str2 = "[email protected]#[email protected]#" + msgs[3];// 2)
// [email protected]#server
// @#
// userName
// (给客户端维护在线用户列表用的)
pw.println(str2);
pw.flush();


} catch (IOException e) {
e.printStackTrace();
}
}


}


// 发送: on @# JList.getSelectedValue() @# tfdMsg.getText() @#
// tfdUserName.getText()
private void sendMsgToSb(String[] msgs) throws IOException {
if ("全部".equals(msgs[1])) {// 群聊
Iterator<String> it = userMap.keySet().iterator();
while (it.hasNext()) {
String name = it.next();
Socket s = userMap.get(name);
PrintWriter pw = new PrintWriter(s.getOutputStream(), true);
// msg @#消息发送者( msgs[3] ) @# 消息内容 (msgs[2])
String str = "[email protected]#" + msgs[3] + "@#" + msgs[2];


pw.println(str);
pw.flush();


}
} else {// 私聊
Socket s = userMap.get(msgs[1]);
PrintWriter pw = new PrintWriter(s.getOutputStream(), true);
String str = "[email protected]#" + msgs[3] + "@#" + msgs[2];
pw.println(str);
pw.flush();
}
}
}


public static void main(String[] args) {
JFrame.setDefaultLookAndFeelDecorated(true);
new ServerForm();
}


}


客户端的的代码:

package tcp.sina;


import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;


import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.border.TitledBorder;




public class ClientForm extends JFrame implements ActionListener {
private static int PORT = 9090;
private static String HOST = "127.0.0.1";
private JTextField tfdUserName; // 用户标识
private Socket clientSocket = null;
private PrintWriter pw = null;
private JList list; // 在线用户列表--表现层
private DefaultListModel lm; // 在线用户列表--数据层


private JTextArea allMsg = new JTextArea(); // 消息显示主窗口
private JTextField tfdMsg; // 发消息输入框


public ClientForm() {
setBounds(300, 300, 400, 300);
setDefaultCloseOperation(EXIT_ON_CLOSE);


// 1上部面板
JPanel p = new JPanel();
addJMenuBar();



p.add(new JLabel("用户标识:"));
tfdUserName = new JTextField(10);
p.add(tfdUserName);
JButton btnConn = new JButton("连接");
btnConn.setActionCommand("c");
btnConn.addActionListener(this);
p.add(btnConn);
JButton btnExit = new JButton("退出");
btnExit.setActionCommand("exit");
btnExit.addActionListener(this);
p.add(btnExit);


this.add(p, BorderLayout.NORTH); // 添加到上部


// 2中部面板
JPanel centerP = new JPanel();
centerP.setLayout(new BorderLayout());
// 2.1东 在线用户列表
lm = new DefaultListModel();
lm.addElement("全部");
list = new JList(lm);
list.setSelectedIndex(0);
list.setVisibleRowCount(2);
JScrollPane jsc = new JScrollPane(list);
jsc.setBorder(new TitledBorder("在线"));
jsc.setPreferredSize(new Dimension(70, centerP.getHeight()));
centerP.add(jsc, BorderLayout.EAST);


// 2.2中 聊天信息窗口
allMsg.setEditable(false);
centerP.add(new JScrollPane(allMsg));


// 2.3南 消息发送面板
JPanel sendP = new JPanel();
sendP.add(new JLabel("消息:"));
tfdMsg = new JTextField(20);
sendP.add(tfdMsg);
JButton btnSend = new JButton("发送");
btnSend.setActionCommand("send");
btnSend.addActionListener(this);
sendP.add(btnSend);


centerP.add(sendP, BorderLayout.SOUTH);


this.add(centerP); // 添加到中部
this.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
sendExitMsg();
}
});


setVisible(true);
}


private void addJMenuBar(){
JMenuBar menubar = new JMenuBar();
JMenu menu = new JMenu("选项");
JMenuItem miSet = new JMenuItem("设置");
JMenuItem miHelp = new JMenuItem("帮助");
miSet.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {

final JDialog setDlg = new JDialog(ClientForm.this);
setDlg.setBounds(ClientForm.this.getX()+10, ClientForm.this.getY(), 350, 100);
setDlg.setLayout(new FlowLayout());
final JTextField tfdHost = new JTextField(10);//ip
tfdHost.setText(HOST);
final JTextField tfdPort = new JTextField(5);//端口号
tfdPort.setText(""+PORT);
JButton btnSet = new JButton("设置");
setDlg.add(new JLabel("服务器:"));
setDlg.add(tfdHost);
setDlg.add(new JLabel(":"));
setDlg.add(tfdPort);
setDlg.add(btnSet);

//“设置”按钮的功能
btnSet.addActionListener( new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
//HOST,PORT
HOST = tfdHost.getText();
PORT = Integer.parseInt( tfdPort.getText() );
setDlg.dispose();//关闭且销毁“设置”窗口
}
} );

setDlg.setVisible(true);
}
});

miHelp.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
final JDialog helpDlg = new JDialog(ClientForm.this);
helpDlg.setBounds(ClientForm.this.getX()+10, ClientForm.this.getY(), 300, 100);
JLabel str = new JLabel("版权所有@城院.2018-01-01.QQ:666666");
helpDlg.add(str);
helpDlg.setVisible(true);
}
});

menu.add(miSet);
menu.add(miHelp);
menubar.add(menu);
this.setJMenuBar(menubar);
}


@Override
public void actionPerformed(ActionEvent e) {
if (e.getActionCommand().equals("c")) {// 连接
System.out.println("连接。。。。");
connecting();
}
if (e.getActionCommand().equals("send")) {
if (pw == null) {
JOptionPane.showMessageDialog(null, "请先连接服务器!");
return;


}


String msg = "[email protected]#" + list.getSelectedValue() + "@#"
+ tfdMsg.getText() + "@#" + tfdUserName.getText();
// on @# JList.getSelectedValue() @# tfdMsg.getText() @#tfdUserName.getText()
pw.println(msg);
pw.flush();
} else if (e.getActionCommand().equals("exit")) {
sendExitMsg();
}


}


private void sendExitMsg() {
if (pw == null) {
System.exit(0);


}
String str = "[email protected]#全部@#[email protected]#" + tfdUserName.getText();// [email protected]#全部@#[email protected]#userName
pw.println(str);
pw.flush();
System.exit(0);
}


private void connecting() {
try {
clientSocket = new Socket(HOST, PORT);
String userName = tfdUserName.getText();
pw = new PrintWriter(clientSocket.getOutputStream(), true);
pw.println(userName);
this.setTitle("用户:【" + userName + "】在线...");
tfdUserName.setEditable(false);


// 新开一个线程与服务器通讯
new ClientThread().start();


} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {


e.printStackTrace();
}


}


class ClientThread extends Thread {
private boolean isFirst = true;


@Override
// 接受服务器端的信息
public void run() {

try {
Scanner sc = new Scanner(clientSocket.getInputStream());
while (sc.hasNext()) {
String str = sc.nextLine();
String msgs[] = str.split("@#");
// 防护一下
if (msgs.length != 3) {
JOptionPane.showMessageDialog(null, "消息中存在非法标记@#");
return;
}


if ("msg".equals(msgs[0])) {// [email protected]#server @#
// 用户[userName]登录了 (给客户端显示用的)
if ("server".equals(msgs[1])) {
str = "系统通知:" + msgs[2];
if (isFirst) {
allMsg.append(str);
isFirst = false;
} else {
allMsg.append("\r\n" + str);
}
} else {// msg @#消息发送者( msgs[3] ) @# 消息内容 (msgs[2])
str = msgs[1] + "说:" + msgs[2];// msg @#消息发送者(
// msgs[3] ) @# 消息内容
// (msgs[2])
allMsg.append("\r\n" + str);
}


} else if ("cmdAdd".equals(msgs[0])) {// 2) [email protected]#server
// @# userName
// (给客户端维护在线用户列表用的)
lm.addElement(msgs[2]);
list.validate();
}else if("cmdRed".equals(msgs[0])){// [email protected]#server @# userName (给客户端维护在线用户列表用的)lm.removeElement(msgs[2]);list.validate();}}} catch (IOException e) {e.printStackTrace();}}}public static void main(String[] args) {JFrame.setDefaultLookAndFeelDecorated(true);new ClientForm();}}