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 --- 악수하는 스레드 서버 소켓은 이 메시지를 전문적으로 수신하고, 다른 서버가 새로 개설한 고객과 통신하는 소켓은 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
"+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<String, Socket> map=new HashMap<String, Socket>();
	
	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
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
user["+msgs[3]+"] leaves."); } } } catch (IOException e) { e.printStackTrace(); } } } private void sendExitMsgToAll(String msgs[]) throws IOException { Iterator<String> 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<String> 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<Socket> 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<String> 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 주소 및 포트 오류로 로그인할 수 없음

좋은 웹페이지 즐겨찾기