티스토리 뷰

1.    스레드 병렬처리의 필요성

-      블로킹( 대기상태 )이 되는 메소드 :

-      (1) accept( ) (2) connect( ) (3) read( ) (4) write( )

-      이때, 스레드가 블로킹이 되면, 다른 작업을 수행하지 못하게 된다.

-      그렇기에, /출력할 동안 다른 클라이언트의 연결 요청 수락이 불가능해지며

-      /출력할 동안 다른 클라이언트의 입/출력이 불가능해진다.

-      그렇기에 accept( ), connect( ), read( ), write( )는 별도의 작업 스레드로 생성해야 된다. ( 이때 서버는 중개의 역할만을 하는 것이 좋다. )

 

2.    데이터 베이스

-      DatabaseOSAPP 사이에 존재하기에, Middleware라고 부른다.

 

3.    @Log4j2 – 로그의 심각도

-      log.trace("TRACE"); <- 가장 낮은 단계

-      log.debug("DEBUG");

-      log.info("INFO");

-      log.warn("WARN");

-      log.error("ERROR");

-      log.fatal("FATAL");  <- 가장 높은 단계

 


더보기

01. Multi Chat - Server구현

package tcp_practice;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import lombok.extern.log4j.Log4j2;

@Log4j2 // 롬복은 static에서만 사용이 가능하다.
// 그렇기에 lombok annotation을 중첩 클래스에 적용하기 위해서는 static을 붙여줘야 한다.
public class S10_MultiChatServer {
	
	private static final int port = 7777;
	private static final Charset charset = Charset.forName("UTF-8"); // 다국어 처리를 위해 Charset을 UTF-8로 지정
	
//	private static final Charset charset = Charset.defaultCharset(); // XX
	
	// Multicast 기반의 체팅을 구현하기 위해서는
	// 서버의 역할은 오로지 중개의 역할을 잘 해주면 된다!
	// 즉, 서버가 직접 자신의 메시지를 보내거나 받는 것이 아니라
	// 이 서버에 연결된 클라이언트들 끼리, 보내고 / 받는 메시지 처리만 중앙에서 잘 처리하면 된다.
	// 그래서, 서버에 연결된 모든 클라이언트 정보를 담은 컬렉션이 필요하다.
	// 그렇기 때문에 이 컬렉션을  Map <K,V> 로 하기로 하였다.
	
	private static Map <String,Socket> clients; // (***)
	
//	=================================================================================
	
	public S10_MultiChatServer () {
		
		log.trace("Default constructor invoked.");
		
		clients = new HashMap<>(); // HashMap 객체 생성
		
		// HashMap은 멀티스레드 환경에서 Thread-Unsafe하기 때문에, 이를 다시
		// Thread-safe하게 조작할 수 있도록 만들어줘야 하며, 이를 해주는 것이 바로 아래의 제네릭 정적 메소드이다.
		clients = Collections.<String,Socket> synchronizedMap (clients);
		
		log.debug("synchronized client list created. type : {}", clients.getClass().getName()); // Log4j2
		
	} //constructor
	
//	=================================================================================
	
	private void start () { // 서버를 시작하는 메소드
		
		log.trace("start() invoked.");
		
		try( ServerSocket serverSocket = new ServerSocket(port); ) { // 서버소켓을 특정 포트번호인 7777와 묶어서 만든다. ( 터널 형성 )
			
			log.debug("Server started."); //서버의 터널이 형성되었음을 알려줌.
			
			while(true) { // 서버는 계속 돌아가야하기 때문에, while(true)로 구현한다.
				
				log.debug("Listen on {} ......", serverSocket); // 요청받을 준비가 되었음을 알려준다.
				
				Socket sock = serverSocket.accept(); // 연결요청을 받는다.
				
				log.debug("Client connected From {}",sock); //어디 클라이언트와 연결되었는지 알려준다.
				
//				============================================================
				
				// 별도의 스레드로 다수의 클라이언트가 보낸 메시지를 수신할 수 있는 Task를 처리하도록 함.
				
				// 새로운 클라이언트가 연결될때 마다 반복하게 된다.
				ServerReceiver receiver = new ServerReceiver(sock); // Worker 스레드 객체
				receiver.start(); // Worker 스레드 시작
				
			} // while
			
		} catch ( IOException e) {
			e.printStackTrace();
		} finally {
			log.info("Server finished.");
		}// try with : serverSocket
		
	} // start
	
//	=================================================================================
	
	// ServerReceiver 클래스 생성 ( Worker 스레드 ) - 중첩 class
	// 중첩 클래스는 위 클래스와 밀접한 관계에 있고, 위 클래스 내에서만 사용이 될 때 사용한다.
	// 메소드 블록 안에서 선언된 클래스는 "로컬Class"라고 부른다.
	
	// 이 worker Thread는 새로이 연결된 클라이언트 Socket당 ServerReceiver를 하나씩 생성한다.
	@Log4j2
	static class ServerReceiver extends Thread { // 서버에 받은 것을 그대로 넘기는 클래스 ( 진짜 중개역할을 하는 서버측 스레드 ) (***)
		
		final Socket sock; // Socket을 구현해야 한다.(****)
		final String clientKey; // 새로운 클라이언트의 닉네임
		
//		=================================================================================
		
		ServerReceiver( Socket sock ){
			
			log.trace("Construcotr ({}) invoked.",sock);
			
			this.sock = sock;
			this.clientKey = sock.getRemoteSocketAddress().toString(); // 어느 IP주소와 Port번호인지
			
			// 서버가 중개역할에 충실하려면, 새롭게 연결된 클라이언트 Socket을 컬랙션으로 관리하게 하고,
			// 그 후에 publish하기 위해서 컬렉션에 저장해야 한다.
			clients.put(clientKey, sock);
			
			log.debug("Clents : {}", clients);
			
//			=================================================================================
			
			try {
				String WELCOME = String.format("<<< Client Key : %s Entered. Welcome!!", this.clientKey); // Welcome 메세지
				
				// 현재 연결된 모든 클라이언트에게 동일 메세지를 전송해주는 메소드 호출(***)
				publish("SERVER",WELCOME,true);
				
			} catch (IOException e) { // 오류가 발생한다면...
				
				// 통신( 입/출력 )에 문제가 발생한 클라이언트는 Map에서 삭제처리 
				clients.remove(this.clientKey);
				
				log.debug("clients : {}", clients);
				
				try {
					
					String BYE = String.format(">>> Client Key : %s Exited. Bye!!!", this.clientKey);
					
					// 현재 연결된 모든 클라이언트에게 동일한 메세지를 전송해주는 메소드 호출
					publish("SERVER",BYE,true); // (***)
					
				} catch(IOException e1 ) { ;; } // inner try - finnally
				
			} // try catch - outter
			
		} // ServerReceiver - constructor
		
//		=================================================================================
		
		@Override
		public void run() {
			
			log.trace("run() invoked.");
			
			try( this.sock; ){
				
				InputStream is = this.sock.getInputStream(); // 입력스트림
				
				// 이 보조 스트림은 기본 스트림에 연결해서 사용하는 보조 스트림이 아니라.
				// JVM 메모리에 바이트 데이터를 축척하기 위해서 사용하는 보조스트림이다. ( 마치 금고처럼 ) (***)
				ByteArrayOutputStream baos = new ByteArrayOutputStream();
				
				try( is; baos; ){ // 보조스트림을 먼저 닫아야 한다. 닫히는 순서는 왼쪽에서 오른쪽으로 진행된다.
					
//					=========================================
//					1. Receive Message From All Clients (***)
//					=========================================
					
					int CR =13, LF = 10; // Enter Key Code
					
					int ch; // ch = char(캐릭터)
					String RECV = null; // 받는 메시지
					
					while( (ch = is.read()) != -1) { // EOF
						
						if( ( ch != CR) & ( ch != LF)) {
							
							baos.write(ch); // 엔터키를 치기 전까지는 계속 입력을 받는다.
							
						} else {
							
							if( ch == LF) { // 엔터키를 쳤다면
								
								RECV = baos.toString(charset); // baos에 저장된 문자열을 charset을 통해 UTF-8로 받아들인다.
								log.info("RECV from {} : {} ", this.clientKey, RECV); // 받은 문자열 줄력
								
								baos.reset(); // baos에서 저장되었던 문자열을 지우고, 새로운 문자를 받을 준비를 한다.
								
//								=========================================
//								2. Publish Message To All Clients (***)
//								=========================================
								
								publish(this.sock.getRemoteSocketAddress().toString(), RECV, false);
								
							} // inner if
							
						}// if - else
						
					} // while : EOF
					
				} // try : is, boas
				
			} catch ( IOException e) {
				e.printStackTrace();
				
			} finally { // 어떤 식으로든 끊어져 더 이상 메시지를 교환할 수 없는 클라이언트는 Map에서 제거하자!
				log.info("Client {} Disconnected", this.sock);
				
				clients.remove(this.clientKey, this.sock);
				log.debug("clients: {}", clients);
				
				try {
					
					String BYE = String.format(">>> Client Key: %s Exited. Bye !!!", this.clientKey);
					publish("SERVER", BYE, true); // (*****)
				
				} catch(IOException e) { ;; } // try catch
				
			} // try : this.sock
			
		} // run
		
	} // end class - ServerReceiver
	
//	=================================================================================
	
	// 현재 연결되어 있는 Connected All Clients에게 메시지 전달
	private static void publish (String fromKey, String message, boolean toSelf) throws IOException {
		// 이렇게 예외를 지정하게 되면, 하나의 클라이언트에서 예외가 발생하면 다른 클라이언트도 멈춰보리기에 따로 처리해야 한다.
		
		log.trace("publish({}, {}) invoked.", fromKey, message);
		
		Set<String> keys = clients.keySet(); // HashMap에서 key값에 keys 값을 입력한다.
		
		for(String key : keys) { // Map 객체의 Key를 traverse하는 중
			
			log.debug("publish to the client key: {}", key);
			
			// 메세지를 받은 클라이언트와 key가 동일하지 않거나, to-self 매개변수가 true일때만( 메세지를 보낸 자신한테도 보낼 것인가)
			// 받은 메세지를 전송해라! false일 때에는 자신한테는 메세지를 보내지 않게 만들어라!
			if( !key.equals(fromKey) | toSelf ) {
					
				int CR=13, LF=10; // Enter Key Code

				Socket sock = clients.get(key); // HashMap에서 key값을 가져온다.
				OutputStream os = sock.getOutputStream(); // 출력 스트림
				
				os.write((fromKey+" : "+message).getBytes(charset)); // 보낸 이 : 메세지를 UTF-8로 출력한다.
				os.write(CR);
				os.write(LF); // 메세지를 다 읽어들이면 엔터키 치게 한다.
				
				os.flush(); // 출력 스트림이기에 flush는 필수이다.
				
//				log.info("SENT: {}", message);
				log.info("SENT from {} : {}", fromKey, message); // 누구로부터 어떤 메세지가 왔는지 표시
				
			} else {
				
				log.debug("Excluding client itself key: {} to publish.", fromKey);
				
			} // if-else
			
		} // enhanced for
		
	} // publish
	
//	=================================================================================

	public static void main(String[] args) {
		log.trace("main({}) invoked", Arrays.toString(args));
		
//		Charset.availableCharsets().forEach((key, cs) -> {
//			log.debug("{}", key);
//		});

		new S10_MultiChatServer().start();
	} // main
	
//	=================================================================================
	
//	출력
	
//	11:00:50.179 TRACE --- [main           ] t.S10_MultiChatServer.main - main([]) invoked
//	11:00:50.182 TRACE --- [main           ] t.S10_MultiChatServer.<init> - Default constructor invoked.
//	11:00:50.183 DEBUG --- [main           ] t.S10_MultiChatServer.<init> - synchronized client list created. type : java.util.Collections$SynchronizedMap
//	11:00:50.183 TRACE --- [main           ] t.S10_MultiChatServer.start - start() invoked.
//	11:00:50.192 DEBUG --- [main           ] t.S10_MultiChatServer.start - Server started.
//	11:00:50.192 DEBUG --- [main           ] t.S10_MultiChatServer.start - Listen on ServerSocket[addr=0.0.0.0/0.0.0.0,localport=7777] ......
	
//	=================================================================================

} // end class

더보기

02. Multi Chat - Client구현

package tcp_practice;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Arrays;
import java.util.NoSuchElementException;
import java.util.Scanner;

import lombok.extern.log4j.Log4j2;


@Log4j2
public class S10_MultiChatClient {
	
	// 채팅 서버의 연결정보 선언 ( 상수 )
	private static final String host = "localhost"; // Loopback address = 127.0.0.1
	private static final int port = 7777; // Port Number 

//	=================================================================
	
	public static void main(String[] args) throws IOException, InterruptedException {
		
		log.trace("main({}) invoked.", Arrays.toString(args));
		
		try ( Socket sock = new Socket(host, port); ) { // 채팅서버 연결
			
			log.info("Connected to the server addr: {}, port: {}", host, port);

			Thread R = new Receiver(sock.getInputStream()); // 메세지 수신 스레드 생성 및 시작
			R.start();
			
			Thread S = new Sender(sock.getOutputStream()); // 메세지 송신 스레드 생성 및 시작
			S.start();
			
//			=================================================================
			
			R.join(); // R ( 수신 스레드 ) 스레드가 종료될 때까지 기다린다.
			S.join(); // S ( 송신 스레드 ) 스레드가 종료될 때까지 기다린다.
			
//			=================================================================
			
		} finally {
			log.info("Disconnected.");
		} // try-with-resources : sock
		
	} // main
	
//	=================================================================
	
	// "멤버 클래스"라고 부른다. ( - Sender클래스는 default인 상태이다. )
	// 맴버 클래스는 이 클래스와 밀접한 관계가 있고, 이 클래스 블록 안에서만 사용할 때 사용된다.
	static class Sender extends Thread { // 메세지 송신
		
		private final OutputStream os;

//		=================================================================
		
		Sender(OutputStream os) {
			log.trace("constructor({}) invoked.", os);

			this.os = os;
		} // constructor
		
//		=================================================================
		
		@Override
		public void run() {
			log.trace("run() invoked.");
			
			Scanner scanner = new Scanner(System.in);
			
			try ( this.os; scanner; ) { // 스캐너부터 닫아줘야 한다.
				
				log.debug("Sender started.");

				int CR=13, LF=10; // Enter Key Code
				
				String line = null;
				
				while(( line = scanner.nextLine() ) != null) { // EOF ( 스캐너에서 EOF는 null이다. )
//					log.info("line: {}", line);
					
					String message = line.trim(); // 가운데에 있는 공백 외의 공백을 제거해준다. ( 양쪽 끝단에 있는 공백을 제거 )
					os.write(message.getBytes("UTF-8")); // 인코딩 -> String을 Byte로 변환해준다.
					
					// Sent CRLF (***) - 엔터키를 활용해 메세지를 구분해줘야 한다. ( 메세지의 끝임을 강제로 정했다. )
					os.write(CR);
					os.write(LF);
					
					os.flush(); // 출력 스트림 강제 flush해주기
					
//					log.info("SENT: {}", message);
					log.info(message); // 메세지를 출력한다.
					
				} // while
				
			} catch(IOException | IllegalStateException | NoSuchElementException e) {
				e.printStackTrace();
				
			} finally {
				log.debug("Sender stopped.");
			} // try-with-resources

		} // run

	} // end class : Sender
	
//	=================================================================
	
	static class Receiver extends Thread { // 메세지 수신
		private final InputStream is;
		
		Receiver(InputStream is) {
			log.trace("constructor({}) invoked.", is);
			
			this.is = is;
			
		} // constructor
		
//		=================================================================
		
		@Override
		public void run() {
			
			log.trace("run() invoked."); // run 메소드가 실행되었다.
			
			ByteArrayOutputStream baos = new ByteArrayOutputStream(); // (***)
			
//			=================================================================
			
			try (this.is; baos;) { // 보조 스트림부터 닫아주고 입력스트림을 닫아준다.
				
				log.debug("Receiver started.");
				
//				=================================================================
				int ch; // char ( 캐릭터 )
				int CR=13, LF=10; // Enter Key Code
//				=================================================================
				
				while((ch=is.read()) != -1) { // EOF
					
//					log.info("ch: {}", ch);
					
					if(ch != CR && ch != LF) {
						
						baos.write(ch);
						
					} else {
//						log.info("\t+ excluded: {}", ch);
						
						if(ch == LF) {
							String recv = baos.toString("UTF-8");
//							log.info("RECV: {}", recv);
							log.info(recv);
							
							baos.reset();
						} // inner - if
						
					} // Outer if-else
					
				} // while
				
//				=================================================================
				
			} catch(IOException e) {
				e.printStackTrace();
			} finally {
				log.debug("Receiver stopped.");
			} // try-with-resources

		} // run
		
//		=================================================================

	} // end class : Receiver

} // end class

더보기

03. 과제 - Annagrams

package hackers_rank_practice;

import java.util.Arrays;
import java.util.Scanner;

public class Anagrams {

    static boolean isAnagram(String a, String b) {
        
        // 소문자로 변환
        String lowerStringA = a.toLowerCase();
        String lowerStringB = b.toLowerCase();
        
        // String A 한글자씩 배열로 생성 - arr1
        char [] arr1 = lowerStringA.toCharArray();
        
        for ( int i = 0; i < arr1.length; i++){
            
            arr1[i] = lowerStringB.charAt(i);
            
         } // enhance for - A
        
        // String B 한글자씩 배열로 생성 - arr2
        char [] arr2 = lowerStringB.toCharArray();
        
        for ( int i = 0; i < arr2.length; i++){
            arr2[i] = lowerStringB.charAt(i);
         } // enhance for - B
         
         // 오름차순으로 배열 정리
         Arrays.sort(arr1);
         Arrays.sort(arr2);
              
        if ( Arrays.toString(arr1).equals( Arrays.toString(arr2) ) ){ // 꼭  Arrays.toString하기!! (**)
            return true;
         } else {
            return false;
         } // if
        
    } // isAnagram

  public static void main(String[] args) {
	  
	    // 스캐너 
        Scanner scan = new Scanner(System.in);
        String a = scan.next();
        String b = scan.next();
        scan.close();
        
        // isAnagram 메소드를 활용하여 블린 타입으로 판단하여 결과 반환
        boolean ret = isAnagram(a, b);
        System.out.println( (ret) ? "Anagrams" : "Not Anagrams" );
        
    } // main
  
} //end class

 

728x90
댓글
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
최근에 올라온 글
Total
Today
Yesterday
공지사항