Joslynn의 하루

대용량 웹서비스를 위한 MSA Full-Stack 개발자 양성 과정 -19일차 노트 필기_멀티스레드(inturrpt method, Daemon Thread), 네트워크, 멀티채팅구현_220817 본문

MSA Full-Stack 개발자 양성과정/Java

대용량 웹서비스를 위한 MSA Full-Stack 개발자 양성 과정 -19일차 노트 필기_멀티스레드(inturrpt method, Daemon Thread), 네트워크, 멀티채팅구현_220817

Joslynn 2022. 8. 18. 22:13

멀티스레드

Inturrupt() 메소드

: 스레드가 일시 정지 상태에 있을 때 InterruptedException 예외를 발생시키는 역할

 

예제)

: thread 시작 후, 0.1초 일시 중지(sleep 메소드) 이후, InterruptedException이 발생하여 thread를 멈추도록 interrupt() 메소드를 호출

package ex0817.thread;

public class IterruptedExam {
	
	public static void main(String[] args) {
		
		
		System.out.println("****메인 시작합니다.*****");
		
		Thread th = new Thread(()->{
			
				while (true) {
					System.out.println("Thread 재미있다...");
					
					// 무한 루프를 일시중지 시켜야 현재 스레드를 중지시킬 수 있음; 
					if(Thread.interrupted()) break;
				}
		});
		
		th.start();
		
		try {
			Thread.sleep(100);
		} catch(InterruptedException e) {
			e.printStackTrace();
		}
		
		//interrupt()는 스레드가 잠시 일시중지 상태가 되어야만 현재 스레드를 중지시킬 수 있다.
		th.interrupt();
		
		System.out.println("****메인 종료합니다.*****");
	}

}

Daemon Thread

: 일반 스레드(Normal Thread)의 작업을 돕는 보조적 역할을 담당하는 스레드

:낮은 우선순위low priority를 가지고 있으며, 메인스레드가 종료가 되면 데몬 스레드도 강제 종료가 된다. 

 

예제)

package ex0817.thread;

public class DaemonThreadExam {
	
	public static void main(String[] args) {
		System.out.println("***메인 시작합니다.***");
		Runnable r = () ->{
			while(true) {
				System.out.println("나는 데몬 스레드입니다.");
				try {
					Thread.sleep(500);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		};
		
		
		Thread th = new Thread(r);
		th.setDaemon(true); // 데몬 스레드 - 메인 스레드가 종료되면 함께 종료
		th.start();
		
		try {
			Thread.sleep(3000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		
		System.out.println("***메인 종료합니다.***");
	}

}

 


Network

: import java.net.*;

 

*용어

- ip: 숫자 체계, 컴퓨터 네트워크 상에서 각 장치들의 고유 번호

- Domain: 문자 체계, ip를 조금 더 식별하기 쉽게

- port: 프로세스

- URL, URI

   ex) http://www.daum.net ----> http://www.daum.net:80/index.html

   통신 규약:// IP 주소 or 도메인 주소 : 포트 주소/파일 이름(자원이름)

   http의 기본 port 번호: 80 (기재하지 않아도 자동으로 열림)

 


 

네트워크를 하는 방법

 

1) UDP 방법

    : 비연결성 통신 방법  ex) 우체국

    : 일정량의 패킷(data)을 모아두었다가 한번에 전송하는 형태

    : 네트워크 부담은 낮지만 신뢰성 떨어짐(바로 전송되지 않고 중간에 데이터 손실될 수 있음)

    : DatagramPacket, DatagramSocket

 

2) TCP 방법

    : 연결성 통신방법    ex) email

    : 데이터를 보내는 동시에 바로 전송되어진다.

    : 신뢰성은 높지만 네트워크 부담은 크다.

    : ServerSocket, Socket


Server

: 클라이언트에게 여러 가지 서비스를 제공하는 것

SeverSocket 객체 = new SeverSocket(port 번호);

 

ServerSocket(객체)

: 서버 프로그램에서만 사용하는 소켓으로, 클라이언트로부터 연결 요청이 오기를 기다렸다가 연결 요청이 들어오면 클라이언트와 연결을 맺고 다른 소켓을 만드는 역할

: .accept() 메소드 : client 접속 대기, client의 요청을 받으면 Socket(접속한 client) 객체를 리턴

 

Socket(객체)

: 소켓은 두 프로그램이 네트워크를 통해 서로 통신을 수행할 수 있도록 양쪽에 생성되는 링크의 단자

: 두 소켓이 연결되면 서로 다른 프로세스끼리 데이터를 전달할 수 있다.

: client와 sever 모두 socket을 가진다 == 둘 간에 연결되어 있음

 

: .getInputStream() 메소드 : InputStream 리턴(바이트 단위 읽기), client가 보내온 byte를 읽기

                                           == Buffered InputStream 사용 등은 IO에서 담당

: .getOutputStream() 메소드: OutputStream 리턴(바이트 단위 쓰기), 연결된 client에게 데이터를 전송하기 위함

class Server{
    ServerSocket s = new ServerSocket(8000);
    while(true){
        Socket sk = s.accept();
        InputStream i = sk.getInputStream();
        OutputStream o = sk.getOutputStream();
    }
    sk.close();
}

 

Client

Socket sk = new Socket(String ip(domain or PC의 ip), port);

---> 문제가 있을 경우 Exception 발생, 없을 경우 server의 accept() 메소드가 받아줌

sk. getInputStream() : 서버가 보내온 데이터를 읽기

sk.getOutputStream(): 서버에게 데이터를 보낸다. 

sk.close(): 소켓을 다 쓰고 나면 close하는 역할

 

Server와 Client

 


데이터 통신에 유용한 IO

1. 읽기

byte 단위로 읽고 문자 단위로 변환

: InputStreamReader

 

Reader -> Buffered를 이용한 속도 향상 권장

: BufferedReader

 

2. 쓰기

byte 단위로 받아서 -> 문자 단위로 변환

: OutputStreamWriter

 

Writer -> Buffered를 이용한 속도 향상 권장

: BufferedWriter

 

**PrintWriter

: 각 타입별로 데이터를 전송할 수 있는 메소드 제공

: 타입별로 데이터 전송 시에는 BufferedWriter보다 이용하기 용이함

 


예제)

1대 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.util.ArrayList;
import java.util.List;

public class ServerChatGUI {
	Socket socket;
	String userIP;
	List<ClientSocketThread> list = new ArrayList<ServerChatGUI.ClientSocketThread>();
	
	public ServerChatGUI(){
		try (ServerSocket server = new ServerSocket(8000)) {
			
			while(true) {
				System.out.println("CLIENT 접속 대기 중");
				socket = server.accept();
				userIP = socket.getInetAddress().toString();
				System.out.println(userIP+"님이 접속되셨습니다.");
				
				// 접속된 client를 Thread로 만들어서 저장소(List)에 저장한다.
				ClientSocketThread th = new ClientSocketThread();
				list.add(th);
				
				th.start();
				
				System.out.println("현재 접속 인원: "+list.size()+" 명");
				System.out.println();
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args) {
		new ServerChatGUI();
	}
	
	/**
	 * List에 저장된 ClientSocketThread를 꺼내서 모든 User에게 메세지를 전송하는 기능
	 * */
	public void sendMessage(String message) {
		for (ClientSocketThread th : list) {
			th.pw.println(message);
		}
	}
	
	/** InnerClass
	 * */
	class ClientSocketThread extends Thread{
		// 외부에서 접근을 허용하기 위해 
		PrintWriter pw;
		BufferedReader br;
		String nikName;
		
		@Override
		public void run() {
			try {
				// 읽기
				br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
				// 쓰기
				pw = new PrintWriter(socket.getOutputStream(), true); // autoFlush;
				
				// 대화명 읽기
				nikName = br.readLine();
				
				// 모든 유저에게 "nikName님이 입장하셨습니다" 메시지를 전송
				sendMessage(nikName+"님이 입장하셨습니다.");
				
				String inputData = null;
				while ((inputData= br.readLine())!=null) {
					// 접속된 모든 client에게 데이터를 전송
					sendMessage("[ "+nikName+" ] "+inputData);		
					
				}
			} catch (Exception e) {
				//e.printStackTrace();
				// 현재 스레드에 해당하는 Client의 socket이 닫힘;
				// list에 제거
				list.remove(this);
				// 남아있는 모든 USER에게 알림
				sendMessage("[ "+nikName+" ] 님이 퇴장하셨습니다.");
				
				// 서버창에도 알린다.
				System.out.println("[ "+nikName+" ] 님이 퇴장하셨습니다. 현재 접속 인원: "+list.size()+" 명");
				
			}
		}
		
	}

}

클라이언트

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;

public class ClientChatGUI extends JFrame{
	
	// client 화면용
	JTextArea textArea = new JTextArea();
	JScrollPane jsp = new JScrollPane(textArea); // 인수로 들어간 component 영역에 스크롤바가 생김
	
	JTextField text = new JTextField();
	
	// 통신용
	Socket socket;
	PrintWriter pw;
	BufferedReader br;

	public ClientChatGUI(){ // 생성자
		super("채팅창");
		
		Container con = super.getContentPane();
		con.add(jsp, BorderLayout.CENTER);
		con.add(text, BorderLayout.SOUTH);
		
		// 옵션 설정
		textArea.setBackground(Color.PINK);
		textArea.setFocusable(false); // 커서가 가르키지 못함;

		// 화면 조정
		setSize(400,300);
		setLocationRelativeTo(null);
		setVisible(true);
		//x를클릭하면 프로그램 종료
		setDefaultCloseOperation(ClientChatGUI.EXIT_ON_CLOSE);
		
		// 서버 접속	
		this.connectToServer();
		
		// 서버가 보내주는 데이터를 받아서 textArea에 출력하는 스레드
		Thread th = new Thread(()->{
			//run 메소드
			
			try {
				String data = null;
				while((data = br.readLine())!=null) {
					textArea.append(data+"\n");
					
					//스크롤이 마지막 글자가 있는 위치로 이동시키기
					textArea.setCaretPosition(text.getText().length());
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		});
		
		th.setDaemon(true); // 메인 스레드가 종료되면 함께 종료
		th.start();
		
		// 생성자 마지막 - 이벤트 등록 (textField의 엔터)
		text.addActionListener((e)->{ // ActionEvent 인수로 받음
			//textField의 값을 읽어서 서버에 전송
			String inputData = text.getText();
			pw.println(inputData);
			//textField의 값 지움
			text.setText("");
		});
		
		
	}// 생성자 끝
	
	/**채팅을 위한 서버에 접속 기능
	 * */
	public void connectToServer() {
		try {
			socket = new Socket("127.0.0.1", 8000);
			br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			pw = new PrintWriter(socket.getOutputStream(), true);
			
			// 대화명 입력창 - this(==JFrame), inputDialog가 끝날 때까지 JFrame에 접근 불가 && user's input 리턴
			String name = JOptionPane.showInputDialog(this, "대화명을 입력해주세요.");
			
			//서버에 대화명 전송
			pw.println(name);
			
			//JFrame 창 이름 변경
			setTitle("[ "+name+" ]");
			
		} catch (Exception e) {
			e.printStackTrace();
		}
		
	}
	
	public static void main(String[] args) {
		new ClientChatGUI();
	}
}

 

Comments