Socket编程------模拟QQ聊天(TCP)

模拟QQ聊天

一、要求

1、一个服务器可以与多个用户同时通讯

2、用户可以通过服务器与用户之间通讯

3、用户可以选择和所有人发消息,也可以选择和某个用户单独发消息

4、服务器要显示当前所有在线人员

5、用户要显示当前在线的人员

6、当有新用户登录时或在线用户退出时,服务器要向所有其他在线用户发送提示信息,并且服务器也要显示相应的提示信息

7、不能有相同的用户名同时登陆

8、不能发送空消息

9、客户端可以设置连接的服务器IP和端口


二、QQ聊天协议

在服务器端 用一个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();
	}
}


本程序登录时需要先启动服务器,然后再输入用户名才可以登录,聊天过程中可以群聊也可以单聊,单聊只要点击需要单聊的人的名字即可,群聊即点击列表中的“ALL”;还可以在菜单设置IP地址和端口号。

本程序修复好的漏洞:

没有输入用户名应不可登录

登陆后应不可再次点击登录按钮

没有登录应不可发送消息且发送的消息不可为空

IP地址和端口错误也不可登录




你可能感兴趣的:(Java学习笔记)