模拟QQ聊天
1、一个服务器可以与多个用户同时通讯
2、用户可以通过服务器与用户之间通讯
3、用户可以选择和所有人发消息,也可以选择和某个用户单独发消息
4、服务器要显示当前所有在线人员
5、用户要显示当前在线的人员
6、当有新用户登录时或在线用户退出时,服务器要向所有其他在线用户发送提示信息,并且服务器也要显示相应的提示信息
7、不能有相同的用户名同时登陆
8、不能发送空消息
9、客户端可以设置连接的服务器IP和端口
在服务器端 用一个HashMap
客户端的动作:
(1)连接(登录):发送userName 服务器的对应动作:1)界面显示,2)通知其他用户关于你登录的信息, 3)把其他在线用户的userName通知当前用户 4)开启一个线程专门为当前线程服务
(2)退出(注销):
(3)发送消息
※※发送通讯内容之后,对方如何知道是干什么,通过消息协议来实现:
客户端向服务器发的消息格式设计:
命令关键字@#接收方@#消息内容@#发送方
1)连接:userName ----握手的线程serverSocket专门接收该消息,其它的由服务器新开的与客户进行通讯的socket来接收
2)退出:exit@#全部@#null@#userName
3)发送: on @# JList.getSelectedValue() @# tfdMsg.getText() @# tfdUserName.getText()
服务器向客户端发的消息格式设计:
命令关键字@#发送方@#消息内容
登录:
1) msg @#server @# 用户[userName]登录了 (给客户端显示用的)
2) cmdAdd@#server @# userName (给客户端维护在线用户列表用的)
退出:
1) msg @#server @# 用户[userName]退出了 (给客户端显示用的)
2) cmdRed@#server @# userName (给客户端维护在线用户列表用的)
发送:
msg @#消息发送者( msgs[3] ) @# 消息内容 (msgs[2])
三、代码
ClientForm.java
package cn.hncu;
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.io.IOException;
import java.io.PrintWriter;
import java.net.Socket;
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.ListSelectionModel;
import javax.swing.border.TitledBorder;
public class ClientForm extends JFrame implements ActionListener{
private JTextField tfdUserName=null;
private JTextArea allMsg=null;
private DefaultListModel dlm=null;
private JList list=null;
private JTextField tfdMsg=null;
private static PrintWriter pw;
private static String HOST="192.168.1.106";
private static int PORT=9090;
private static Socket clientSocket=null;
public ClientForm(){
//menu
addMenu();
//panel above
JPanel p1=new JPanel();
p1.add(new JLabel("userLogo"));
tfdUserName=new JTextField(10);
p1.add(tfdUserName);
JButton connect=new JButton("connect");
connect.setActionCommand("connect");
connect.addActionListener(this);
JButton exit=new JButton("exit");
exit.setActionCommand("exit");
exit.addActionListener(this);
p1.add(connect);
p1.add(exit);
this.getContentPane().add(p1,BorderLayout.NORTH);
//middle panel
JPanel p2=new JPanel(new BorderLayout());
this.getContentPane().add(p2,BorderLayout.CENTER);
//online list widget
dlm=new DefaultListModel();
dlm.addElement("ALL");
list=new JList(dlm);
list.setSelectedIndex(0);
list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
list.setVisibleRowCount(2);
JScrollPane js=new JScrollPane(list);
js.setBorder(new TitledBorder("Online"));
js.setPreferredSize(new Dimension(70, p2.getHeight()));
p2.add(js,BorderLayout.EAST);
//chatting message area
allMsg=new JTextArea();
allMsg.setEditable(false);
p2.add(new JScrollPane(allMsg),BorderLayout.CENTER);
//message sending panel
JPanel p3=new JPanel();
JLabel messageLabel=new JLabel("message");
p3.add(messageLabel);
tfdMsg=new JTextField(20);
p3.add(tfdMsg);
JButton sendBtn=new JButton("send");
sendBtn.setActionCommand("send");
sendBtn.addActionListener(this);
p3.add(sendBtn);
this.getContentPane().add(p3,BorderLayout.SOUTH);
this.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
sendExitMsg();
}
});
this.setBounds(300, 300, 400, 300);
this.setVisible(true);
}
private void addMenu() {
JMenuBar menubar=new JMenuBar();
this.setJMenuBar(menubar);
JMenu menu=new JMenu("Selection");
menubar.add(menu);
JMenuItem itemSet=new JMenuItem("Set");
JMenuItem itemHelp=new JMenuItem("Help");
menu.add(itemSet);
menu.add(itemHelp);
itemSet.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
final JDialog dlg=new JDialog(ClientForm.this);
dlg.setLayout(new FlowLayout());
dlg.setBounds(ClientForm.this.getX()+40, ClientForm.this.getY()+40, 300, 100);
dlg.add(new JLabel("server"));
final JTextField tfdId=new JTextField(HOST);
final JTextField tfdPort=new JTextField(""+PORT);
dlg.add(tfdId);
dlg.add(new JLabel(":"));
dlg.add(tfdPort);
JButton btnSet=new JButton("Set");
dlg.add(btnSet);
btnSet.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
HOST=tfdId.getText();
PORT=Integer.parseInt(tfdPort.getText());
dlg.dispose();
}
});
dlg.setVisible(true);
}
});
itemHelp.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
JDialog dlg=new JDialog(ClientForm.this);
dlg.setLayout(new FlowLayout());
dlg.setBounds(ClientForm.this.getX()+40, ClientForm.this.getY()+40, 300, 100);
dlg.add(new JLabel("[email protected],QQ:666666"));
dlg.setVisible(true);
}
});
}
@Override
public void actionPerformed(ActionEvent e) {
if (e.getActionCommand().equalsIgnoreCase("connect")){
System.out.println("connecting server");
String userName=tfdUserName.getText().trim();
byte byteName[]=userName.getBytes();
if (byteName.length==0){
JOptionPane.showMessageDialog(this, "please input right name format");
return;
} else {
try {
connecting(userName);
} catch (Exception e1) {
JOptionPane.showMessageDialog(this, "Failed in connecting,please check your internet or ip address and port");
return;
}
((JButton)e.getSource()).setEnabled(false);
tfdUserName.setEditable(false);
}
}
if (e.getActionCommand().equalsIgnoreCase("send")){
if (tfdMsg.getText()==null){
JOptionPane.showMessageDialog(this, "Please input dialog message");
return;
}
String str="on@#"+list.getSelectedValue()+"@#"+tfdMsg.getText()+"@#"+tfdUserName.getText();
try {
pw.println(str);
pw.flush();
} catch (Exception e1) {
JOptionPane.showMessageDialog(this, "Please connect the server first.");
return;
}
tfdMsg.setText("");
}
if (e.getActionCommand().equalsIgnoreCase("exit")){
sendExitMsg();
}
}
private void sendExitMsg() {
if (clientSocket==null){
System.exit(0);
}
String str="exit@#all@#null@#"+tfdUserName.getText();
pw.println(str);
pw.flush();
System.exit(0);
}
private void connecting(String userName) throws Exception{
clientSocket=new Socket(HOST,PORT);
if (userName!=""){
pw=new PrintWriter(clientSocket.getOutputStream(),true);
pw.println(userName);
this.setTitle("User ["+userName+"] is online...");
new ClientThread().start();
}
}
class ClientThread extends Thread{
@Override
public void run() {
try {
Scanner input=new Scanner(clientSocket.getInputStream());
while (input.hasNextLine()){
String str=input.nextLine();
String msgs[]=str.split("@#");
if ("msg".equals(msgs[0])){
if ("server".equals(msgs[1])){
str="[notify]:"+msgs[2];
} else{
str="["+msgs[1]+"] says:"+msgs[2];
}
allMsg.append("\r\n"+str);
} else if ("cmdAdd".equals(msgs[0])){
dlm.addElement(msgs[2]);
} else if ("cmdRed".equalsIgnoreCase(msgs[0])){
dlm.removeElement(msgs[2]);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
JFrame.setDefaultLookAndFeelDecorated(true);
new ClientForm();
}
}
ServerForm.java
package cn.hncu;
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.Scanner;
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 static final int PORT=9090;
private JTextArea area;
private DefaultListModel dlm;
private Map map=new HashMap();
public ServerForm() {
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
final int winWidth=(int) Toolkit.getDefaultToolkit().getScreenSize().getWidth();
final int winHeight=(int) Toolkit.getDefaultToolkit().getScreenSize().getHeight();
//message window
area=new JTextArea();
area.setEditable(false);
this.getContentPane().add(new JScrollPane(area),BorderLayout.CENTER);
//online list widget
dlm=new DefaultListModel();
JList list=new JList(dlm);
JScrollPane js=new JScrollPane(list);
js.setBorder(new TitledBorder("Online"));
js.setPreferredSize(new Dimension(100, this.getHeight()));
this.getContentPane().add(js,BorderLayout.EAST);
//menu
JMenuBar bar=new JMenuBar();
this.setJMenuBar(bar);
JMenu menu=new JMenu("Control(C)");
bar.add(menu);
menu.setMnemonic('C');
final JMenuItem run=new JMenuItem("run");
run.setActionCommand("run");
run.setAccelerator(KeyStroke.getKeyStroke('R',KeyEvent.CTRL_MASK));
menu.add(run);
menu.addSeparator();
final JMenuItem exit=new JMenuItem("exit");
exit.setActionCommand("exit");
exit.setAccelerator(KeyStroke.getKeyStroke('E',KeyEvent.CTRL_MASK));
menu.add(exit);
ActionListener al=new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
if (e.getActionCommand().equalsIgnoreCase("run")){
startServer();
run.setEnabled(false);
} else {
System.exit(0);
}
}
};
run.addActionListener(al);
exit.addActionListener(al);
this.setBounds(winWidth/4, winHeight/4, winWidth/2, winHeight/2);
this.setVisible(true);
}
private void startServer() {
try {
ServerSocket server=new ServerSocket(PORT);
area.append("start service:"+server);
new ServerThread(server).start();
} catch (IOException e) {
e.printStackTrace();
}
}
class ServerThread extends Thread{
private ServerSocket server=null;
ServerThread(ServerSocket server){
this.server=server;
}
@Override
public void run() {
try {
while (true){
Socket clientSocket=server.accept();
Scanner input=new Scanner(clientSocket.getInputStream());
if (input.hasNextLine()){
String userName=input.nextLine();
area.append("\r\n user:["+userName+"] login,"+clientSocket);
dlm.addElement(userName);
new ClientThread(clientSocket).start();
msgAll(userName);
msgSelf(clientSocket);
map.put(userName, clientSocket);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
class ClientThread extends Thread{
private Socket clientSocket=null;
public ClientThread(Socket clientSocket) {
this.clientSocket = clientSocket;
}
@Override
public void run() {
try {
Scanner input=new Scanner(clientSocket.getInputStream());
while (input.hasNextLine()){
String str=input.nextLine();
String msgs[]=str.split("@#");
if (msgs.length!=4){
JOptionPane.showMessageDialog(null, "wrong message format");
}
if ("on".equalsIgnoreCase(msgs[0])){
sendMsgToSb(msgs);
}
if ("exit".equalsIgnoreCase(msgs[0])){
map.remove(msgs[3]);
dlm.removeElement(msgs[3]);
sendExitMsgToAll(msgs);
area.append("\r\n user["+msgs[3]+"] leaves.");
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void sendExitMsgToAll(String msgs[]) throws IOException {
Iterator it=map.keySet().iterator();
while (it.hasNext()){
String userName=it.next();
Socket s=map.get(userName);
PrintWriter pw=new PrintWriter(s.getOutputStream(), true);
String msg="msg@#server@#user ["+msgs[3]+"] leaves";
pw.println(msg);
msg="cmdRed@#server@#"+msgs[3];
pw.println(msg);
pw.flush();
}
}
private void sendMsgToSb(String[] msgs) throws IOException {
if ("all".equalsIgnoreCase(msgs[1])){
Iterator userNames=map.keySet().iterator();
while (userNames.hasNext()){
String userName=userNames.next();
Socket s=map.get(userName);
String msg="msg@#"+msgs[3]+"@#"+msgs[2];
PrintWriter pw=new PrintWriter(s.getOutputStream(), true);
pw.println(msg);
pw.flush();
}
} else {
String userName=msgs[1];
Socket s=map.get(userName);
String msg="msg@#"+msgs[3]+"@#"+msgs[2];
PrintWriter pw=new PrintWriter(s.getOutputStream(), true);
pw.println(msg);
pw.flush();
}
}
private void msgAll(String userName) {
Iterator it = map.values().iterator();
while(it.hasNext()){
Socket s = it.next();
try {
PrintWriter pw = new PrintWriter(s.getOutputStream(), true);
String msg = "msg@#server@#用户["+userName+"]登录了.";
pw.println(msg);
pw.flush();
msg = "cmdAdd@#server@#"+userName;
pw.println(msg);
pw.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void msgSelf(Socket clientSocket) {
try {
PrintWriter pw=new PrintWriter(clientSocket.getOutputStream(),true);
Iterator it=map.keySet().iterator();
while (it.hasNext()){
String msg="cmdAdd@#server@#"+it.next();
pw.println(msg);
pw.flush();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
JFrame.setDefaultLookAndFeelDecorated(true);
new ServerForm();
}
}
本程序修复好的漏洞:
没有输入用户名应不可登录
登陆后应不可再次点击登录按钮
没有登录应不可发送消息且发送的消息不可为空
IP地址和端口错误也不可登录