Thread와 Socket 통신 이해하기
Thread 개념
Thread 개념 완벽하게 이해하기Thread는 타임슬라이싱(Time Slicing)하여 문맥교환(Context Switching)을 수행하면서 CUP에게 여러개의 일을 동시에 하도록 만드는 일을 한다....
Thread 특징 요약
- Thread는 static 함수로 생명 주기가 함수 안에서 시작하고 끝난다.
- Thread는 트립(trip)시 문맥을 알아야한다.
- Thread는 Runnable 타입이여야하고 반드시 run 함수를 가지고 있어야한다.
- Main Thread는 Sub Thread를 실행만 시키고 자기 일을 한다.
- 자바는 Thread가 하나라도 돌아가고 있으면 종료되지 않는다.
Thread 예제
예제1
package ex01; //새로운 쓰레드는 반드시 러너블 타입이여야하고 run함수를 가지고 있어야한다. class NewThread implements Runnable{ @Override public void run() { // target for (int i = 1; i < 11; i++) { System.out.println("새로운 스레드 : " + i); try { Thread.sleep(1000); // 메인 스레드가 1000ms 쉬게 만든다. } catch (InterruptedException e) { // Interrupted : 방해하다. 간섭하다. e.printStackTrace(); } } } } public class ThreadEx01 { public static void main(String[] args) { // 생명 시작 // **새로 만든 서브 쓰레드 사용하기** Thread t1 = new Thread(new NewThread()); // 괄호 안에는 target(run 함수를 들고 있는 대상)이 들어간다. t1.start(); // 새로운 쓰레드 시작하기 -> run 함수가 실행 for (int i = 1; i < 11; i++) { System.out.println("메인 스레드 : " + i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } //생명 끝 } }
예제2
package ex01; // 메인 쓰레드의 일이 끝나더라도 서브 쓰레드는 종료되지 않는다. class NewThread2 implements Runnable{ @Override public void run() { for (int i = 1; i < 21; i++) { System.out.println("새로운 스레드 : " + i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } public class ThreadEx02 { public static void main(String[] args) { Thread t1 = new Thread(new NewThread2()); t1.start(); for (int i = 1; i < 11; i++) { System.out.println("메인 스레드 : " + i); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } }
Socket 통신
웹 서버에서 통신은 요청이 들어왔을 때 단발적으로 이루어지는데 반해 소켓 통신은 연결이 계속 이어지는 stateful 서버를 사용한다.
Thread를 사용하여 Socket 통신하는 방법
원리 이해하기
- 서버 소켓과 클라이언트 소켓이 같은 포트에 연결한다.
- 클라이언트가 접속할 때까지 락을 걸어준다.(accept 함수를 사용 : 내부적으로 while문이 돌고 있다.)
- OS의 상시 감시자 리스너에 의해 클라이언트의 접속을 확인한다.(accept 함수 호출)
- 클라이언트가 접속하면 Thread를 새로 생성하여 서브 소켓과 연결하고 서버 소켓과의 연결은 끊는다.
- 클라이언트와 소켓에 각각 Buffer를 달아 서로 통신할 수 있게 만든다.
- 서버 소켓은 또 다른 클라이언트의 통신을 받을 수 있게 된다.
예제로 이해하기
예제1.
서버 소켓과 클라이언트 소켓을 만들어 서로 통신할 수 있게 만든다.
package ex02; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; // 소켓 서버 = stateFul public class MyServerSocket { private ServerSocket serverSocket; private Socket socket; private BufferedReader br; public MyServerSocket() { try { serverSocket = new ServerSocket(10000); System.out.println("클라이언트로부터 접속 대기중"); socket = serverSocket.accept(); // accept(리스너에 의해 호출) : 클라이언트가 접속할 때까지 락이 걸림. -> 내부적으로 while(반복) 돌고있다. System.out.println("클라이언트 연결 완료"); // getInputStream : 클라이언트로부터 읽는다. BufferedReader : 바이트를 문자로 바꿔서 읽음 br = new BufferedReader(new InputStreamReader(socket.getInputStream())); String input =null; while((input = br.readLine()) != null) { System.out.println("클라이언트 : "+input); } } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { new MyServerSocket(); } }
package ex02; import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.Socket; import java.net.UnknownHostException; import java.util.Scanner; public class MyClientSocket { private Socket socket; private BufferedWriter bw; private PrintWriter pw; private MyClientSocket() { try { socket = new Socket("localhost",10000); bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); //pw = new PrintWriter(socket.getOutputStream(), true); //auto Flush Scanner sc = new Scanner(System.in); while(true) { String keyboardInput = sc.nextLine(); // 락 걸림(내부적으로 리스너 작동중) //pw.println(); printWriter는 이게 \n 대신해줌 bw.write(keyboardInput+"\n"); // \n : 소켓 통신에서 메시지의 끝을 의미 bw.flush(); } } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { new MyClientSocket(); } }
예제2.
서버 소켓과 클라이언트 소켓을 만들어 여러 명이 통신할 수 있게 만든다.
package ex03; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; // 소켓 서버 = stateFul public class MyServerSocket { private ServerSocket serverSocket; public MyServerSocket() { try { serverSocket = new ServerSocket(10000); while(true) { // 메인 스레드 = 데몬 스레드 System.out.println("클라이언트로부터 접속 대기중"); // Socket을 지역 변수로 받아 하나의 소켓을 공유하지 않게 만든다. Socket socket = serverSocket.accept(); // accept(리스너에 의해 호출) : 클라이언트가 접속할 때까지 락이 걸림. -> 내부적으로 while(반복) 돌고있다. System.out.println("클라이언트 연결 완료"); Thread t = new Thread(new InnerThread(socket)); // 지역 변수로 선언한 소켓 넘겨 t.start(); } } catch (IOException e) { System.out.println("클라이언트와의 연결에 실패하였습니다."); } } // 내부 클래스 -> 나만 사용할 때 class InnerThread implements Runnable { private BufferedReader br; private Socket socket; public InnerThread(Socket socket) { // 소켓을 받아준다. this.socket = socket; } @Override public void run() { try { // getInputStream : 클라이언트로부터 읽는다. BufferedReader : 바이트를 문자로 바꿔서 읽음 br = new BufferedReader(new InputStreamReader(socket.getInputStream())); String input =null; // 겁나 바빠요 -> target 만들어서 일을 맡긴다. while((input = br.readLine()) != null) { System.out.println("클라이언트 : "+input); } } catch (Exception e) { System.out.println("클라이언트와의 연결이 종료되었습니다."); try { socket.close(); br.close(); } catch (Exception e2) { System.out.println("메모리 릭이 발생하였습니다."); } } } } public static void main(String[] args) { new MyServerSocket(); } }
package ex03; import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.net.Socket; import java.net.UnknownHostException; import java.util.Scanner; public class MyClientSocket { private Socket socket; private BufferedWriter bw; private PrintWriter pw; private MyClientSocket() { try { socket = new Socket("localhost",10000); bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())); //pw = new PrintWriter(socket.getOutputStream(), true); //auto Flush Scanner sc = new Scanner(System.in); while(true) { String keyboardInput = sc.nextLine(); // 락 걸림(내부적으로 리스너 작동중) //pw.println(); printWriter는 이게 \n 대신해줌 bw.write(keyboardInput+"\n"); // \n : 소켓 통신에서 메시지의 끝을 의미 bw.flush(); } } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { new MyClientSocket(); } }
개념이해를 위한 참고 포스팅
스프링을 배우기 위해 꼭 알아야할 자바 기본 문법스프링은 IoC(Inversion of Control) 컨테이너 관리권을 가지고 있어 어노테이션을 사용하여 의존성 주입을 한다......
스프링을 배우기 위해 꼭 알아야할 웹 용어 정리스프링부트 2강 -웹 기본 개념
html, http, browser, url
아파치와 톰캣
Stream
Stateless와 Stateful
Session과 Cookies
Tomcat의 Scope...
Thread와 비동기 비교하기동기적 프로그래밍은 테스크(Task)를 순차적, 직렬적으로 수행하도록 하여 어떤 테스크가 수행 중이면 다음 테스크는 대기하게하는 방식의 프로그래밍이다....