[JAVA.15] 쓰레드(Thread)와 채팅 프로그래밍 (Chatting Programming)

Kama_Code·2023년 7월 29일
1

JAVA

목록 보기
20/20
post-thumbnail
post-custom-banner
  • 드디어 자바(JAVA) 정식 교육의 마지막 단원 쓰레드이다.
    쓰레드가 정식적인 자바 공부는 마무리되지만 포스팅은 끝나지 않는다.
    이후는 심화 내용이 있을 경우 게속 추가해서 작성될 예정이다.

< Step 1. 쓰레드 (Thread) >

  • 쓰레드?
    프로세스는 실행중인 프로그램을 의미한다.
    쓰레드는 프로세스 내에서 별도의 실행흐름을 갖는 대상이다.
    프로세스 내에서 둘 이상의 쓰레드를 생성하는 것이 가능하다.
    = 여러 개의 작업을 동시에 할 수 있게 된다.
    두 가지 이상의 작업을 동시에 처리하는 것을 멀티태스킹 Multi-tasking 이라고 한다.]
    java.lang패키지에 포함되어 있어서 따로 import할 필요가 없다

< Step 2. 쓰레드 (Thread)의 생성과 실행 >

  1. 쓰레드를 생성하는 첫번째 방법
    • Thread 클래스를 상속 받아 run() 메서드를 오버라이딩한다.
      start() 메소드를 통해 쓰레드가 생성된다.
      생성된 쓰레드는 run() 메소드를 호출하여 동장한다.

run() 메소드는 쓰레드의 main() 메소드에 해당한다
run() 메소드는 오버라이딩 한 것으로 해당 메소드는 직접 호출하면 안된다.
start() 메소드를 통해 간접적으로 호출해야 한다

class ShowThread extends Thread{
	String threadName;
	public ShowThread(String name) {
		threadName = name;
	}
	@Override
	public void run() {		 
		메소드의 실행부...
	}
}

▣ 둘 이상의 스레드를 생성

▣ 쓰레드의 우선순위

  • 쓰레드는 생성시 기본적으로 동일한 우선순위를 부여받는다.
  • 높은 우선순위가 필요한 쓰레드가 있다면 setPriority()메소드를 사용하여 설정한다.
  • 단, 우선순위는 절대적이지 않다. CPU의 유휴상태가 생기면
    우선순위가 낮은 쓰레드도 실행될 수 있다.
new MessageThread("첫번째", Thread.MAX_PRIORITY);//10
new MessageThread("두번째", Thread.NORM_PRIORITY);//5
new MessageThread("세번째", Thread.MIN_PRIORITY);//1

▣ 쓰레드의 생명주기(Life cycle)

  • Runnable 상태의 쓰레드만이 스케쥴러에 의해 스케쥴링 가능하다.
  • sleep(), join() 메소드를 호출로 인해 쓰레드는 Blocked 상태가된다.
  • Blocked 상태의 쓰레드는 조건에 따라 Runnable 상태로 변경될 수 있다.
  • Dead상태가 된 쓰레드는 Runnable 상태가 될 수 없다.

▣ 독립쓰레드와 데몬쓰레드

  • ◈ 독립 스레드(Non Daemon Thread)
    일반적으로 main() 메소드의 종료는 프로그램의 종료로 이어진다.
    단, 독립쓰레드는 main() 메소드가 종료되어도 계속 실행되며 Dead 상태가 되어야 종료된다.

  • ◈ 데몬 스레드(Daemon Thread)
    모든 독립쓰레드가 종료될때 자동으로 종료되는 쓰레드
    프로그램이 종료될때 자동으로 종료되므로 주로 무한루프로 쓰레드를 구성한다.
    일반적으로 main()메소드가 종료될때 프로그램도 종료되지만,
    독립쓰레드가 남아있다면 종료된 것이 아니라서 유의해야한다.
    ㄴ배경음악재생, 로그 자동저장 등의 업무에 많이 쓰인다

DaemonThread dThread = new DaemonThread();
dThread.setDaemon(true);
dThread.start();
  1. 쓰레드를 생성하는 두번째 방법
  • Runnable 인터페이스의 구현 (java.lang 패키지에 포함)
  • 일반적인 인터페이스의 구현
  • 람다식으로 구현
class AdderThread extends Sum implements Runnable {
	public void run() {
		함수의 실행부...
	}
}

▣ 쓰레드의 메모리 구성

  • 쓰레드1과 쓰레드2는 스택을 제외한 메소드 영역과 힙영역을 공유한다.
    ㄴ스태틱 역시 서로 한 구역을 공유한다.
  • 이 두 영역을 통해서 데이터를 공유할 수 있다.
  • 쓰레드의 실행이 메소드의 호출을 통해서 이뤄지고,
  • 메소드 호출을 위해 사용되는 공간이 스택이므로 공유할 수 없다.

▣ 쓰레드의 동기화 ( ★★★ 매우 중요 )
쓰레드는 하나의 메모리 공간에 동시에 접근하면 따로 논다.
ㄴ그러므로 동기화를 해줘야 한다.
ㄴ메소드 전체를 호출하면 시간은 길어지나 정확한 값을 가지게 된다.

동기화 방법: 메소드에 synchronized 키워드를 지정하거나
코드의 일부에 동기화 블럭을 지정한다

  • synchronized에 의해 쓰레드1이 메소드를 호출하게 되면
    쓰레드2는 접근이 불가능하다. 호출시 자물쇠로 잠그게 되고,
    실행종료시 열쇠를 통해 열게되는 개념이다. 이를 ‘동기화’라고 한다.

▣ 동기화 메소드

< Step 3. 쓰레드 풀 ( Thread pool ) >

  • 쓰레드 풀이란?
    쓰레드를 미리 생성하고, 작업 요청이 발생할 때 마다 미리 생성된 쓰레드로
    해당 작업을 처리하는 방식 = 쓰레드 재활용하기 위한 모델
    (쓰레드의 생성과 소멸은 리소스 소모가 많은 작업이므로)

▣ 쓰레드 풀 유형
newSingleThreadExecutor

  • 풀 안에 하나의 스레드만 생성하고 유지한다.
    스레드의 숫자가 하나이고 하나의 태스크가 완료된 이후에 다음 태스크가 실행한다.

newFixedThreadPool

  • 풀 안에 인자로 전달된 수의 스레드를 생성하고 유지한다.
    초기 스레드 개수는 0개, 코어 스레드 수와
    최대 스레드 수는 매개변수 nThreads 값으로 지정한다.
    만약 생성된 스레드가 놀고 있어도 스레드를 제거하지 않고 내버려 둔다.

newCachedThreadPool

  • 풀 안의 쓰레드의 수를 작업의 수에 맞게 유동적으로 관리한다.
    초기 스레드와 코어 스레드 개수는 0개,
    최대 스레드 수는 integer 데이터형이 가질 수 있는 최대 값이다.
    만약 스레드가 60초동안 아무일도 하지 않으면 스레드를 종료시키고 스레드풀에서 제거한다.

< Step 4. Callable & Future, ReentrantLock >

  • 스레드는 실행만 시켜 줄 수 있고, 스레드로부터 결과를 반환 받을 수 없다.
  • 스레드가 실행될 때 각 스레드마다 스택영역, 힙 영역이 따로 가지기 때문이다.
  • 스레드끼리 데이터를 공유하기 위해서는 스태틱 영역의 변수를 사용했다.
  • Callable: Runnalble과 유사하지만 return을 통해 작업의 결과를 받아볼 수 있다.
  • Future: 비동기적인 작업의 현재 상태를 조회하거나 결과를 가져올 수 있다. get()

Executor 프레임워크를 사용하면 태스크 처리가 끝난 다음
결과를 반환 받아 태스크들을 병렬로(concurrent) 실행할 수 있다.

ReentrantLock(명시적 동기화) : 눈으로 볼 수 있는 동기화
synchronized는 메소드 전체나 구간을 묶어서 동기화를 시켰다.
그런데 ReentrantLock 클래스를 사용하면 시작점과 끝점을 명백히 명시할 수 있다.

사용법 : lock, unlock 메소드를 통해 시작과 종료를 명시적으로 작성해줄 수 있다.

< Step 5. 컬렉션 객체 동기화>

비동기화된 메소드를 동기화된 메소드로 래핑하는
Collections의 synchronizedXXX() 메서드를 제공한다.

하지만 컬렉션 객체의 동기화를 이렇게 했다고 하더라도
이 컬렉션 객체를 기반으로 생성하는 반복자는 별도로 동기화를 다시 해 주어야 한다.

▣ ConcurrentHashMap
스레드가 컬렉션 객체의 요소를 처리할 때 전체 잠금이 발생하여
컬렉션 객체에 접근하는 다른 스레드는 대기 상태가 된다.
이는 객체의 요소를 다루는 것은 안전해졌지만, 처리 속도는 느려졌다는 이야기가 된다.

멀티스레드가 컬렉션의 요소를 병렬적으로 처리할 수 있도록
java.util.concurrent 패키지에서
ConcurrentHashMap, ConcurrentLinkedQueue를 제공한다.

이 클래스는 부분적으로 잠금을 사용하기 때문에 객체의 요소를 처리할 때
스레드에 안전하면서 빠르게 처리가 가능해진다.

< Step 6. 채팅 프로그래밍 ( Chatting Programming )

먼저 이것을 읽어보자!
네트워크 기초 다운로드

▣ 안드로이드 폰에서 IP 주소 찾기

홈 화면에서 "설정" 아이콘을 찾고 누르세요.
일반적으로 기어 모양의 아이콘입니다.
설정 메뉴에서 "Wi-Fi 및 인터넷" 또는 "Wi-Fi"를 선택하세요.
현재 연결된 Wi-Fi 네트워크를 찾아 탭하세요.
네트워크 세부 정보가 표시됩니다. 여기에서 "IP 주소" 또는 "IPv4 주소"를
확인할 수 있습니다.

▣ 아이폰에서 IP 주소 찾기

홈 화면에서 "설정" 앱을 찾고 누르세요.
일반적으로 기어 모양의 아이콘이 있습니다.
설정 메뉴에서 "Wi-Fi"를 선택하세요.
현재 연결된 Wi-Fi 네트워크를 찾아 탭하세요.
네트워크 세부 정보가 표시됩니다. 여기에서 "IP 주소" 또는 "IPv4 주소"를
확인할 수 있습니다.

채팅서버 한글이 깨진다면 Putty

  • 서버 코드
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class MultiServer7
{
	ServerSocket serverSocket = null;
	Socket socket = null;
	Map<String, PrintWriter> clientMap;

	// 생성자
	public MultiServer7()
	{
		//클라이언트의 출력스트림을 저장할 해쉬맵 생성.
		clientMap = new HashMap<String, PrintWriter>();
		//해쉬맵 동기화 설정.
		Collections.synchronizedMap(clientMap);
	}

	public void init()
	{
		try
		{
			serverSocket = new ServerSocket(9999);
			System.out.println("서버가 시작되었습니다.");
			while(true)
			{
				socket = serverSocket.accept();
				// 연결된 원격 어드레스의 정보를 보여준다.
				System.out.println(socket.getInetAddress() + ":" + socket.getPort());
				
				Thread mst = new MultiServerT(socket); //쓰레드 생성
				mst.start(); //쓰레드 시동
			}
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
		finally
		{
			try
			{
				serverSocket.close();
			}
			catch(Exception e)
			{
				e.printStackTrace();
			}
		}
	}
	
	// 접속자 리스트 보내기
	public void list(PrintWriter out)
	{
		//출력스트림을 순차적으로 얻어와서 해당 메세지를 출력
		Iterator<String> it = clientMap.keySet().iterator();
		String msg = "사용자 리스트[";
		while (it.hasNext())
		{
			msg += (String)it.next() + ",";
		}
		msg = msg.substring(0, msg.length()-1) + "]";
		try
		{
			out.println(msg);
		}
		catch (Exception e)
		{
			
		}
	}
	// 접속된 모든 클라이언트들에게 메세지를 전달
	public void sendAllMsg(String msg, String name)
	{
		//출력 스트림을 순차적으로 얻어와서 해당 메세지를 출력한다.
		Iterator<String> it = clientMap.keySet().iterator();
		
		while (it.hasNext())
			try
			{
				PrintWriter it_out = (PrintWriter) clientMap.get(it.next());
				if(name.equals(""))
				{
					it_out.println(msg);
				}
				else
				{
					it_out.println(name + " > " + msg);
				}
			}
			catch (Exception e)
			{
				System.out.println("예외:"+e);
			}
					
		}
		public static void main(String[] args)
		{
			// 서버객체 생성
			MultiServer7 ms = new MultiServer7();
			ms.init();
		}

	// 내부 클래스
	// 클라이언트로부터 읽어온 메세지를 다른 클라이언트에 보내는
	// 역할을 하는 메소드
	
	class MultiServerT extends Thread
	{
		Socket socket;
		PrintWriter out = null;
		BufferedReader in = null;
		String name = "";
		
		//생성자
		public MultiServerT(Socket socket)
		{
			this.socket = socket;
			try
			{
				out = new PrintWriter(this.socket.getOutputStream(), true);
				in = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
			}
			catch(Exception e)
			{
				System.out.println("예외:"+e);
			}
		}
			@Override
			public void run()
			{
				String s = "";
				
				try
				{
					name = in.readLine();
					System.out.println("[" + name + "]님이 대화방에 입장하셨습니다.");
					
					// 현재 객체가 가지고 있는 소켓을 제외하고 다른 소켓(클라이언트)들에게
					// 접속을 알림
					
					sendAllMsg(name + "님이 입장하셨습니다.", "");

					// 해쉬맵에 키를 name으로 출력스트림 객체를 저장.
					clientMap.put(name, out);
					
					System.out.println("현재 접속자 수는 "+clientMap.size()+"명 입니다.");
					
					// 입력스트림이 null이 아니면 반복
					while (in!=null)
					{
						s = in.readLine();

						if (s.equals("q") || s.equals("Q"))
							break;
						
						System.out.println(name + " > "+ s);
						if (s.equals("/list"))
							list(out);
						else
							sendAllMsg(s,name);
					}
					System.out.println("쓰레드 종료");
				}
				catch (Exception e)
				{
					System.out.println("예외:"+e);
				}
				finally
				{
					// 예외가 발생할때 퇴장. 해쉬맵에서 해당 데이터 제거.
					// 보통 종료하거나 나가면 java.net.SocketException: 예외발생
					clientMap.remove(name);
					sendAllMsg(name + "님이 퇴장하셨습니다.", "");
					System.out.println("현재 접속자 수는"+clientMap.size()+"명 입니다.");
					try
					{
						in.close();
						out.close();

						socket.close();
					} 
					catch (Exception e)
					{
						e.printStackTrace();
					}
				}
			}
		}
}

+클라이언트 코드

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;
//서버와 클라이언트 연결
public class MultiClient5
{
	public static void main(String[] args) throws UnknownHostException, IOException
	{
		System.out.println("이름을 입력해 주세요.");
		Scanner sc = new Scanner(System.in);
		String name = sc.nextLine();
		
		try
		{
			String ServerIP = "localhost";
			if(args.length > 0)
				ServerIP = args[0];
			Socket socket = new Socket(ServerIP, 9999); //소켓 객체 생성
			System.out.println("서버와 연결이 되었습니다.....");
			
			//서버에서 보내는 메세지를 사용자의 콘솔에 출력하는 쓰레드
			Thread receiver = new Receiver5(socket);
			receiver.start();			
			
			//사용자로부터 얻은 문자열을 서버로 전송해주는 역할을 하는 쓰레드
			Thread sender = new Sender5(socket, name);
			sender.start();
		}	
		catch(Exception e)
		{
			System.out.println("예외[MultiClient class]:"+e);
		}
	}

}
  • Receiver 코드
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.Socket;

public class Receiver5 extends Thread
{
	Socket socket;
	BufferedReader in = null;
	
	//Socket을 매개변수로 받는 생성자
	public Receiver5(Socket socket)
	{
		this.socket = socket;
		
		try
		{
			in = new BufferedReader(new InputStreamReader(this.socket.getInputStream() ));
		}
		catch(Exception e)
		{
			System.out.println("예외1:" +e);
		}
	}
	//run()메소드 재정의
	@Override
	public void run()
	{
		while (in!=null)
		{
			try
			{
				System.out.println("Thread Receive : " + in.readLine());				
			}
			catch (java.net.SocketException ne)
			{
				break;
			}
			catch (Exception e)
			{
				System.out.println("예외:"+e);
			}
		}
		try
		{
			in.close();
		}
		catch (Exception e)
		{
			System.out.println("예외3:"+e);
		}
	}
	
}
  • sender 코드
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

public class Sender5 extends Thread
{
	Socket socket;
	PrintWriter out = null;
	String name;
	
	// 생성자
	public Sender5(Socket socket, String name)
	{
		this.socket = socket;		
		try
		{
			out = new PrintWriter(this.socket.getOutputStream(), true);
			this.name = name;
		}
		catch(Exception e)
		{
			System.out.println("예외S3:" +e);
		}
	}
	//run()메소드 재정의
	@Override
	public void run()
	{
		Scanner sc = new Scanner(System.in);
		try
		{ // 입력한 사용자의 이름을 서버에 보내준다
			out.println(name);
			
			while(out!=null)
			{
				try
				{
					String s = sc.nextLine();
					out.println(s);
					
					if (s.equals("q") || s.equals("Q") )
					{
						break;
					}
				}
				catch (Exception e)
				{
					System.out.println("예외S1:"+e);				
				}
			}
			
			out.close();
			socket.close();
		}
		catch (Exception e)
		{
			System.out.println("예외S2:"+e);
		}

		sc.close();
	}
}

<Step.7> 발전을 위한 문제풀이 (kama_code 출제)

  • 아래 기능을 만족하는 채팅 프로그램을 만드시오.
  1. 가입
    중복 아이디 처리 : 데이터베이스 연동
  1. 접속
    중복 아이디 처리 : 데이터베이스 연동
    접속거부 : 블랙리스트 처리
  1. 공통
    대화상대 차단
    대화 금칙어 처리
    서버 적용 금칙어 (공용, 관리자)
    개인 추가 금칙어
    귓속말
    1회용 설정
    고정 설정
    공지사항 전달 (대기실 + 대화방 전체, 관리자)
  1. 대기실 명령어 :
    공개방만들기
    타이틀 설정
    정원 설정
    비공개방만들기
    타이틀 설정
    정원 설정
    비밀번호 설정
    방리스트보기
    전체 보기
    공개방만 따로 보기
    비공개방만 따로 보기
    대화방 참여
  1. 룸 명령어 :
    대화방 리스트 보기
    대기실 사용자 리스트 보기
    내 룸 사용자 리스트 보기
    초대하기
    방장만 가능
    상대방이 수락시 자동으로 방 참여시키기
    강퇴
    일회성 강퇴
    영구 강퇴
    룸 나가기
    방장 승계
    방 폭파 기능
  1. 관리용
    관리자 로그인
    원하는 방 모니터링
    블랙리스트 처리
    방 폭파 처리

/list ← 서버의 접속자 리스트를 자기 화면에 출력하면 됩니다.
/to 홍길동 안녕하세요 (엔터)← 홍길동한테만 메시지를 보여주면 됩니다. (귓속말 기능)

[[[ 힌트 ]]]

서버에서...
1.클라이언트에서 대화가 올라옴
s = in.readLine();
현재 “이름 + 입력한 내용” 올라옴 → 입력한 내용만 올라오게 수업에서 만든 클라이언트 파일 수정해야 함
2.올라온 내용이 “/”로 시작하는지 검사
“/”로 시작하면 명령어임
아니면 일반 대화임
/list의 경우 명령어만 올라오므로 분리 없이 통째로 비교 가능함
3.명령어면 substring이나 StringTokenizer 로 내용 분리
분리한 내용에 list 가 있으면 해쉬맵의 키값만 묶어서 해당 사용자에게 내려 보냄
현재 해쉬맵에 키값이 사용자 이름임

4.귓속말 : 분리한 내용의 첫 번째 요소에 to 가 있으면 귓속말 명령임
서버로 올라온 내용이 “/to 이름 내용” 이므로 이름을 분리해 냄
이름이 해쉬맵에서 키값으로 사용되고 있으므로 해당 키값의 밸류를 찾아 냄
현재 밸류값에 사용자와 연결된 Socket의 PrinterWriter 값이 저장되어 있슴
밸류값이 OutputStream 이므로 그걸 이용해서 해당 사용자에게만 내용을 내려보내면 귓속말이 구현됨

/to 홍길동 (엔터)
← 귓속말 고정 설정 / 설정 풀기

★ 정답 및 해설 ☆

  1. 서버 코드
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.List;
import java.util.Scanner;
import java.util.StringTokenizer;


public class ChatServer2 {

	Map<String, String> listw;
	Map<String, String> inviteM;
	static Connection con;
	static ServerSocket serverSocket = null;
	static Socket socket = null;
	Map<String, PrintWriter> waitUser;									//대기방 사용자
	Map<String, PrintWriter> clientMap;									// 사용자 아이디와 출력할 내용 -- 대기실용 해시맵		
	Map<String, Map<String, PrintWriter>> roomN;						// 공개방 해시맵	
	Map<String, Map<String, PrintWriter>> roomP;						// 비밀방
	Map<String, Integer> capa;											// 각방의 정원
	Map<String, String> pwd;											// 비밀방의 비밀번호
	
	//생성자
	public ChatServer2() {
		//client의 출력스트림을 저장할 해시맵 생성.
		inviteM = new HashMap<String, String>();
		waitUser = new HashMap<String, PrintWriter>();
		clientMap = new HashMap<String, PrintWriter>();
		roomN = new HashMap<String, Map<String, PrintWriter>>();
		capa = new HashMap<String, Integer>();
		pwd = new HashMap<String, String>();
		listw = new HashMap<String, String>();
		listw.put("/list", "모든 사용자 리스트");
		listw.put("/waituser", "대기방 유저 리스트");
		listw.put("/rlist", "채팅방 리스트 출력");
		listw.put("/adword", "개인 금칙어 설정 추가");
		listw.put("/delword", "개인 설정 금칙어 삭제");
		listw.put("/agree", "채팅방 초대 수락 명령어");
		

		//해쉬맵 동기화 설정.
		Collections.synchronizedMap(inviteM);
		Collections.synchronizedMap(waitUser);
		Collections.synchronizedMap(roomN);
		Collections.synchronizedMap(clientMap);
		Collections.synchronizedMap(capa);
		Collections.synchronizedMap(pwd);

	}
	
	public void init()
	{
		try {
			serverSocket = new ServerSocket(9990);
			System.out.println("서버가 시작되었습니다.");
		
			while(true) {
				socket = serverSocket.accept();
				//System.out.println(socket.getInetAddress()+" : " + socket.getPort());
				
				Thread mst = new MultiServerR(socket);	//쓰레드 생성(대화방 입장 알림, 대화)
				mst.start();									
			}
		}catch(Exception e) {
			e.printStackTrace();
		}finally {
			try {
				System.out.println("서버가 종료됩니다.");
				serverSocket.close();
			}catch(Exception e) {
				
			}
		}
	}
	
	//대기방 사용자 리스트
	public void waitU(PrintWriter out) {
		Iterator<String> it = waitUser.keySet().iterator();
		String msg = "대기방 사용자 리스트 [";
		while(it.hasNext()) {
			msg += (String)it.next() + ",";
		}
		msg = msg.substring(0, msg.length()-1) + "]";
			
		try {
			out.println(msg);		//리스트 출력
		}catch(Exception e) {
		}
	}
	
	//사용자 리스트
	public void list(PrintWriter out)
	{
		//출력스트림을 순차적으로 얻어와서 해당 메세지를 출력한다
		Iterator<String> it = clientMap.keySet().iterator();
		String msg = "사용자 리스트 [";
		while(it.hasNext()) {
			msg += (String)it.next() + ",";
		}
		msg = msg.substring(0, msg.length()-1) + "]";
			
		try {
			out.println(msg);		//리스트 출력
		}catch(Exception e) {
		}
	}

	//대화방 리스트
	public void roomList(PrintWriter out) {
		Iterator<String> it = roomN.keySet().iterator();
		String msg = "방 리스트 [";
		while(it.hasNext()) {
			msg += (String)it.next() + ",";
		}
		msg = msg.substring(0, msg.length()-1) + "]";
			
		try {
			out.println(msg);		//리스트 출력
		}catch(Exception e) {
		}
	}
	
	//해당방 사용자
	public void userR(String title, PrintWriter out) {
		Iterator<String> it = roomN.get(title).keySet().iterator();
		String msg = title + "방 사용자 리스트 [";
		while(it.hasNext()) {
			msg += (String)it.next() + ",";
		}
		msg = msg.substring(0, msg.length()-1) + "]";
			
		try {
			out.println(msg);		//리스트 출력
		}catch(Exception e) {
		}
	}
	
	//접속된 모든 클라이언트들에게 메세지를 전달
	public void sendAllMsg(String msg, String name) {

		PreparedStatement pstmt2 = null;
		String sql = null;
		ResultSet rs = null;
		String key = null;
		String word = null;
		String query = null;
		int chk = 0;
		int id2 = 0;
		int id1 = 0;
		
		//해쉬 맵과 이터레이터를 이용해서 출력스트림을 순차적으로 얻어와서 해당 메시지를 각방에 출력한다.
		Iterator<String> it = clientMap.keySet().iterator();
		
		while(it.hasNext()) {
			chk = 0;
			id2 = 0;
			try {
				key = it.next();
				PrintWriter it_out = (PrintWriter) clientMap.get(key);
				sql = "select * from blackword";
				pstmt2 = con.prepareStatement(sql);
				rs = pstmt2.executeQuery();
				while(rs.next()) {
					word = rs.getString(1);
					if(msg.contains(word)) {
						chk = 1;
						for(int i = 0; i < msg.length(); i++) {
							 it_out.print("*");
						 }
						 it_out.println();
						 break;
					}
				}
				
				
				sql = "select count(*) from $tablename";
				query = sql.replace("$tableName", key);
				pstmt2 = con.prepareStatement(query);
				rs = pstmt2.executeQuery();
				while(rs.next()) {
					id1 = Integer.parseInt(rs.getString(1));
				}
				
				if(id1 > 0) {
					sql = "select * from $tablename";
					query = sql.replace("$tableName", key);
					pstmt2 = con.prepareStatement(query);
					rs = pstmt2.executeQuery();
					
					while(rs.next()) {
						 word = rs.getString(1);
						 if(msg.equals(word)) {
							 id2 = 1;
							 for(int i = 0; i < msg.length(); i++) {
								 it_out.print("*");
							 }
							 it_out.println();
						 }
					}
					
					id1 = 0;
				}
				if(id2 == 0) {
					if(name.equals(""))
							it_out.println(msg);				//받은 메세지 각 방에 출력
						else
							it_out.println(name + " > " + msg);
				}
			}catch(Exception e) {
				System.out.println("예외:" + e);
			}
				
		}
	}
	
	//방 내의 클라이언트들에게 메세지 전달
	public void sendMsg(String msg, String name, String i) {

		//해쉬 맵과 이터레이터를 이용해서 출력스트림을 순차적으로 얻어와서 해당 메시지를 각방에 출력한다.
		
		Iterator<String> it = roomN.get(i).keySet().iterator();
		String sql = null;
		ResultSet rs = null;
		String word = null;
		String query = "";
		PreparedStatement pstmt2 = null;
		PreparedStatement pstmt3 = null;
		String next = null;
		int chk = 0;
		int id2 = 0;
		int id1 = 0;
		while(it.hasNext()) {
			chk = 0;
			id2 = 0;
			try {
				next = it.next();
				if(!next.equals(name)) {

					PrintWriter it_out = (PrintWriter) roomN.get(i).get(next);
					
					sql = "select * from blackword";
					pstmt2 = con.prepareStatement(sql);
					rs = pstmt2.executeQuery();
					while(rs.next()) {
						word = rs.getString(1);
						if(msg.equals(word)) {
							chk = 1;
							for(int j = 0; j < word.length(); j++) {
								 it_out.print("*");
							 }
							 it_out.println();
							 break;
						}
					}

					if(chk == 1) continue;			 
								
				
					sql = "select * from " + next ;
					pstmt2 = con.prepareStatement(sql);
					rs = pstmt2.executeQuery();
					
					while(rs.next()) {
						 word = rs.getString(1);
						 if(msg.equals(word)) {
							 id2 = 1;
							 for(int j = 0; j < msg.length(); j++) {
								 it_out.print("*");
							 }
							 it_out.println();
							 break;
						 }
					}
				
					if(id2 == 1)continue;
					
					if(name.equals(""))
						it_out.println(msg);				//받은 메세지 각 방에 출력
					else
						it_out.println(name + " > " + msg);
					
				}
			}catch(Exception e) {
				System.out.println("예외:" + e);
			}
				
		}
	}
	
	//관리자용
	public void adlogin(BufferedReader in, PrintWriter out) throws IOException, SQLException {
		
		String menu = "";
		String sql = null;
		PreparedStatement pstmt4 = null;
		PreparedStatement pstmt2 = null;
		ResultSet rs = null;
		String id = null;
		String word = null;
		
		while(true) {
			out.println("===메뉴===");
			out.println("1. 블랙리스트 목록");
			out.println("2. 블랙리스트 등록");
			out.println("3. 블랙리스트 해제");
			out.println("4. 방폭파");
			out.println("5. 금칙어 목록");
			out.println("6. 금칙어 등록");
			out.println("7. 금칙어 삭제");
			out.println("8. 나가기");
			out.println();
			menu = in.readLine();
			//블랙리스트 목록
			if(menu.equals("1")) {

				sql = "select id from chatuser where utype = ?";
				pstmt2 = con.prepareStatement(sql);
				pstmt2.setString(1,  "1");
				rs = pstmt2.executeQuery();
				out.println("<<블랙리스트 목록>>");
				while(rs.next()) {					
					word = rs.getString(1);
					out.println(word);
				}
				out.println();
			}
			//블랙리스트 등록
			else if(menu.equals("2")) {
								
				sql = "select id from chatuser";
				pstmt2 = con.prepareStatement(sql);
				rs = pstmt2.executeQuery();
				out.println("<<사용자 아이디>>");
				while(rs.next()) {					
					word = rs.getString(1);
					out.println(word);
				}
				
				out.println("블랙리스트 등록을 원하는 id를 입력하세요.");
				id = in.readLine();
				
				sql = "update chatuser set utype = '1' where id = ?";
				pstmt4 = con.prepareStatement(sql); 
				pstmt4.setString(1,  id);
				rs = pstmt4.executeQuery();
				out.println("완료되었습니다.");
				out.println();
			}
			//블랙리스트 해제
			else if(menu.equals("3")) {
				out.println("블랙리스트 해제를 원하는 id를 입력하세요.");
				id = in.readLine();
				sql = "update chatuser set utype = '0' where id = ?";
				pstmt4 = con.prepareStatement(sql); 
				pstmt4.setString(1, id);
				rs = pstmt4.executeQuery();
				out.println("완료되었습니다.");
				out.println();
			}
			//방폭파
			else if(menu.equals("4")) {
				roomList(out);
				out.println("방폭파를 원하는 방 이름을 입력하세요");
				id = in.readLine();
				roomN.remove(id);
				out.println(id + "방이 폭파되었습니다.");
				out.println();
			}
			//금칙어 목록
			else if(menu.equals("5")) {
				out.println("<<금칙어 목록>>");
				sql = "select * from blackword";
				pstmt2 = con.prepareStatement(sql);
				rs = pstmt2.executeQuery();
				while(rs.next()) {
					out.println("> " + rs.getString(1));
				}
				out.println();
			}
			//금칙어 추가
			else if(menu.equals("6")) {
				out.println("<<금칙어 추가>>");
				out.println("금칙어 : ");
				id = in.readLine();
				sql = "insert into blackword values (?)";
				pstmt4 = con.prepareStatement(sql); 
				pstmt4.setString(1, id);
				rs = pstmt4.executeQuery();
				out.println("\'" + id + "\'가 금칙어로 추가되었습니다. ");
				out.println();
			}
			//금칙어 삭제
			else if(menu.equals("7")) {
				
				out.println("삭제할 금칙어 : ");
				id = in.readLine();
				sql = "delete from blackword where word = ?";
				pstmt2 = con.prepareStatement(sql); 
				pstmt2.setString(1, id);
				rs = pstmt2.executeQuery();
				out.println("금칙어가 삭제되었습니다.");
				out.println();
			}
			
			//페이지 종료
			else if(menu.equals("8"))
			{
				out.println("관리자 페이지를 종료합니다.");
				break;
			}else {
				out.println("잘못 선택하셨습니다.");
			}
		}
	}
	//귓속말
	public void secretMsg(String msg, String name, String nameto, String i) {
		
		PrintWriter p = null;
		try {
			p = (PrintWriter) roomN.get(i).get(nameto);
			if(name.equals(""))
				p.println(msg);				//받은 메세지 각 방에 출력
			else
				p.println(name + "(귓속말) > " + msg);
		}catch(Exception e) {
			
		}
		
	}
	
	//초대
	public void invite( String name, String nameto, String i) {
		PrintWriter p = null;
		inviteM.put(nameto, i);
		try {
			p = (PrintWriter) clientMap.get(nameto);
			p.println(name + "님께서 " + i + "방으로 초대하셨습니다.");
			p.println("수락 --> /수락");
			p.println("거부 --> /거부");
			
		}catch(Exception e) {
		
		}
	}
	
	
	//강퇴
	public void cutUser(String title, String name, PrintWriter out) {
		
		roomN.get(title).remove(name);
	}
	
	
	public static void main(String[] args) {
		
		try {
			//오라클과 연결(데이터 제공)
			Class.forName("oracle.jdbc.driver.OracleDriver");
		}catch(ClassNotFoundException cnfe) {
			cnfe.printStackTrace();
			System.out.println("오라클 실패");
		}

		try {
			con = DriverManager.getConnection(
					"jdbc:oracle:thin:@localhost:1521:xe", //localhost = 사용위치(현재는 내컴퓨터)
					"scott",
					"tiger");
		}catch(SQLException sqle) {System.out.println("두마이런 실패");}
		
		//서버객체 생성
		ChatServer2 ms = new ChatServer2();
		ms.init();
	
	}
	

////////////////////////////////////////////////////////////////////////////////////////////
/////////////대기방 --> 방을 생성하거나 방에 입장할 수 있음
	class MultiServerR extends Thread
	{
		String sql1 = null;
		String sql = null;
		PreparedStatement pstmt1 = null;
		PreparedStatement pstmt2 = null;
		PreparedStatement pstmt3 = null;
		PreparedStatement pstmt4 = null;
		ResultSet rs = null;
		
		Socket socket;
		PrintWriter out = null;
		BufferedReader in = null;
		String name = "";
		String title = null;
		
		//생성자
		public MultiServerR(Socket socket) {
			this.socket = socket;
			
			try {
				out = new PrintWriter(socket.getOutputStream(), true);
				in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			}catch(Exception e) {
				
			}
		}
		
		//쓰레드를 사용하기 위해 run()메서드 재정의
		@Override
		public void run()
		{
			int type = 0;
			String s = "";
			String id = null;			//아이디
			String pw = null;			//비밀번호
			int th = 0;
			Iterator<String> iter = null;
			String ss = "";
			String key = null;
			String getMenu = "";

			out.println("=============================================");
			out.println("==================대기실====================");
			out.println("=============================================");
			out.println();
			
			try {
				String pw1 = null;	//비밀번호 비교에 사용
				int ld =0;
				while(true) {
					
					ld = 0;
					out.println("관리자 로그인은 /adlogin을 입력해주세요");
					out.println("=============================================");
					out.println("메뉴를 선택하세요");
					out.println("1. 로그인  2. 회원가입 3. 회원탈퇴");
					out.println("=============================================");

					try {
						getMenu = in.readLine();
						
					}catch (IOException e) {
						
					}
					
					if(getMenu.equals("/adlogin")) {
						out.println("<<관리자 페이지입니다>> \n 비밀번호를 입력해주세요");
						pw = in.readLine();
						sql = "select pw from chatuser where id = ?";
						pstmt1 = con.prepareStatement(sql);
						pstmt1.setString(1, "head");
						rs = pstmt1.executeQuery();
						
						while(rs.next()){
							pw1 = rs.getString(1);
							if(pw.equals(pw1)) {
								out.println("환영합니다 관리자님");
								adlogin(in, out);
							}else {
								out.println("비밀번호가 틀렸습니다.");
							}
							break;
						}
					}
					else {
						
						type = Integer.parseInt(getMenu);
						//로그인
						if(type == 1) {	
							
							int id1 = 0;
							String id11 = null;
								
							out.println("<<로그인>>");
							out.println("id");
							try {
								id = in.readLine();
								
								if(id.equals("back")) continue;
								sql = "select count(*) from chatuser where id = ?";
								pstmt2 = con.prepareStatement(sql);
								pstmt2.setString(1, id);
								rs = pstmt2.executeQuery();
								while(rs.next()) {
									id1 = Integer.parseInt(rs.getString(1));
								}
								if(id1 == 0) {
									out.println("존재하지 않는 아이디 입니다.");
									continue;
								}
								
								sql = "select utype from chatuser where id = ?";
								pstmt1 = con.prepareStatement(sql); 
								pstmt1.setString(1, id);
								rs = pstmt1.executeQuery();
								
								while(rs.next()) {
									int utype = Integer.parseInt(rs.getString(1));
									if(utype == 1) {
										out.println("로그인이 금지된 아이디입니다.");
										ld = 1;
									}
								}
								
							}catch (IOException e1) {
								e1.printStackTrace();
							}
							if(ld == 1) continue;	
							
							id1 = 0;
							Iterator<String> at = clientMap.keySet().iterator();
							while(at.hasNext()) {
								id11 = (String)at.next();
								if(id.equals(id11)) {
									id1 = 1;
									break;
								}
							}
							if(id1 == 1){
								out.println("이미 활동중인 아이디입니다.");
							}
							else {
								out.println("pw ");
								try {
									pw = in.readLine();
								} catch (IOException e1) {
									
								}
									
											
								try {
									try {
										sql = "select pw from chatuser where id = ?";
										pstmt1 = con.prepareStatement(sql); 
										pstmt1.setString(1, id);
										rs = pstmt1.executeQuery();
											
										while(rs.next()) {
											pw1 = rs.getString(1);
										}
									}catch(SQLException sqle) {
										
										System.out.println("sql문 오류");
									}
										
									if(pw.equals(pw1)) {
										clientMap.put(id, out);
										waitUser.put(id, out);
										out.println("로그인에 성공했습니다");
										Thread mst = new MultiServer2(socket,id,title);	//쓰레드 생성(대화방 입장 알림, 대화)
										th = 1;
										mst.start();
										mst.join();
										continue;
									}else {
										out.println("로그인에 실패했습니다");
									}
								}finally {
									try {
										if(rs != null) rs.close();
										if(pstmt1 != null) pstmt1.close();
										if(pstmt2 != null) pstmt2.close();
									}catch(SQLException sqle){}
								}
							} 
						}
							
						//회원가입
						else if(type == 2) {
							try {
								out.println("<<회원가입>>");
								int id1 = 0;
										
								while(true) {
									
									out.println("id: ");
									id = in.readLine();		
											
									try {
										if(id.equals("back")) break;
										sql = "select count(*) from chatuser where id = ?";
										pstmt2 = con.prepareStatement(sql);
										pstmt2.setString(1, id);
										rs = pstmt2.executeQuery();
										while(rs.next()) {
											id1 = Integer.parseInt(rs.getString(1));
										}
										if(id1 == 0) {
											out.println("pw: ");
											pw = id = in.readLine();	
											sql = "insert into chatuser(id, pw, utype) values(?, ?, 0)";
											pstmt3 = con.prepareStatement(sql); 
											pstmt3.setString(1, id);
											pstmt3.setString(2, pw);
											rs = pstmt3.executeQuery();
									
											//개인 금칙어 테이블 생성
											String strQuery = "create table $tableName (wordd varchar(10) )";
											String query =strQuery.replace("$tableName",id);
											pstmt4 =con.prepareStatement(query);       
											rs = pstmt4.executeQuery();
											
											out.println("회원가입 되었습니다.");
											break;
											
										}else {
											out.println("이미 존재하는 아이디입니다.");
											break;
										}
									
									}catch(SQLException sqle) {
										sqle.printStackTrace();
									}finally {
										try {
											if(rs != null) rs.close();
											if(pstmt2 != null) pstmt2.close();
											if(pstmt3 != null) pstmt3.close();
										}catch(SQLException sqle){}
											
									}
								}
								//break;
							} catch (IOException e) {							
								
							}
						}
						//회원탈퇴
						else if(type == 3) {
							int id1 = 0;
							out.println("<<회원 탈퇴>>");
							out.println("id: ");
							id = in.readLine();	
							
							if(id.equals("back")) continue;
							
							sql = "select count(*) from chatuser where id = ?";
							pstmt2 = con.prepareStatement(sql);
							pstmt2.setString(1, id);
							rs = pstmt2.executeQuery();
							while(rs.next()) {
								id1 = Integer.parseInt(rs.getString(1));
							}
							if(id1 == 0) {
								out.println("존재하지 않는 아이디 입니다.");
								continue;
							}
							out.println("pw: ");
							pw = in.readLine();	
							
							sql = "select pw from chatuser where id = ?";
							pstmt1 = con.prepareStatement(sql); 
							pstmt1.setString(1, id);
							rs = pstmt1.executeQuery();
								
							while(rs.next()) {
								pw1 = rs.getString(1);
							}
							
							if(pw.equals(pw1)) {
								
								out.println("정말 탈퇴하시겠습니까? Y/N");
								String o = in.readLine();
								if(o.equals("Y") || o.equals("y")) {
									sql = "delete from chatuser where id = ?";
									pstmt3 = con.prepareStatement(sql); 
									pstmt3.setString(1, id);
									rs = pstmt3.executeQuery();
									

									sql = "drop table " + id;
									pstmt4 = con.prepareStatement(sql);
					
									rs = pstmt4.executeQuery();
									out.println("탈퇴되었습니다.");
									continue;
								}
							}
						}
					else {
						System.out.println(" 메뉴를 다시 선택하세요");
					}			
				}
			}
			}catch(Exception e) {
				System.out.println("예외1:" +e);
			}finally {
				//예외가 발생할때 퇴장. 해쉬맵에서 해당 데이터 제거.
				//보통 종료하거나 나가면 java.net.SocketException: 예외발생
				if(th == 0) {
					clientMap.remove(name);
					sendAllMsg(name + "님이 퇴장하셨습니다.", "");
					System.out.println("00현재 접속자 수는 " + clientMap.size()+"명 입니다.");
				}
				
				try {
					serverSocket.close();
					in.close();
					out.close();
				}catch(Exception e) {
					
				}
			}
		}
	
	}

////////////////////////////////////////////////////////////////////////////////////
///////로그인 이후 방입장 단계
	class MultiServer2 extends Thread
	{
		
		Socket socket;
		String title = null;
		PrintWriter out = null;
		BufferedReader in = null;
		String id = "";
		String pw = null;
		
		//생성자
		public MultiServer2(Socket socket, String name, String title) {
			this.socket = socket;
			this.id = name;
			this.title = title;
			try {
				out = new PrintWriter(socket.getOutputStream(), true);
				in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			}catch(Exception e) {
				
			}
		}
		
		//쓰레드를 사용하기 위해 run()메서드 재정의
		@Override
		public void run()
		{	
			String sql1 = null;
			String sql = null;
			PreparedStatement pstmt1 = null;
			PreparedStatement pstmt2 = null;
			PreparedStatement pstmt3 = null;
			PreparedStatement pstmt4 = null;
			ResultSet rs = null;
			
			Iterator<String> iter = null;
			String ss = "";
			String menu = null;
			int count = 0;
			int th = 0;
			String type = null;
				
			try {
				
				
				System.out.println("==============================================");
				System.out.println("	" + id + "님이 입장하셨습니다.	");
				System.out.println("==============================================");
				
				System.out.println();
				System.out.println("==============================================");
				System.out.println("==============================================");
				System.out.println("	현재 전체 접속자 수는 " +clientMap.size() + "명 입니다.");
				System.out.println("==============================================");
				System.out.println("==============================================");
				System.out.println();
				
				while(true){
					
					
					out.println("/* --> 명령어 보기");
					out.println("원하는 메뉴를 선택하세요\n"
								+ "1. 공개채팅방 만들기\n"
								+ "2. 비밀채팅방 만들기\n"
								+ "3. 전체 방 리스트 보기\n"
								+ "4. 채팅방 입장하기\n"
								+ "5. 로그아웃");
					menu = in.readLine();
					type = menu;
					

					if(menu.startsWith("/")) {
						if(menu.equals("/*")) {
							iter = listw.keySet().iterator();
							out.println("<<명령어>>");
							while(iter.hasNext()) {
								String key = iter.next();
								out.println(">> " + key + " :		" + listw.get(key));
							}
							out.println(">> /userlist :		방 참여자 리스트");
							
							
						}
						else if(menu.equals("/수락")) {
							title = inviteM.get(id);
							try {
								
								if(id.equals(""))
									sendMsg("수락하셨습니다", id, title);
								else 
									sendMsg("수락",id, title);
								Thread mst = new MultiServerT(socket, id, title);	//쓰레드 생성(대화방 입장 알림, 대화)
								mst.start();
								mst.join();	
								//continue;
								
							}catch(Exception e) {
							
							}
						}else if(menu.equals("/거부")) {
							try {
								title = inviteM.get(id);
								inviteM.remove(id);
								
								if(id.equals(""))
									sendMsg("거부하셨습니다", id, title);			//받은 메세지 각 방에 출력
								else
									sendMsg("거부", id, title);
								
							}catch(Exception e) {
						
							}
							
						}
						else if(menu.equals("/list")) {
							list(out);
							
						}
						
						//방리스트
						else if(menu.equals("/rlist")){
							roomList(out);
							
						}
						//대기실 사용자 리스트
						else if(menu.equals("/waituser")) {
							waitU(out);
						}
						
						//금칙어 추가
						else if(menu.equals("/adword")) {
							out.println("<<금칙어 추가>>");
							out.println("금칙어 : ");
							ss = in.readLine();
							sql = "insert into $tableName values (?)";
							String query =sql.replace("$tableName",id);
							pstmt1 = con.prepareStatement(query); 
							pstmt1.setString(1, ss);
							rs = pstmt1.executeQuery();
							out.println("\'" + ss + "\'가 금칙어로 추가되었습니다. ");
						
						}
						
						//금칙어 삭제
						else if(menu.equals("/delword")) {
							sql = "select * from $tableName";
							String query = sql.replace("$tableName", id);
							pstmt2 = con.prepareStatement(query);
							rs = pstmt2.executeQuery();
							while(rs.next()) {
								out.println("> " + rs.getString(1));
							}
							
							out.println("삭제할 금칙어 : ");
							ss = in.readLine();
							sql = "delete from $tableName where wordd = ?";
							String query1 =sql.replace("$tableName",id);
							pstmt3 = con.prepareStatement(query1); 
							pstmt3.setString(1, ss);
							rs = pstmt3.executeQuery();
							out.println("금칙어가 삭제되었습니다.");
							
						}
						
						else if(menu.equals("/userlist")) {
							userR(title, out);
						}
					}
					
						//1번 선택시 방추가
				
					else if(type.equals("1")) {
						
						out.println(" 공개방을 생성합니다");
						out.println("========================");
							out.println("방이름을 설정해 주세요: ");
							title = in.readLine();
							
							out.println("방의 정원을 설정하세요: ");
							count = Integer.parseInt(in.readLine());
							
							//방을 저장해둔 해시맵에 참여자 해시맵 추가
							roomN.put(title, new HashMap<String, PrintWriter>(count));
							capa.put(title, count);
							waitUser.remove(id);
							
							th = 1;
							Thread mst = new MultiServerT(socket, id, title);	//쓰레드 생성(대화방 입장 알림, 대화)
							mst.start();
							mst.join();
						
							
						}
						
						//2번 선택시 비밀방추가
						else if(type.equals("2")) {
							
					
							out.println("  비밀방을 생성합니다");
							out.println("========================");
							out.println("방이름을 설정해 주세요: ");
							title = in.readLine();
							
							out.println("방의 정원을 설정하세요: ");
							count = Integer.parseInt(in.readLine());
							
							
							out.println("비밀번호를 입력하세요: ");
							pw = in.readLine();
							
							//방을 저장해둔 해시맵에 참여자 해시맵 추가
							roomN.put(title, new HashMap<String, PrintWriter>(count));
							capa.put(title, count);
							pwd.put(title, pw);
							waitUser.remove(id);
							
							th = 1;
							Thread mst = new MultiServerT(socket, id, title);	//쓰레드 생성(대화방 입장 알림, 대화)
							mst.start();
							mst.join();
						
	
						}
						
						//3번 선택시 전체 방 리스트
						else if(type.equals("3")) {
							roomList(out);
						}
						
						//4번 선택시 선택한 채팅방 입장하기
						else if(type.equals("4")) {
								menu = null;
								out.println("입장을 원하는 방 이름을 입력해 주세요: ");
								title = in.readLine();
								out.println();
								
								//입력한 방이름 없음
								if(!(roomN.containsKey(title))) {
									out.println("존재하지 않는 방입니다.");
								}
								
								////////정원초과 구현 안됨~~~~~~~~~~~~!!!!!!!!!!!!!!!!
								else if(roomN.get(title).size() == capa.get(title)) {
									out.println("정원초과로 입장하실 수 없습니다.");
								} 			
								
								//비밀방 입장
								else if(pwd.containsKey(title)) {
									
									out.println("비밀번호를 입력해주세요: ");
									pw = in.readLine();
										
									//비밀번호 맞음
									if(pwd.get(title).equals(pw)) {
										//roomN.get(title).put(name, out);
										System.out.println("\'" + title + "\' 방에 입장하셨습니다.");
					
											
										Thread mst = new MultiServerT(socket, id, title);	//쓰레드 생성(대화방 입장 알림, 대화)
										th = 1;
										waitUser.remove(id);
										mst.start();
										mst.join();
											
										//비밀번호 틀림
									}else {
											out.println("비밀번호가 틀렸습니다");
									}
									
								//일반방 입장
								}else {
									//roomN.get(title).put(name, out);
									
									
									Thread mst = new MultiServerT(socket, id, title);	//쓰레드 생성(대화방 입장 알림, 대화)
									th = 1;
									waitUser.remove(id);
									mst.start();
									mst.join();
									
								}
						}else if(type.equals("5")) {
							clientMap.remove(id);
							out.println("로그아웃 되었습니다.");
							out.println();
							break;
						}
								
				}	
			}catch(Exception e) {
				
				
			}finally {
				//예외가 발생할때 퇴장. 해쉬맵에서 해당 데이터 제거.
				//보통 종료하거나 나가면 java.net.SocketException: 예외발생
				if(th == 0) {
					roomN.get(title).remove(id);
					sendMsg(id + "님이 퇴장하셨습니다.", "", title);
					System.out.println("현재 접속자 수는 " + roomN.get(title).size()+"명 입니다.");
				}try {}
				catch(Exception e) {
					out.println("방입장 문제");
				}
			}
		}
	}
	

	
/////////////////////////////////////////////////////////////////////////////////////
////////////////내부클래스
////////////////클라이언트로부터 읽어온 메시지를 다른 클라이언트(socket)에 보내는 역할을 하는 메서드
	
	class MultiServerT extends Thread
	{
		Socket socket;
		String title = null;
		PrintWriter out = null;
		BufferedReader in = null;
		String name = "";
		
		//생성자
		public MultiServerT(Socket socket, String name, String title) {
			this.socket = socket;
			this.title = title;
			this.name = name;
			try {
				out = new PrintWriter(socket.getOutputStream(), true);
				in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			}catch(Exception e) {
				
			}
		
		}
		
		//쓰레드를 사용하기 위해 run()메서드 재정의
		@Override
		public void run()
		{
			int th = 0;
			roomN.get(title).put(name, out);
			String s = "";
			String sss = "";

			String ss;
			String sql;
			PreparedStatement pstmt1;
			ResultSet rs;
				
			try {
				
				//현재 객체가 가지고 있는 소켓을 제외하고 다른 소켓(클라이언트)들에게
				//접속을 알림.
				//sendAllMsg("!!!!" + name + "님이 입장하셨습니다. !!!!", "");
				
				sendMsg("------" + name + "님이 입장하셨습니다------", "", title);
				
				out.println("\'" + title + "\' 방에 입장하셨습니다.");
				out.println();
				out.println("\'" + title + "\'방의 정원은 " + capa.get(title) + "명 입니다.");
				out.println("\'" + title + "\'방의 현재 접속자 수는 " +roomN.get(title).size() + "명 입니다.");
				out.println("퇴장을 원하시면 /나가기,  /퇴장을 입력해주세요");
				out.println("명령어 목록을 원하시면 /*를 입력해 주세요");
				
				//입력 스트림이 null이 아니면 반복
				while(in != null) {
					
					s = in.readLine();
					//방빠져나가야함
					//방폭파
					if(!roomN.keySet().contains(title)) {
						waitUser.put(name, out);
						out.println("퇴장.");
						th = 1;
						break;
					}
					//강퇴
					if(!roomN.get(title).keySet().contains(name)) {
						waitUser.put(name, out);
						out.println("퇴장.");
						th = 1;
						break;
					}
					else {
						
						if(s.startsWith("/")) {
							String s2 = null;
							String n = null;
							String[] s1 = s.split(" ");
							if(s1.length > 1) {
								s2 = s1[0];
								
								//귓속말 명령어
								if(s2.equals("/to")) {
									n = s1[1];
									out.println(n + "에게 귓속말>>");
									while(in != null) {
										sss = in.readLine();
										if(sss.equals("//stop")) {
											break;
										}else {
											secretMsg(sss, name, n, title); 
										}
									}
									continue;
								}
								//강퇴기능
								else if(s2.equals("/강퇴")) {
									for(int j = 1; j < s1.length; j++) {
										n = s1[j];
										roomN.get(title).remove(n);
									}
									continue;
								}
								//초대
								else if(s2.equals("/초대")) {
									n = s1[1];
									invite(name, n, title);
								}
							}
							
							
							//명령어 리스트
							else if(s.equals("/*")) {
								Iterator<String> iter = listw.keySet().iterator();
								out.println("<<명령어>>");
								while(iter.hasNext()) {
									String key = iter.next();
									out.println(">> " + key + " :		" + listw.get(key));
								}
								out.println(">> /userlist :		방 참여자 리스트");
								out.println(">> /to 사용자이름:	귓속말");
								out.println(">> /초대 사용자이름:사용자 초대하기");
								out.println(">> /강퇴 사용자아이디:		강퇴기능");
								
							}
							
							else if(s.equals("/list")) {
								list(out);
								
							}
							
							//방리스트
							else if(s.equals("/rlist")){
								roomList(out);
								
							}
							//대기실 사용자 리스트
							else if(s.equals("/waituser")) {
								waitU(out);
							
							}
							
							//금칙어 추가
							else if(s.equals("/adword")) {
								out.println("<<금칙어 추가>>");
								out.println("금칙어 : ");
								ss = in.readLine();
								sql = "insert into $tableName values (?)";
								String query =sql.replace("$tableName",name);
								pstmt1 = con.prepareStatement(query); 
								pstmt1.setString(1, ss);
								rs = pstmt1.executeQuery();
								out.println("\'" + ss + "\'가 금칙어로 추가되었습니다. ");
					
							}
							
							//금칙어 삭제
							else if(s.equals("/delword")) {
								sql = "select * from $tableName";
								String query = sql.replace("$tableName", name);
								PreparedStatement pstmt2 = con.prepareStatement(query);
								rs = pstmt2.executeQuery();
								while(rs.next()) {
									out.println("> " + rs.getString(1));
								}
								
								out.println("삭제할 금칙어 : ");
								ss = in.readLine();
								sql = "delete from $tableName where wordd = ?";
								String query1 =sql.replace("$tableName",name);
								pstmt1 = con.prepareStatement(query1); 
								pstmt1.setString(1, ss);
								rs = pstmt1.executeQuery();
								out.println("금칙어가 삭제되었습니다.");
								
							}
							else if(s.equals("/userlist")) {
								userR(title, out);
								
							}
							
							else if(s.equals("/나가기")|| s.equals("/퇴장")) {
								roomN.get(title).remove(name);
								waitUser.put(name, out);
								if(roomN.get(title).isEmpty()) {
									roomN.remove(title);
								}
								th = 1;
								break;
							}else {
								out.println("올바르지 않은 명령어 입니다.");
							}
							
							
						}
						else {
							//sendAllMsg(s, title);
							sendMsg(s, name, title);
							
						}	
					}
					
				}
				
				
			}catch(Exception e) {
				
				
			}finally {
				//예외가 발생할때 퇴장. 해쉬맵에서 해당 데이터 제거.
				//보통 종료하거나 나가면 java.net.SocketException: 예외발생
				if(th != 1) {
					roomN.get(title).remove(name);
				}
				if(!roomN.get(title).isEmpty())
					sendMsg(name + "님이 퇴장하셨습니다.", "", title);			
				try {
				}catch(Exception e) {
					
				}
			}
				
		}
	}
}
  1. 클라이언트 코드
import java.io.IOException;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.Scanner;

public class ChatClient
{
	public static void main(String[] args) throws UnknownHostException, IOException
	{
		try
		{
			String ServerIP = "localhost";
			if (args.length > 0)
				ServerIP = args[0];
			Socket socket = new Socket(ServerIP, 9990); // 소켓 객체 생성
			System.out.println("서버와 연결이 되었습니다....");

			// 서버에서 보내는 메시지를 사용자의 콘솔에 출력하는 쓰레드.
			Thread receiver = new Receiver(socket);
			receiver.start();

			// 사용자로부터 얻은 문자열을 서버로 전송해주는 역할을 하는 쓰레드
			Thread sender = new Sender(socket);
			sender.start();

		} catch (Exception e)
		{
			System.out.println("예외[MultiClient class]:" + e);
		}
		// }
	}
}
  1. Receiver 코드
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.Socket;

public class Receiver extends Thread{

	Socket socket;
	BufferedReader in = null;

	//Socket을 매개변수로 받는 생성자
	public Receiver(Socket socket)
	{
		this.socket = socket;
		
		try {
			in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
		}catch(Exception e) {
			System.out.println("예외1: " + e);
		}
	}

	//run() 메소드 재정의
	@Override
	public void run() {
		while(in != null) {
			try {
				System.out.println(in.readLine());
			}catch(java.net.SocketException ne) {
				//여기서 while문을 멈춰 주어야 한다.
				break;
			}		
			catch(Exception e) {
				System.out.println("예외3:"+e);
			}
		}
	
		try {
			in.close();
		}catch (Exception e) {
			System.out.println("예외3:" + e);
		}
	}
}
  1. Sender 코드
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

public class Sender extends Thread
{
	Socket socket;
	PrintWriter out = null;
	String name;
	
	//생성자
	public Sender(Socket socket)
	{
		this.socket = socket;
		try {
			out = new PrintWriter(this.socket.getOutputStream(), true);
			
		}catch(Exception e) {
			System.out.println("예외S3: " + e);
		}
	}
	
	//run()메소드 재정의
	@Override
	public void run() {
		
		Scanner sc = new Scanner(System.in);
		
		try {
			//입력한 사용자이름을 서버에 보내준다
			//out.println(name);
			
			while(out != null)
			{
				try {
					String s = sc.nextLine();
					out.println(s);
					
					if(s.equals("q") || s.equals("Q")){
						break;
					}
				}catch(Exception e) {
					System.out.println("예외S1:" + e);
				}
			}
			
			out.close();
			
			socket.close();
		}catch(Exception e) {
			System.out.println("예외S2: " + e);
		}
		sc.close();
	}

}

이렇게 쓰레드까지 java 정식 교육은 모두 마치게 되었다.
그동안 짧았다면 짧았고 길었다면 긴 과정동안 포스팅을 봐주신 분들께
감사를 보내며 다음 포스팅부터는 java가 아닌 새로운 프로그래밍 언어로
찾아뵙겠다!

profile
[Java SQL HTML CSS JS Studying] 발전을 꿈꾸며 이상을 실현합니다
post-custom-banner

0개의 댓글