24일차 멀티 쓰레드와 네트웤

쿠우·2022년 4월 25일
0

메세지를 주고 받는 서버와 클라이언트 코드를 만들자!

대략적인 간단 로직

1.서버클래스에서 서버소켓생성 후 연결소켓 생성 메시지를 받는 클래스와 돌려주는 클래스를 실행한다.
2.클라이언트에서 IP와 port에 연결 후 메세지를 주고 받는 클래스를 실행한다.
-공통점은 쓰레드를 상속받아 메세지를 받는 클래스와 주는 클래스를 참조한다.

1. 서버코드

package thisjava;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Arrays;

import lombok.extern.log4j.Log4j2;


@Log4j2
public class MultiThreadSocketServer {
	private static final int port = 7777;
	
	public static void main(String[] args) throws IOException {
		log.debug( "main({}) invoked", Arrays.toString(args));
	
		ServerSocket serversock = new ServerSocket(port);

	
		try(serversock){
		
			while(true) { 
				
				log.info("Listenning on {} ......", serversock);
				
				Socket sock = serversock.accept();
				log.info("\t + Client connected from {}", sock);
				
//				------------------
				
				new Receiver01("Receiver01",sock).start();
				

				new Sender01("Sender01", sock).start();
				
			} // while

			
		}// try - with - resources
		

		
	}// main

}//end class

2.클라이언트 코드

package thisjava;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.Arrays;

import lombok.extern.log4j.Log4j2;

@Log4j2
public class ClientEx01 {
	
	private static final String host = "127.0.0.1";
	private static final int port = 7777;
	
	public static void main(String[] args) {
		log.debug("main({}) invoked. ",  Arrays.toString(args));

		
		try {
			// 1.서버로 연결할 Socket 객체를 우선생성하고
			Socket sock = new Socket();
			// 2. connect() 메소드로 실제 서버에 연결요청 생성 및 연결 생성(터널뚤림)
			sock.connect(new InetSocketAddress(host, port), 1000); // 1000분의 1초 , 연결시도 타임아웃 시간 설정가능 
			
			log.info("Connected to the server addr : {} , port: {}  ", host, port);
			
			//-------------------------
			//1. 메세지보내기 
			
			new Sender01("Sender07", sock).start();
	
			
			// 2.메세지 수신 
			
			new Receiver01("Reciver01", sock).start();
			
			
		}catch (IOException e) {
			e.printStackTrace();
		}// try - catch
		
	}//main

}// end class

3. 메세지를 받는 코드

  1. 소켓변수와 스트림변수는 생성자를 통해서만 접근할 수 있게 해준다.
  2. Thread 클래스의 run 메서드를 오버라이딩 해준다.
  3. https://atsky.tistory.com/8 < ByteArrayOutputStream 시그니처분석설명
  4. 받은 값을 조건을 비교해서 baos 변수에 담아준다.
    -조건:끝을만나면 종료 엔터를 만나면 출력하고 baos초기화
package thisjava;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;

import lombok.extern.log4j.Log4j2;


@Log4j2
public class Receiver01 extends Thread{
	private Socket sock;
	private InputStream is;
	
	public Receiver01(String threadName, Socket sock) {
		log.debug("Constructor({}) invoked.", sock);
		
		this.sock =sock;
		
		super.setName(threadName+ "-" + super.getName());
	
		try {this.is =this.sock.getInputStream();} catch (IOException e) {;;}
		
	}// Constructor	

	
	@Override
	public void run() {
		
		log.debug("run() invoked");
		
		//바이트배열 기반의 보조스트림: 주로 메모리에, 바이트로 구성된 데이터를 저장하는 용도로 사용
		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		//기본스트림에 붙는 보조스트림이 아닌 별도의 보조스트림 
		
		try(baos){
			
			int ch; 
			int CR=13, LF = 10 ;
			// 엔터키를 누르면 , 두개의 키코드 발생: CRLF => 코드값으로 하면 , 13/10 2개의 코드값 발생 
			// Mac/ Linux/ UNIX 에서는 다르다 : LF,10 한개의 키코드값만 발생 					
			
			
			while((ch=is.read())!= -1) { // 입력스트림의 EOF(-1)을 만나기까지 계속 바이트를 읽어냄
				
				if(ch != CR && ch!= LF) {
					baos.write(ch);
				} else {
					
					if(ch == LF) {
						String recv = baos.toString("utf-8");
						log.info("RECV: {}",recv);
						
						baos.reset();
					} // if 
					
				}// if -else
				
			}//while
			
		} catch (Exception e) {
			;;
		}finally {
			try {this.is.close();}  catch(IOException e) {;;}
			try {this.sock.close();}catch(IOException e) {;;}
			
		} // try catch finally
		
		log.info("Done.");
		
	}//run
	
	 
} //end class

4.메세지를 보내는 코드

  1. 소켓과 스트림의 변수는 생성자로만 초기화할 수 있게 은닉해준다.
  2. Thread 클래스의 run 메서드를 오버라이딩 해준다.
  3. 보낼메세지를 UTF-8형식으로 반복하고 엔터키를 같이 보낸다.
  4. 1초간 쓰레드를 멈춘다. > 3,4 10번 반복
  5. 끝나는 문자 출력
package thisjava;

import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;

import lombok.extern.log4j.Log4j2;

@Log4j2
public class Sender01 extends Thread {
	private Socket sock; 
	private OutputStream os;
	
	
	public Sender01(String threadName, Socket sock) {
		log.debug("Constructor({}) invoked.", sock);
		
		this.sock = sock;
		
		super.setName(threadName+ "-" + super.getName());
		
		try {this.os =this.sock.getOutputStream();} catch (IOException e) {;;}
		
		
	}//constructor
	
	
	@Override
	public void run() {
		
		log.debug("run() invoked");
		
		try{
			
			
			int CR=13, LF = 10 ;
			// 엔터키를 누르면 , 두개의 키코드 발생: CRLF => 코드값으로 하면 , 13/10 2개의 코드값 발생 
			// Mac/ Linux/ UNIX 에서는 다르다 : LF,10 한개의 키코드값만 발생 					
			
			for(int i=0; i<10 ; i++) {
			
				String message = "클라이언트 메시지-" + i;
				os.write(message.getBytes("utf-8"));
				
				// Sen CRLF : 왜 엔터키를 보내느냐 ???  메세지의 설계는 엔터키까지
				os.write(CR);
				os.write(LF);
				
				os.flush();
				log.info("SENT: {}", message);
			
				Thread.sleep(1000 *1);
			}//for
			
			Thread.sleep(1000);
			
		} catch (Exception e) {
			e.printStackTrace();
		}finally {
			try {this.os.close();}  catch(IOException e) {;;}
			try {this.sock.close();}catch(IOException e) {;;}
			
		} // try catch finally
		
		log.info("Done.");
		
	}//run
	
} // end class

-클라이언트 클래스를 3번 연속으로 실행하면서 서버가 서버의 역할을 위해 멀티쓰레드가 필요한 이유

만약 싱글쓰레드 였으면 한개 출력하고 끝나고 또 한개 출력하고 했다. 그럼 클라이언트들에 대기시간이 생김

-네트워크 다루면서 항상 조심해야할 점!

서버코드 한번 더 실행해서 포트가 중첩으로 오류가 남

따라서!!!

scoop install sysinternals 이렇게 다운받아준다
-윈도우 네트워크 모니터링 프로그램
실행은 tcpview 실행


IP와 port를 확인 할 수 있음 자바에서 실행한 네트워크 쓰레드도 확인 가능! 자원 다 사용 하고 닫아서 Time Wait


쓰레드가 필요한 것은 알았다 그럼 쓰레드는 무엇일까?

하나의 프로그램을 실행하면 프로그램은 프로세스라는 단위로 나눠진다!
그럼 프로세스는 또 쓰레드라는 단위로 나눠진다. 따라서 쓰레드는 프로그램을 실행하는 최소단위다

1. 쓰레드의 필요성을 나타내는 클래스

package thread;

import java.awt.Toolkit;

public class BeepPrintExample02 {
	
	//2개의 task를 main 이란 이름의 Single Thread만으로는 
	// 순차실행할 수 밖에 없음을 보여주는 예제
	public static void main(String[] args) {
		Toolkit tookit = Toolkit.getDefaultToolkit();
		
		for(int i= 0; i <5 ; i ++) {
			tookit.beep();
			
			try {
				Thread.sleep(500);
			} catch (Exception e) {
				;;
			}// try -catch
		}//for
		for(int i= 0; i <5 ; i ++) {
			System.out.println("띵");
			
			try {
				Thread.sleep(500);
			} catch (Exception e) {
				;;
			}// try -catch
		}//for
//한가지 끝나고 나머지 작업을 실행 		

	} // main

} // end class

2-1. 쓰레드를 사용하는 다양한 방법

package thread;

import java.awt.Toolkit;

public class BeepPrintExample01 {
	
	//2개의 task를 각각 담당하는 멀티 스레드를 만들어서 
	// 병렬로 동시에 각각 task를 실행한다.
	
	public static void main(String[] args) {
		
		//-------------1. Runnable한 첫번째 방법 
//		Runnable beepTask = new BeepTask(); // Runnable한 클래스 생성 
//		Thread wokerthread = new Thread(beepTask); // Runnable한 클래스의 run을 품은 쓰레드 객체를 생성
//		
//		wokerthread.start();
	
	// --------------2. 익명구현객체의 코딩 기법을 통한, 익명구현객체 생성 
	
//	Thread thread = new Thread(new Runnable() {
//		
//		@Override
//		public void run() {
//			Toolkit toolkit = Toolkit.getDefaultToolkit();
//			
//			for(int i= 0; i <5 ; i ++) {
//				toolkit.beep();
//				
//				try {
//					Thread.sleep(500);
//				} catch (Exception e) {
//					;;
//				}// try -catch
//			}//for
//		}//run
//	}); // 익명구현 객체코딩 기법을 통한, 익명구현객체생성
//	thread.start();
	
	// --------------3. 람다를 이용한 익명구현객체 생성
	
	Thread wokerthread = new Thread(new Runnable() {
		
		@Override
		public void run() {
			Toolkit toolkit = Toolkit.getDefaultToolkit();
			
			for(int i= 0; i <5 ; i ++) {
				toolkit.beep();
				
				try {
					Thread.sleep(500);
				} catch (Exception e) {
					;;
				}// try -catch
			}//for
		}//run
	}); // Lamda Expression(람다 표현식 -> 생성: 익명구현객체)
	wokerthread.start();
	
	
	
	
	
//		---------
		for(int i= 0; i <5 ; i ++) {
			System.out.println("띵");
			
			try {
				Thread.sleep(500);
			} catch (Exception e) {
				;;
			}// try -catch
		}//for
//한가지 끝나고 나머지 작업을 실행 		

	} // main

} // end class

-----재료 클래스-------

package thread;

import java.awt.Toolkit;

public class BeepTask implements Runnable {

	
	// 이 메소드가 바로,
	// 각각의 Worker Thread의 Entry Point like main method 역할 수행
	@Override
	public void run() {
		
		Toolkit tookit = Toolkit.getDefaultToolkit();
		for(int i= 0; i <5 ; i ++) {
			tookit.beep();
			
			try {
				Thread.sleep(500);
			} catch (Exception e) {
				;;
			}// try -catch
		}//for
		
	}// run

}// end class

2-2. 쓰레드를 사용하는 다양한 방법

package thread;

import java.awt.Toolkit;

public class BeepPrintExample03 {

	public static void main(String[] args) {
		
//		------4 thread의 자손클래스 직접 이용 
//		Thread thread =new BeepTask();
//		thread.start();
		
		
//		------5.쓰레드의  자식객체 익명구현객체식으로 생성 방법
		Thread thread =new Thread() {
			
			@Override
			public void run() {
				Toolkit tookit = Toolkit.getDefaultToolkit();
				for(int i= 0; i <5 ; i ++) {
					tookit.beep();
					
					try {
						Thread.sleep(500);
					} catch (Exception e) {
						;;
					}// try -catch
		
				}//for
			} // run
		}; // 익명자식객체 코딩 기법을 통한 , 익명의 Thread 클래스의 자식객체 생성 
		thread.start();
		
		
		for(int i= 0; i <5 ; i ++) {
			System.out.println("띵");
			
			try {
				Thread.sleep(500);
			} catch (Exception e) {
				;;
			}// try -catch
		}//for
		
		
	}// main 

}// end class

-----재료 클래스-------

package thread;

import java.awt.Toolkit;

import lombok.NoArgsConstructor;
import lombok.extern.log4j.Log4j2;

//Thread를 직접상속받은 자식클래스에서
// 부모클래스로부터 물려받은 메소드 중에, run() 메소드를 오버라이딩 수행
@Log4j2
@NoArgsConstructor
public class BeepThread extends Thread{
	
		@Override
		public void run() {
			
			Toolkit tookit = Toolkit.getDefaultToolkit();
			for(int i= 0; i <5 ; i ++) {
				tookit.beep();
				
				try {
					Thread.sleep(500);
				} catch (Exception e) {
					;;
				}// try -catch
			}//for
		
		
		}//run
	
}//end class

메인 메서드가 끝나도 별개로 실행된 쓰레드는 종료되기전까지 계속 실행

profile
일단 흐자

1개의 댓글

comment-user-thumbnail
2022년 4월 25일

과제 코드 분석있다 내일 아침에 해야겠다

답글 달기

관련 채용 정보