본문 바로가기

programming/Gukbi

국비 교육 31일차 - 네트워크를 이용한 채팅 프로그램

728x90
반응형

이라고 제목에 적긴 했지만 솔직히 이해 한게 10%도 안될 것 같다. 강사님도 그냥 이런게 있다고만 생각하고 넘어가라고 하셨다. 

 

package com.sist.client;
// 윈도우
import javax.swing.*;
import javax.swing.text.Document;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyleContext;

import com.sist.common.Function;

import java.awt.*;
import java.awt.event.*;
/*
 *    오라클 : 데이터 수집해서 저장하는 장소 
 *    자바 : 오라클 연결 => 데이터를 브라우저에 전송 
 *    자바스크립트 : 브라우저에서 이벤트 처리(버튼,검색,애니메이션)
 *    HTML:화면 출력 
 *          \n => <br>
 *    CSS: 화면 디자인 (Layout)
 *    
 *    ==> JSP
 */
// 네트워크 관련 클래스 : Socket , BufferedReader , OutputStream , StringTokenizer
import java.net.*;
import java.util.*;
import java.io.*;
/*
 *    class 유형 
 *    = 단일 상속  ==> 키워드(class)
 *    = 다중 상속  ==> 키워드(interface)
 *         상속 
 *    class ==> class
 *        extends
 *    interface ===> interfece
 *          extends 
 *    interfece ====> class
 *           implements 
 *           
 *    인터페이스 여러개 ==> A,B,C
 *    ========
 *     주로 => 관련된 여러개의 클래스를 한개의 이름으로 제어 
 */
public class MovieMainFrame extends JFrame implements ActionListener,Runnable{
	
	// Menu
	JMenuItem home=new JMenuItem("홈");
	JMenuItem rmovie=new JMenuItem("현재상영영화");
	JMenuItem smovie=new JMenuItem("개봉예정영화");
	
	JMenuItem wbox=new JMenuItem("주간");
	JMenuItem mbox=new JMenuItem("월간");
	JMenuItem ybox=new JMenuItem("연간");
	
	JMenuItem chat=new JMenuItem("채팅");
	
	CardLayout card=new CardLayout();
	// 화면 UI
	MovieHomeForm mhf=new MovieHomeForm();
	ChatForm cf=new ChatForm();
	// Login
	Login login=new Login();
	// 쪽지보내기
	SendMessage sm=new SendMessage();
	RecvMessage rm=new RecvMessage();
	// 네트워크 
	Socket s;
	BufferedReader in;
	OutputStream out;
	String name;
    public MovieMainFrame()
    {
    	setLayout(card);
    	JMenuBar bar=new JMenuBar();
    	JMenu menu1=new JMenu("홈");
    	menu1.add(home);
    	
    	JMenu menu2=new JMenu("영화");
    	menu2.add(rmovie);
    	menu2.add(smovie);
    	
    	JMenu menu3=new JMenu("박스오피스");
    	menu3.add(wbox);
    	menu3.add(mbox);
    	menu3.add(ybox);
    	
    	JMenu menu4=new JMenu("네트워크");
    	menu4.add(chat);
    	
    	bar.add(menu1);
    	bar.add(menu2);
    	bar.add(menu3);
    	bar.add(menu4);
    	// 윈도우 추가
    	setJMenuBar(bar);
    	
    	// 화면 추가
    	add("CHAT",cf);
    	add("HOME",mhf);
    	
    	
    	setSize(1024, 768);
    	//setVisible(true);
    	setResizable(false);
    	setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
    	
    	home.addActionListener(this);
    	chat.addActionListener(this);
    	
    	// 로그인 
    	login.b1.addActionListener(this);
    	login.b2.addActionListener(this);
    	
    	// 채팅 
    	cf.tf.addActionListener(this);//Enter
    	// 쪽지보내기 
    	cf.b1.addActionListener(this);
    	sm.b1.addActionListener(this);
    	sm.b2.addActionListener(this);
    	rm.b1.addActionListener(this);
    	rm.b2.addActionListener(this);
    	// 로또 (멀티쓰레드)
    	// 나가기
        cf.b3.addActionListener(this);
        
		/*
		 * int temp=cf.model.getRowCount(); if(temp>1) { cf.b1.setEnabled(true); } else
		 * { cf.b1.setEnabled(false); }
		 */
    }
    // 서버 연결 => 호출시기 (로그인 버튼 클릭시)
    public void connection(String id,String name,String sex)
    {
    	try
    	{
    		// 서버 연결 
    		s=new Socket("localhost",3355);
    		// s=> 서버정보 
    		// 서버가 보내준 데이터를 읽을 위치 확인  => in
    		in=new BufferedReader(new InputStreamReader(s.getInputStream()));
    		// 서버로 보내는 위치 확인 => out 
    		out=s.getOutputStream();
    		
    		// 로그인 요청 
    		out.write((Function.LOGIN+"|"+id+"|"+name+"|"+sex+"\n").getBytes());
    	}catch(Exception ex){}
    	// 서버로부터 값을 읽어 와라 => 실시간으로 읽는다 => 쓰레드 
    	new Thread(this).start(); //run호출 
    	/*
    	 *   쓰레드 사용법 
    	 *    1) 상속 => 멀티쓰레드 
    	 *       class A extends Thread
    	 *    2) 구현 => 싱글쓰레드 
    	 *       class A implements Runnable
    	 *    *** 자바는 단일 상속 
    	 */
    }
    // 서버로부터 응답을 받아서 출력하는 기능 
    /*
     *    switch()
     *    {
     *       case 1:
     *       {
     *         String name="";
     *       }
     *         break;
     *       case 2:
     *          String name="";
     *          break;
     *    }
     */
    public void run()
    {
    	try
    	{
    		while(true)
    		{
    			// 1. 서버에서 응답한 데이터를 받는다
    			String msg=in.readLine();
    			StringTokenizer st=new StringTokenizer(msg,"|");
    			// split=> 정규식  "\\|"
    			// Function.LOGIN+"|"+id+"|"+name+"|"+sex => 테이블에 출력 
    			// Function.MYLOG => 윈도우 변경 (로그인=>채팅창)
    			// Function.CHAT => ta=> 출력 
    			int protocol=Integer.parseInt(st.nextToken());
    			switch(protocol)
    			{
    			  case Function.LOGIN:
    			  {
    				  // 테이블에 출력 
    				  String[] data= {
    					st.nextToken(), // id
    					st.nextToken(), // name
    					st.nextToken()  // sex
    				  };
    				  cf.model.addRow(data);
    			  }
    			  break;
    			  case Function.MYLOG:
    			  {
    				  name=st.nextToken();
    				  setTitle(name);
    				  login.setVisible(false);//로그인창 종료
    				  setVisible(true);// 채팅창 
    			  }
    			  break;
    			  case Function.CHAT:
    			  {
    				  initStyle();
    				  append(st.nextToken(),st.nextToken());
    			  }
    			  break;
    			  case Function.EXIT:
    			  {
    				  String id=st.nextToken();
    				  String str="";
    				  for(int i=0;i<cf.model.getRowCount();i++)
    				  {
    					  str=cf.model.getValueAt(i, 0).toString();
    					  if(id.equals(str))
    					  {
    						  cf.model.removeRow(i);
    						  break;
    					  }
    				  }
    			  }
    			  break;
    			  case Function.MYEXIT:
    			  {
    				  System.exit(0);
    			  }
    			  break;
    			  case Function.SENDMESSAGE:
    			  {
    				  String youId=st.nextToken();
    				  String strMsg=st.nextToken();
    				  rm.tf.setText(youId);
    				  rm.ta.setText(strMsg.replace("\t", "\n"));
    				  rm.setVisible(true);
    			  }
    			  break;
    			}
    			
    		}
    	}catch(Exception ex){}
    }
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		try
		{
		  UIManager.setLookAndFeel("com.jtattoo.plaf.mcwin.McWinLookAndFeel");
		}catch(Exception ex) {}
		
		 new MovieMainFrame();
	}
	@Override
	public void actionPerformed(ActionEvent e) {
		// TODO Auto-generated method stub
		if(e.getSource()==home)
		{
			card.show(getContentPane(), "HOME");
		}
		else if(e.getSource()==chat)
		{
			card.show(getContentPane(), "CHAT");
		}
		else if(e.getSource()==login.b1)
		{
			//1. id를 읽는다 
			String id=login.tf1.getText();
			if(id.length()<1)
			{
				login.tf1.requestFocus();
				return;
			}
			String name=login.tf2.getText();
			if(name.length()<1)
			{
				login.tf2.requestFocus();
				return;
			}
			
			String sex="";
			if(login.rb1.isSelected())
				sex="남자";
			else
				sex="여자";
			
			connection(id, name, sex);//로그인 요청
		}
		else if(e.getSource()==login.b2)
		{
			System.exit(0);
		}
		else if(e.getSource()==cf.tf)
		{
			//1. 입력한 채팅문자열 읽기 
			String msg=cf.tf.getText();
			if(msg.length()<1)
				return;
			
			String color=cf.box.getSelectedItem().toString();
			// Object => String형변경 toString()
			// (String)
			try
			{
				out.write((Function.CHAT+"|"+msg+"|"+color+"\n").getBytes());// 서버로 전송 
				
			}catch(Exception ex) {}
			cf.tf.setText("");
			cf.box.setSelectedIndex(0);
		}
		else if(e.getSource()==cf.b3)// 나가기 버튼 
		{
			try
			{
				out.write((Function.EXIT+"|\n").getBytes());// 서버로 전송 (서버에서 처리)
				// readLine() => 패킷을 받을때 \n
				// 클라이언트 ===>                   서버  ====> 클라이언트 
				//       요청(Enter,버튼클릭,메뉴클릭)  요청 처리(결과값)   결과값을 출력 
			}catch(Exception ex){}
		}
		else if(e.getSource()==cf.b1)
		{
			sm.tf.removeAllItems();
			String str="";
			for(int i=0;i<cf.model.getRowCount();i++)
			{
				String id=cf.model.getValueAt(i, 0).toString();
				String yname=cf.model.getValueAt(i, 1).toString();
				if(!name.equals(yname))
				{
					sm.tf.addItem(id);
				}
			}
			sm.setVisible(true);
		}
		
		else if(e.getSource()==sm.b1)
		{
			// 서버로 전송 
			String strMsg=sm.ta.getText();
			String youId=sm.tf.getSelectedItem().toString();
			try
			{
				out.write((Function.SENDMESSAGE+"|"+strMsg.replace("\n", "\t")+"|"+youId+"\n").getBytes());
			}catch(Exception ex) {}
			sm.setVisible(false);
		}
		else if(e.getSource()==sm.b2)
		{
			sm.setVisible(false);
		}
		else if(e.getSource()==rm.b1)
		{
			sm.ta.setText("");
			sm.tf.removeAllItems();
			String str="";
			for(int i=0;i<cf.model.getRowCount();i++)
			{
				String id=cf.model.getValueAt(i, 0).toString();
				String yname=cf.model.getValueAt(i, 1).toString();
				if(!name.equals(yname))
				{
					sm.tf.addItem(id);
				}
			}
			sm.setVisible(true);
			rm.setVisible(false);
			
		}
		else if(e.getSource()==rm.b2)
		{
			rm.setVisible(false);
		}
	}
	
	public void append(String msg,String color)
	{
		try
		{
			Document doc=cf.pane.getDocument();
			doc.insertString(doc.getLength(), msg+"\n", cf.pane.getStyle(color));
			// 문자열 결합 
		}catch(Exception ex){}
	}
	public void initStyle()
	{
		Style def=StyleContext.getDefaultStyleContext().getStyle(StyleContext.DEFAULT_STYLE);
		
		Style red=cf.pane.addStyle("red", def);
		StyleConstants.setForeground(red, Color.red);
		
		Style yellow=cf.pane.addStyle("yellow", def);
		StyleConstants.setForeground(yellow, Color.yellow);
		
		Style blue=cf.pane.addStyle("blue", def);
		StyleConstants.setForeground(blue, Color.blue);
		
		Style cyan=cf.pane.addStyle("cyan", def);
		StyleConstants.setForeground(cyan, Color.cyan);
		
		Style green=cf.pane.addStyle("green", def);
		StyleConstants.setForeground(green, Color.green);
	}

}

채팅 프로그램을 만들기 위해서는 먼저 서버에 연결을 해야한다. 

그래서 맨 처음에 connection 메소드를 이용해서 서버를 연결하고, 로그인 요청을 해줬다. 

Socket을 사용해서 서버에 먼저 연결하고, BufferedReader를 사용해서 데이터를 읽을 위치를 확인해준다. 

그리고 읽어온 정보를 다시 서버로 보내야하기 때문에 getOutputStream을 이용해서 서버로 보내는 위치를 확인해준다. 

 

 

package com.sist.server;
/*
 *     서버
 *     class Server
 *     {
 *         접속자를 저장 (변수) => Server , Client => 공유
 *         ==> 저장 
 *         class Client
 *         {
 *            ==> 퇴장 (제거) ==> 여러개 데이터 , 메소드를 동시에 공유 (쓰레드)
 *         }
 *     }
 *     
 *     공유 : 내부 클래스 , static (저장 공간 1)
 *     
 *     class Server
 *     {
 *         static Vector waitvc=new Vector();
 *     }
 *     class Client
 *     {
 *     }
 *     
 *     class DAO
 *     {
 *        Connection conn;
 *        Statement stmt;
 *     }
 */
import java.net.*;
import java.io.*;
import java.util.*;

import com.sist.common.Function;
// LOGIN => id,대화명,성별  ==> 로그인 버튼 클릭 => 100|id|대화명|성별 ==> Server (StringTokenizer)
public class Server implements Runnable{
    // 접속을 담당 
	private ServerSocket ss;
	private Vector<Client> waitVc=new Vector<Client>();// 접속자 정보 저장 (동기화)
	public Server()
	{
		try
		{
			ss=new ServerSocket(3355); // 접속자가 50명 , ServerSocket(3355,1000)
			// ss.bind("localhost",3355) => 개통
			// ip(전화번호), port(연결선)
			// 대기 => listen() 
			/*
			 *    class ServerSocket
			 *    {
			 *        public ServerSocket(int port)
			 *        {
			 *            this.bind("localhost",port);
			 *            this.listen();
			 *        }
			 *    }
			 */
			System.out.println("Server Start...");
		}catch(Exception ex){}
	}
	// 클라이언트가 접속시마다 처리 
	/*
	 *    클라이언트 접속을 하면 => 클라이언트 정보(IP,PORT=>SCOKET) 받아서 쓰레드에 전송 
	 *                                                           ======
	 *                                                           전송받은 클라이언트와 통신 
	 *                      => 정보를 저장 
	 *                      
	 *    while(true)
	 *    {
	 *        ss=getClient();
	 *        System.out.println(ss);
	 *    }
	 */
	public void run()
	{
		// 접속시 처리 
		try
		{
			while(true) // 서버 종료시까지 접속을 받는다 
			{
				Socket s=ss.accept(); // 멈춤 (특수한 메소드) => 조건 (클라이언트가 접속시에만 호출이 가능) => 대기
				// 접속 (핸드폰 => 발신자정보(이름,전화번호)) => IP,PORT(IP+PORT=Socket)
				// 한명이 여러개 실행 => IP동일 ,PORT 다르다 
				// 각자 통신 => 쓰레드를 생성 
				// ======= 쓰레드가 IP를 가지고 있어야 한다 
				Client client=new Client(s);
				// 통신을 시작한다 
				client.start();
				// 저장을 하지 않는다 
				/*
				 *   통신 기능  
				 *     1. 로그인 처리 
				 *     2. 채팅 처리
				 *     3. 쪽지 보내기 
				 *     4. 나가기 
				 */
			}
		}catch(Exception ex){}
	}
	public static void main(String[] args) {
		// TODO Auto-generated method stub
        Server server=new Server();
        new Thread(server).start();
	}
	
	// 통신만 담당하는 클래스 => 접속자마다 따로 생성 => 여러개가 동시에 동작 (쓰레드)
	class Client extends Thread
	{
		// 클라이언트의 모든 정보를 가지고 있다 
		// ip,port
		Socket s;
		// 클라이언트와 입출력 (저장 공간 확인)
		OutputStream out;
		BufferedReader in;
		// 일반 변수 
		String id,name,sex;
		// 소켓을 받아 온다 
		// Client client=new Client(s);
		public Client(Socket s)
		{
			try
			{
				this.s=s; // 나가기 => Socket을 닫는다  s.close()
				// 위치 확인 => 클라이언트가 데이터를 읽어가는 위치 ==> out
				out=s.getOutputStream();
				//            클라이언트로부터 데이터를 읽어오는 위치 ==> in
				in=new BufferedReader(new InputStreamReader(s.getInputStream()));
				// 서버 => 클라이언트 
				// 클라이언트 => 서버
			}catch(Exception ex) {}
		}
		// 통신 처리 
		public void run()
		{
			try
			{
				while(true)
				{
					// 클라이언트로부터 요청값 받기 
					String msg=in.readLine(); // 100|id|name|sex
					System.out.println("Client=>"+msg);// 서버 디버깅 
					StringTokenizer st=new StringTokenizer(msg,"|");
					int protocol=Integer.parseInt(st.nextToken());
					switch(protocol)
					{
					// 로그인 처리   id|name|sex
					   case Function.LOGIN://100
					   {
						   // 변수값 저장 
						   id=st.nextToken();
						   name=st.nextToken();
						   sex=st.nextToken();
						   
						   // 현재 접속중인 사람에게 전송 
						   messageAll(Function.LOGIN+"|"+id+"|"+name+"|"+sex);
						   
						   // waitVc에 저장
						   waitVc.add(this);
						   
						   // 현재 접속중인 사람들의 정보 읽어온다 (접속한 사람에게 보낸다)
						   messageTo(Function.MYLOG+"|"+name);
						   for(Client client:waitVc)
						   {
							   messageTo(Function.LOGIN+"|"+client.id+"|"+client.name+"|"+client.sex);
						   }
						   messageAll(Function.CHAT+"|[알림 ☞] "+name+"님이 입장하셨습니다|red");
						   
					   }
					   break;
					   case Function.SENDMESSAGE://200  200|sid|rid|messge
					   {
						   String strMsg=st.nextToken();
						   String youId=st.nextToken();
						   
						   for(Client client:waitVc)
						   {
							   if(youId.equals(client.id))
							   {
								   client.messageTo(Function.SENDMESSAGE+"|"+id+"|"+strMsg);
								   break;
							   }
						   }
					   }
					   break;
					   case Function.CHAT://300  300|message
					   {
						  String strMsg=st.nextToken();
						  String color=st.nextToken();
						  
						  // 접속한 모든 사람에게 보낸다 
						  messageAll(Function.CHAT+"|["+name+"]"+strMsg+"|"+color);
					   }
					   break;
					   case Function.EXIT:
					   {
						   // 1. 접속된 모든 사람에게 나간다는 메세지 전송 
						   for(Client client:waitVc)// 접속한 사람의 정보 : waitVc
						   {
							   // id => 나가는 사람 => this
							   if(!id.equals(client.id))
							   {
								   client.messageTo(Function.CHAT+"|[알림 ☞] "+name+"님이 퇴장하셨습니다|red");
								   client.messageTo(Function.EXIT+"|"+id);//테이블에 출력된 명단에 제거
							   }
						   }
						   // => 남아 있는 사람 , 나가는 사람 처리 
						   for(int i=0;i<waitVc.size();i++)
						   {
							   Client client=waitVc.get(i);
							   if(id.equals(client.id))
							   {
								   // 윈도우 종료해라 
								   messageTo(Function.MYEXIT+"|");
								   // waitVc에서 제거 
								   waitVc.remove(i);
								   // in,out.close()
								   in.close();
								   out.close();
								   break;// 종료 
							   }
						   }
					   }
					   break;
					}
					
				}
			}catch(Exception ex){}
		}
		// 동일한 기능 
		// 한사람에게 전송
		public void messageTo(String msg)
		{
			try
			{
				out.write((msg+"\n").getBytes());
			}catch(Exception ex){}
		}
		// 접속자 모두에게 보내는 기능 
		public void messageAll(String msg)
		{
			try
			{
				for(Client client:waitVc)
				{
					client.messageTo(msg);
				}
			}catch(Exception ex){}
		}
	}

}
/*
 *     서버 프로그램
 *       = 접속시 처리 (클래스) : 교환소켓 (메니저) => 클라이언트 정보(ip,port) 저장 
 *       = 각 클라이언트마다 통신을 담당 (쓰레드) => 저장된 클라이언트 정보를 사용
 *         ==========
 *          여러개를 동시에 실행 
 *          *** 두개이상의 클래스에서 같은 데이터를 공유할 경우 : static,멤버클래스(내부클래스)
 *                                                            =================
 *                                                            모든 내용(변수,메소드) 공유
 *          *** 내부 클래스 : 하둡(데이터를 수집,분석,공유)
 *                             ========== === MapReduce (채봇)
 *                             
 *       = 한개의 프로그램안에서 여러개 메소드가 동시 실행 => 쓰레드 (실시간 구현) : 금융권 , 증권 , 예매
 *       = 네트워크 : 클라이언트 , 서버 
 *         서버 
 *          1) 요청 받기 => readLine() => Decodingm => InputStreamReader()(필터스트림)
 *          2) 요청 처리 , 찾기 => get() => Collection (Vector,ArrayList)
 *          3) 삭제 (remove()) , 추가 (add()) 
 *          4) 전송 => write => Encoding => getBytes()
 *         클라이언트 
 *          1) 요청 => write()
 *          2) 결과 받기 => readLine()
 *          3) 결과 출력 => 윈도우,브라우저 
 *         *** 웹 , 오라클 => TCP를 이용하는 프로그램 
 *            ========== 처리과정,결과값 추출 
 */

서버에서 처리해주는 기능들

미리 지정해준 Function들에 맞춰서 하나씩 처리하게 만들어준다. 

 

실제로 프로그램을 실행하면 이런 채팅창이 뜬다

 

 

728x90
반응형