JAVA 웹개발 과정 국비 30일차 정리

30일차

※ 배우는 과정이므로 정확하지 않을 수 있습니다.
잘못된 점은 알려주시면 수정하도록 하겠습니다!


1. 방명록 시스템 - 예제 이어서..

1) 작성자 또는 내용으로 검색하기

작성자 또는 내용으로 검색하기 기능을 만들려고 한다.
쿼리문에 LIKE를 써야하는데 어떻게 써주는 것이 좋을까?
SQL Developer에서 해준 것처럼 ? 양 옆으로 %를 써주면 될까?

public List<GuestbookDTO> search(String keyword) throws Exception {
	String sql = "SELECT * FROM GuestBook WHERE LIKE %?% OR message LIKE %?%";
}

하지만 위와 같이 코드를 쓴다면 ?에 keyword가 들어갈 때 양 옆으로 ‘ ‘(작은따옴표)가 붙어서 결국에는 %’keyword’% 이런 형식으로 쿼리문이 입력될 것이다.
따라서 이것은 방법이 아니다.

public List<GuestbookDTO> search(String keyword) throws Exception {
	String sql = "SELECT * FROM GuestBook WHERE LIKE ? OR message LIKE ?";
	
	try(Connection con = this.getConnection();
			PreparedStatement pstat = con.prepareStatement(sql);){
		pstat.setString(1, "%"+keyword+"%");
		pstat.setString(2, "%"+keyword+"%");
		try(ResultSet rs = pstat.executeQuery();){
			List<GuestbookDTO> list = new ArrayList<>();
			
			while(rs.next()) {
				GuestbookDTO dto = new GuestbookDTO();
				dto.setSeq(rs.getInt("seq"));
				dto.setWriter(rs.getString("writer"));
				dto.setMessage(rs.getString("message"));
				dto.setWrite_date(rs.getDate("write_date"));
				list.add(dto);
			}
		}
	}
}

위와 같이 pstat.setString 메소드에 keyword를 넣을 때 “%”를 양 옆에 붙여서 함께 입력시켜주어야 ‘%keyword%’ 형식으로 쿼리문 안쪽에 들어가게 된다.

2. 네트워크 파일 전송

어제 배운 것들은 모두 네트워크로 데이터를 전송하는 방법이었다.
오늘은 네트워크로 파일을 전송하는 방법을 배웠다.

네트워크로 파일을 전송하는 방법은 아래와 같이 3 step으로 진행된다.

  1. 서버의 HDD에 있는 파일을 서버 RAM으로 꺼내오기
  2. 서버 RAM에 있는 파일을 클라이언트 RAM으로 전달하기
  3. 클라이언트 RAM에 있는 파일을 클라이언트 HDD에 넣기

1) 서버 HDD -> 서버 RAM

① 대상 파일 지정

우선 어떤 파일을 이동시킬 것인지 대상을 지정한다.
File 인스턴스를 하나 만들어서 대상의 경로를 적어준다.
이 File 인스턴스는 내용은 가지고 있지 않고 파일의 메타데이터(속성 등의 정보)만 가지고 있다.
경로를 지정할 때 역슬래시 두개와 슬래시 한개는 동일하게 작용한다.

File target = new File("C:\\Users\\user\\Desktop\\보낼폴더\\파일.jpg");
// File target = new File("C:/Users/user/Desktop/보낼폴더/파일.jpg"); 

File 클래스는 아래와 같이 속성을 확인할 수 있는 여러 메소드를 가지고 있다.

System.out.println("파일 존재 : " + target.exists());
System.out.println("파일 사이즈 : " + target.length());
System.out.println("파일 경로 : " + target.getAbsolutePath());
System.out.println("파일 이름 : " + target.getName());

② 스트림 개방

파일을 골랐으면, RAM은 하드디스크에 있는 수많은 파일 중에 대상으로 지정된 파일을 찾는다. 이 때, RAM은 파일의 내용까지는 알지 못한다. 즉, 멀리서 발견만 한 상태이다.
이 파일의 내용까지 RAM이 알기 위해서는 파일에 빨대를 푹 꽂아서 내용을 쭉쭉 빨아와야한다. 여기서 이 빨대의 역할을 하는 것이 File Stream이다.
Input이냐 Output이냐를 나누는 기준은 RAM 을 기준으로 본다.
RAM 기준으로 파일을 받는 것이므로 Input Stream을 쓴다.

FileInputStream fis = new FileInputStream(target);

③ 스트림 업그레이드

데이터를 받아올 때와 마찬가지로 현재 스트림은 아주 얇은 빨대여서 내용을 빨아오기는 어려운 상태이다. 따라서 큰 빨대로 업그레이드를 해준다.

DataInputStream dis = new DataInputStream(fis);

④ 파일 내용 가져오기

dis의 readFully 메소드로 파일 내용을 전부 받아올 수 있는데 문제는 어디에 저장할 것인가? 이다.
파일 내용은 byte들의 묶음으로 다룰 수 있다. 따라서 파일 내용을 보관할 byte 배열을 만들어주는데 이 때, byte 배열의 사이즈는 위에서 언급했던 File 클래스의 length 메소드로 받아올 수 있다.
readFully 메소드는 byte 배열을 파라미터로 받음으로 아래와 같이 코드를 써줄 수 있다.

byte[] fileContents = new byte[(int)target.length()];
dis.readFully(fileContents);

주의할 점은 File 클래스의 length 메소드는 long 형으로 값을 반환한다.
배열 사이즈는 int값만을 받을 수 있기 때문에 형변환을 해줘야 한다.

2) 서버 RAM -> 클라이언트 RAM

① 네트워크 연결

서버에서 클라이언트로 보내야 하기 때문에 Socket을 만들어 스트림을 개방시켜준다.
파일을 내보내야하기 때문에 DataOutputStream을 써야한다.

ServerSocket server = new ServerSocket(30000);
System.out.println("연결을 대기 중 입니다..");
Socket sock = server.accept();
System.out.println(sock.getInetAddress() + "로부터 연결되었습니다.");
DataOutputStream dos = new DataOutputStream(sock.getOutputStream());

② 파일 전송하기

1번에서 readFully로 파일 내용을 다 받아서 저장했기 때문에, 남은 것은 파일 내용을 저장한 저장소의 내용을 전달하기만 하면 된다.
이 때, 클라이언트에서도 배열의 사이즈를 알아야 저장소를 생성하기 때문에 저장소의 크기와 내용 둘 다를 flush 해준다.

dos.writeInt(fileContents.length);
dos.write(fileContents);
dos.flush();

클라이언트 클래스에서도 마찬가지로 네트워크를 연결하고 스트림을 개방한 뒤, 배열의 사이즈를 받아 배열을 생성하고 파일의 내용을 받아오면 된다.

// Client Class
Socket client = new Socket("127.0.0.1", 30000);
DataInputStream dis = new DataInputStream(client.getInputStream());

int size = dis.readInt();
byte[] fileContents = new byte[size];
dis.readFully(fileContents);

3) 클라이언트 RAM -> 클라이언트 HDD

배열에 저장한 파일 내용을 HDD로 저장해야 한다.
1번과 마찬가지로 저장할 파일을 지정(생성)한 뒤 빨대를 꽂아 파일 내용을 넘긴다.

① 대상 파일 생성

1번과 약간 다른 점은, 파일 경로가 실제로 존재하지 않는 경로여야 한다는 것이다.
그래야 덮어씌어지지 않고 새로 파일을 생성하여 다운받을 수 있다.

File target = new File("C:\\Users\\user\\Desktop\\받을폴더\\파일.jpg");

② 스트림 개방

RAM을 기준으로 데이터(파일내용)를 내보내는 것이므로 Output Stream을 이용한다.

FileOutputStream fos = new FileOutputStream(target);

③ 스트림 업그레이드

DataOutputStream dos = new DataOutputStream(fos);

④ 파일 내용 집어넣기

dos의 write메소드는 byte배열을 받는다.
따라서 이 메소드를 사용하여 파일 내용을 전달한 뒤 flush를 해준다.
try with resource문을 따로 적용하지 않았으므로 close도 해준다.

dos.write(fileContents);
dos.flush();
dos.close();

4) 폴더 대상으로 File 클래스 사용하기

위의 과정들은 모두 파일을 대상으로 이루어진 실습이였다.
폴더를 대상으로 아래와 같은 메소드들을 이용하면 다양한 응용이 가능하다.

① 파일? 폴더?

파일인지 폴더인지 알고 싶다면 isFile 메소드와 isDirectory 메소드를 이용한다.

File f = new File("E:\\2021_09_웹응용과정\\folder");
System.out.println(f.isFile()); // false
System.out.println(f.isDirectory()); // true

② 폴더 안의 모든 파일 가져오기

File[] fileList = f.listFiles();

for (File tmp : list) {
	System.out.println(tmp.getName() + ":" + tmp.length());
}

3. 서버-클라이언트 파일 전송 프로그램 - 예제

아래는 예제를 통한 실습 소스 코드이다.
try with resource 문과 클라이언트 입력 등을 응용하여 연습했다.

1) Server Class

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
	public static void main(String[] args) throws Exception {

		// 서버소켓은 프로그램 전체에서 하나 켜졌다가 프로그램 꺼질때 날라가기 때문에 close를 안해줘도 괜찮긴 하다.
		// 정석은 try with resource로 해줘야 함
		ServerSocket server = new ServerSocket(22000);
		System.out.println("연결을 대기중 입니다..");
		while(true) { // 다른 사용자를 기다리는 반복문

			try(Socket sock = server.accept();
					DataOutputStream dos = new DataOutputStream(sock.getOutputStream());
					DataInputStream dis = new DataInputStream(sock.getInputStream());){
				System.out.println(sock.getInetAddress() + "로부터 연결되었습니다.");
				// 여기에 while반복문을 만들면 한명이 주구장창 다운받고 나가는 프로그램 
				File folder = new File("C:\\Users\\user\\Desktop\\보낼폴더");
				File[] fileList = folder.listFiles();

				dos.writeInt(fileList.length);
				dos.flush();

				for (File file : fileList) {
					dos.writeUTF(file.getName());
					dos.flush();
				}// 1. 전송할 수 있는 파일 목록을 생성해서 클라이언트에게 전달한다.

				String fileName = dis.readUTF(); 
				System.out.println("클라이언트가 선택한 파일 이름 : " + fileName);
				// 4. 클라이언트가 입력해서 전송한 파일이름을 화면에 출력한다.

				File target = new File(folder.getPath() + "\\"+ fileName);

				try(
						FileInputStream fis = new FileInputStream(target);
						DataInputStream disFile = new DataInputStream(fis);){
					byte[] fileContents = new byte[(int)target.length()];
					disFile.readFully(fileContents);
					// 5. 클라이언트가 보낸 파일을 RAM으로 로딩

					dos.writeInt(fileContents.length);
					dos.write(fileContents);
					dos.flush();
					// 6. 로딩된 파일의 내용을 클라이언트에게 전송한다. 
				}
			}catch(Exception e) {
				System.out.println("접속자가 접속을 종료했습니다.");
			}
		}
	}
}

2) Client Class

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.net.Socket;
import java.util.Scanner;

public class Client {
	public static void main(String[] args) throws Exception {

		try(
				Scanner sc = new Scanner(System.in);
				Socket client = new Socket("127.0.0.1", 22000);
				DataInputStream dis = new DataInputStream(client.getInputStream());
				DataOutputStream dos = new DataOutputStream(client.getOutputStream());
				){		
			int size = dis.readInt();

			for (int i=0;i<size;i++) {
				System.out.println(dis.readUTF());
			} // 2. 서버가 보내오는 파일 명단을 받아서 화면에 출력한다.

			System.out.print("받을 파일이름 입력 : ");
			String fileName = sc.nextLine();
			dos.writeUTF(fileName);
			dos.flush();
			// 3. 서버가 보내온 명단 중에, 한개 파일을 선택하여 이름을 입력하고 
			// 입력한 이름을 서버에 전송한다.

			int fileContentsSize = dis.readInt();
			byte[] fileContents = new byte[fileContentsSize];
			dis.readFully(fileContents);
			//7. 서버에서 전달되어오는 파일 내용을 받아서 RAM에 적재한다.
			
			File f = new File("C:\\Users\\user\\Desktop\\받을폴더\\" + fileName);
			try(
					FileOutputStream fos = new FileOutputStream(f);
					DataOutputStream dosFile = new DataOutputStream(fos);
					){
				dosFile.write(fileContents);
				dosFile.flush();
				dosFile.close();
				// 8. 3번에서 선택한 파일 이름으로 클라이언트 경로에 저장한다.
			}
		}
	}
}

4. Thread

쓰레드는 너무 어렵고 깊은 내용이라 아직은 이해하기 어렵지만, 웹 개발자라면 꼭 알아야하는 개념이므로 조금씩 공부해보라고 하셨다.
수업시간에는 간단한 개념과 사용 방법을 배웠다.

1) Thread 란?

어떠한 프로그램 내에서, 특히 프로세스 내에서 일을 처리하는 작업의 단위

현재 만드는 프로그램들은 모두 하나의 쓰레드를 가진 싱글 쓰레드 프로세스이다.
예를 들면 아래와 같은 for문이 두번 도는 프로그램이 있다고 하면, 당연히 출력은 0 ~ 99 다음 0 ~ 99 가 출력되고 끝난다.

public class Exam_01 {
	public static void main(String[] args) {
	
		for (int i=0;i<100;i++) {
			System.out.println(i);
		}
		
		for (int i=0;i<100;i++) {
			System.out.println(i);
		}
	}
}

그러나 상황에 따라서는 이 for문 두개를 한꺼번에 실행해야 하는 프로그램을 만들어야 할 수도 있다. 이때 사용되는 개념이 바로 Thread(쓰레드)이다.
그래서 Thread 는 ‘병렬처리’라는 단어와 밀접한 연관을 가진다.

이 코드를 작성하고 컴파일하면 프로그램이라는 결과물이 나온다.
이 프로그램은 현재 나의 HDD에 저장이 되어있는 상태인데, 프로그램을 실행한다고 하면 이 HDD에 있는 내용이 CPU에 의해 처리되기 위해서 RAM이라는 장치에 로딩이 된다.
이 때 RAM으로 가져온 프로그램을 프로세스라고 부른다.
프로세스 : HDD에 있는 프로그램을 실행해서 메모리에 로딩한 상태. 즉, 실행 중인 프로그램을 의미

CPU는 1개인데, 실행하는 프로세스는 많다.
CPU는 한순간 한개의 프로세스밖에 처리하지 못한다.
따라서 O/S는 CPU가 처리해야 하는 것 중에 어떤 것이 중요한가를 쓰레드에 CPU 사용시간을 배정하여 관리한다.

쓰레드는 프로세스를 처리하는 일꾼이며, 프로세스 하나당 하나의 쓰레드는 꼭 필요하다.
쓰레드는 CPU를 쓸 시간을 배정받고 왔다갔다하며 프로세스를 처리한다. 이것이 바로 CPU의 시분할 처리 기법이다.

시분할 처리기법 때문에 위의 for문을 돌리는 프로그램은 사실 CPU가 다른 프로세스와 왔다갔다하며 처리를 하고 있는 것이다. 그러나 그 순간이 너무 짧아서 사람의 눈으로는 볼 수 없는 것이다.
즉 일꾼(쓰레드)이 for문을 돌리다가 CPU에 할당된 시간이 다 되면 그대로 멈춰있다가 또 다시 배정받은 시간이 오면 다시 일을 하고 이런식으로 프로세스가 진행되어 for문이 출력되는 것이다.

위의 프로그램과는 다른 여러개 쓰레드를 만들어서 실행시키는 프로세스를 멀티 쓰레드 프로세스라고 한다.

2) Thread 사용 방법

① Thread 상속받기

Thread 클래스를 상속받는 사용자 정의 클래스를 생성한다.

class Worker extends Thread {

}

② run 메소드 오버라이딩

Thread로부터 상속받은 public void run 메소드를 오버라이드하여 병렬처리 코드를 작성한다.

class Worker extends Thread {
	
	public void run() {
		// 쓰레드를 생성할 코드 넣기
		for(int i=0;i<100;i++) {
			System.out.print(i + " ");
		}	
		
	}
}

③ 인스턴스 생성

작성된 클래스로 인스턴스를 생성한다.

public class Exam_01 {
	public static void main(String[] args) {
			
		Worker w = new Worker();
	}
}

④ start 메소드 호출

생성된 인스턴스로부터 start method를 호출한다. 주의할 점은 run 메소드를 호출하는 것이 아니라는 것이다.

public class Exam_01 {
	public static void main(String[] args) {
		
		Worker w = new Worker();
		w.start();
		
		for(int i=0;i<100;i++) {
			System.out.print(i + " ");
		}
				
	}
}

⑤ 출력값 확인

두 for문이 섞여서 출력되는 것을 확인할 수 있다.

0 1 2 3 0 4 1 5 6 7 8 9 10 2 3 4 5 6 11 7 12 8 13 14 15 16 17 18 9 19 10 ...

댓글남기기