[자바]Java 정석 기본편 제네릭스 12장 이후
제네릭스, 열거형, 어노테이션
제네릭스 : 다양의 타입의 객체를 다루는 메서드나 컴파일 시 타입 체크를 해주는 기능.
객체 타입 컴파일 시 타입 안정성을 높이고 형 변환의 번거로움이 줄어든다.
제네릭스의 장점
- 타입 안정성을 제공한다.
- 타입 체크와 형변환을 생략할 수 있으므로 코드가 간결해진다.
타입변수
- ArrayList 클래스의 선언에서 클래스의 이름 옆의 <> 안에 있는 E를 타입변수라고 하며 일반적으로는 Type의 첫글자인 T를 쓴다.
물론 T만 쓰는게 아니고 다른걸 써도 된다.
ArrayList< E > 의 경우 Element의 E를 쓰거나 Map< K,V >와 같이 여러개일 경우 콤마로도 구분이 가능하다.
그리고 ArrayList와 같은 제네릭 클래스 생성시 참조변수와 생성자에 타입 변수 E 대신 Tv와 같은 실제타입을 지정해 줘야한다.
ArrayList < Tv > tvList = new ArrayList< Tv >();
제네릭스의 용어
class Box < T > {} 라는 제네릭 클래스 BOX 가 선언되어 있을 떄
BOX< T > : 제네릭 클래스 ‘T의 Box’ 또는 ‘T Box’라고 읽는다.
T: 타입 변수 또는 타입 매개변수(T는 타입 문자) Box : 원시타입(raw type)
와일드 카드
- 제네릭 클래스를 생성시 참조변수에 지정된 제네릭 타입과 생성자에 지정된 제네릭 타입은 일치해야 한다.
ArrayList< Tv > list = new ArrayList< Tv >()
만약 일치하지 않으면 컴파일 에러가 난다.
그럼 제네릭 타이벵 다형성을 쓰는 법은 없나?
제네릭 타입으로 와일드 카드를 사용하면 된다.
와일드 카드는 기호 “?”를 사용하는데 ‘extends’와 ‘super로 상한, 하한을 제한할 수 있다.
< ? extends T > : 와일드 카드의 상한 제한. T와 그 자손들만 가능 < ? super T > : 와일드 카드의 하한 제한 . T와 그 조상들만 가능 < ? > : 제한 없음. 모든 타입이 가능 < ? extends Object >와 동일.
어노테이션이란
- 주석처럼 프로그래밍 언어에 영향을 미치지 않으면서도 다른 프로그램에게 유용한 정보를 제공할 수 있다라는 장점이 있다.( 어노테이션의 뜻은 주석, 주해 , 메모이다.)
쓰레드
프로세스(Process)와 쓰레드(thread)
- 프로세스란 간단히 말해서 실행중인 프로그램이다. 프로개름을 실행하면 OS로 부터 실행에 필요한 자원(메모리)을 할당받아 프로세스가 된다.
프로세스는 프로그램을 수행하는데 필요한 데이터와 메모리 등 자원 그리고 쓰레드로 구성되어 있으며, 프로세스의 자원을 이용해서 실제로 작업을 수행하는 것이 쓰레드다.
그래서 모든 프로세스에는 최소한 하나 이상의 쓰레드가 존재하며, 둘 이상의 쓰레드를 가진 프로세스를 ‘멀티쓰레드 프로세스’라고 한다.
멀티쓰레딩의 장단점
- DOS 와 같은 OS는 한번에 한 가지 작업만 할 수 있다. 반면에 윈도우와 같은 멀티 태스킹이 가능한 OS는 동시에 여러 작업을 수행할 수 있다.
싱글 쓰레드 프로그램과 멀티 쓰레드 프로그램의 차이도 이와 같다고 생각하면 된다.
멀티쓰레드의 장점.
- CPU의 사용률을 향상시킨다.
- 자원을 보다 효율적으로 사용할 수 있다.
- 사용자에 대한 응답성이 향상된다.
- 작업이 분리되어 코드가 간결해진다.
메신저로 채팅하면서 파일을 다운로드 받거나 음성대화를 나눌 수 있는 것이 가능한 이유가 바로 멀티쓰레드로 작성되어있기 때문이다.
만약 싱글 쓰레드로 작성되어 있다면 파일을 다운 받을 시 다른 일(채팅)을 전혀 할 수 없을 것이다.
여러 사용자에게 서비스를 해주는 서버인 경우 멀티스레드로 작성하는 것은 필수적이여서 하나의 서버프로세스가 여러개의 스레드를 생성해서 스레드와 사용자의 요청이 일대이로 처리되도록 프로그래밍 해야한다.
만일 싱글스레드로 서버 프로그램을 작성한다면 사용자의 요청마다 새로운 프로세스를 생성해야하는 데 프로세스를 생성하는 것은 쓰레드를 생성하는 것에 비해 더 많은 시간과 메모리 공간이 필요하므로 더 많은 수의 사용자 요청을 서비스 하기 어렵다.
그러나 멀티스레드가 꼭 좋은것만 있는것은 아닌데 멀티쓰레드는 여러 쓰레드가 같은 프로세스 내에서 자원을 공유하면서 작업을 하기 때문에 발생할 수 있는 동기화, 교착상태와 같은 문제들을 고려해서 신중히 프로그래밍 해야한다.
쓰레드의 구현과 실행
- 쓰레드를 구현하는 건 Thread 클래스 상속 받는 것과 Runnable 인터페이스를 구현하는 법 모두 두가지가 있다. 어느 쪽을 선택해도 별 차이는 없지만 Thread 클래스를 상속한다면 다른 클래스를 상속받을수 없으므로 Runnable 인터페이스를 구현하는 법이 일반적이다.
Runnable 인터페이스를 구현하는 방법은 재사용성이 높고 코드의 일관성을 유지할 수 있기 떄문에 보다 객체지향적인 방법이다.
쓰레드의 실행 - start()
쓰레드 실행했다고 자동으로 실행되는 것은 아니다. start()를 호출해야만 쓰레드가 실행된다.
t1.start(); // 쓰레드 1을 실행시킨다.
t2.start(); // 쓰레드 2를 실행시킨다.
사실은 start()가 호출되어도 바로 실행이 아니라 실행 대기상태에 있다가 자기 차례가 되어야 실행된다. 물론 대기중인 쓰레드가 하나도 없으면 곧바로 실행상태가 된다.
쓰레드의 실행순서는 OS의 스케줄러가 작성한 스케줄에 의해 결정된다.
또, 한번 실행이 종료된 쓰레드는 다시 실행할 수 없다. 하나의 쓰레드에 대해 한번만 start()가 호출이 가능하다.
즉 쓰레드를 한번 더 수행하려면 쓰레드 객체를 new로 한번 더 다시 생성해서 수행해야 하낟.
start(), run()
start,run 의 차이? 왜 쓰레드 실행시 run이 아닌 start를 호출하나?
main에서 run을 호출하는 건 생성된 쓰레드를 실행하는게 아니라 단순히 클래스에 선언된 메서드를 호출하는 것일 뿐이다.
반면 start()는 새로운 쓰레드가 작업 실행하는데 필요한 호출스택(call stack)을 생성한 뒤 run을 호출해서 생성된 호출스택에 run()이 첫번째로 올라가게 한다.
모든 쓰레드는 독립적인 작업을 수행하기 위해 자기만의 호출스택을 필요로 하고 새로운 쓰레드를 생성하고 실행시킬 때 마다 새로운 호출스택이 생성되고 쓰레드가 종료되면 작업에 사용된 호출스택은 소멸된다.
main 쓰레드
- main메서드 작업 수행하는 것도 쓰레드고 이를 main쓰레드라고 한다.
public static void main 메서드 사용하면서 이미 쓰레드를 쓰고 있었다.
지금까지 main이 수행 마치면 프로그램 종료되었으나 만약 다른 쓰레드가 있으면 모든 쓰레드가 종료되기 전까지 프로그램이 종료되지 않는다.
실행 중인 사용자 쓰레드가 하나도 없을 때 프로그램은 종료된다.
쓰레드는 사용자 쓰레드(‘user thread’, non-daemon thread)와 데몬쓰레드 (daemon thread). 두 종류가 있다.
싱글 쓰레드와 멀티 쓰레드
전에 멀티쓰레드의 장점을 수행했는데 마냥 장점만 있진 않다.
멀티쓰레드가 싱글쓰레드보다 더 걸릴수 있는데 이유는 프로세스나 쓰레드간 작업전환인 컨텍스트 스위칭(context switching) 때문이다.
작업전환시엔 현재 진행중인 작업상태, 예를 들어 다음에 실행할 위치, 프로그램 카운터 등의 정보를 저장하고, 읽어오는 시간이 소요된다.
참고로 쓰레드의 스위칭에 비해 프로세스 스위칭이 더 많은 정보를 저장해야하므로 더 많은 시간이 소요된다.
그래서 싱글코어에서 단순히 cpu만 쓰는 계산작업이면 멀티쓰레드보다 싱글쓰레드로 프로그래밍 하는 것이 효율적이다.
쓰레드의 I/O 블락킹
두 쓰레드가 서로 다른 자원 쓰는 작업시앤 싱글쓰레드 프로세스보다 멀티 프로세스가 더 효율적이다.
예를 들면 사용자로부터 데이터를 입력받는 작업, 네트워크로 파일 주고받는 작업, 프린터로 파일 출력하는 작업과 같이 외부기기와의 입출력을 필요로 하는 경우가 이에 해당한다.
쓰레드의 우선순위
쓰레드는 우선순위(priority)라는 속성(멤버변수)를 가지고 있는데 이 우선순위 값에 따라 스레드가 얻는 실행시간이 달라진다.
쓰레드가 수행하는 작업의 중요도에 따라 쓰레드의 우선순위를 서로 다르게 지정하여 특정 쓰레드가 더 많은 작업 시간을 갖도록 할 수 있다.
void setPriority(int newPriority) // 쓰레드의 우선순위를 지정한 값으로 변경한다. int getPrioirty() // 쓰레드의 우선순위를 반환한다.
데몬 쓰레드
데몬 쓰레드는 다른 일반 쓰레드(데몬 쓰레드가 아닌 쓰레드)의 작업을 돕는 보조적인 역할을 수행하는 쓰레드다.
일반쓰레드가 모두 종료되면 데몬쓰레드는 강제로 자동 종료된다. 왜냐하면 일반 쓰레드가 종료되면 데몬 쓰레듸의 존재가 의미가 없다.
이 점을 제외하고는 데몬쓰레드와 일반 쓰레드는 다르지 않다.
데몬쓰레드의 예로는 가비지 컬렉터, 워드프로세서의 자동저장, 화면 갱신 등이 있다. 데몬쓰레드는 일반 쓰레드의 작성방법과 실행방법이 같으며 다만 쓰레드를 생성한 뒤 실행전 setDaemon(true)를 호출하기만 하면 된다. 그리고 데몬 쓰레드가 생성한 쓰레드는 자동적으로 데몬 쓰레드가 된다.
boolean isDaemon() : 쓰레드가 데몬쓰레드인지 확인한다. 데몬쓰레드면 true를 반환한다. void setDaemon(boolean on) : 쓰레드를 데몬 쓰레드로 또는 사용자 쓰레드로 변경한다 매개변수 on의 값을 true로 지정시 데몬쓰레드가 된다.
쓰레드의 상태
- 쓰레드는 생성된 후부터 종료까지 여러 상태를 가질수 있다
new : 쓰레드가 생성되고 아직 start() 가 호출되지 않은 상태 Runnable : 실행중 또는 실행 가능한 상태 Blocked : 동기화 블럭에 의해서 일시정지된 상태(lock이 풀릴때 까지 기다리는 상태) Waiting, Timed_wating: 쓰레드의 작업이 종료되지는 않았지만 실행 가능하지 않은(unrunnable) 일시정지의 상태, Timed_Waiting 은 일시정지 시간이 지정된 겨웅를 의미 Terminated: 쓰레드의 작업이 종료된 상태
쓰레드의 실행제어
쓰레드가 어려운 이유는 동기화와 스케줄링이다.
우선순위를 통해 쓰레드간의 스케줄링으로 앞에서 하긴 했지만 이것만으로 부족하다. 효율적인 멀티쓰레드 프로그램을 만들기 위해 보다 정교한 스케줄링으로 프로세스에게 주어진 자원과 시간을 여러쓰레드가 낭비없이 사용하도록 프로그래밍 해야한다.
sleep: 지정된 시간 동안 스레드를 일시정지 시킨다, 지정된 시간이 지나고 나면 자동으로 다시 실행대기상태가 된다.
join: 지정된 시간동안 쓰레드가 실행되도록 한다. 지정된 시간이 지나거나 작업이 종료되면 join()을 호출한 쓰레드로 다시 돌아와 실행을 계속한다.
interrupt: sleep()이나 join()에 의해 일시정지상태인 쓰레드를 깨워서 실행대기상태로 만든다.
stop : 쓰레드를 즉시 종료시킨다.
suspend: 쓰레드를 일시정지 시킨다. resume()을 호출하면 다시 실행대기 상태가 된다.
yield(): 실행 중에 자신에게 주어진 실행시간을 다른 쓰레드에게 양보(yield)하고 자신은 실행대기상태가 된다.
쓰레드의 동기화(synchronization)
싱글쓰레드 프로세스의 경우 프로세스 내에서 단 하나의 쓰레드만 작업하기 때문에 프로세스 자원을 가지고 작업하는 데 별 문제가 없지만, 멀티 쓰레드 프로세스의 경우 여러 쓰레드가 같은 프로세스 내의 자원을 공유해서 작업하기 때문에 서로의 작업에 영향을 주게 된다.
만일 쓰레드 A가 작업 중에 다른 쓰레드 B로 제어권이 넘어갔을 때, 쓰레드 A가 작업하던 공유데이터를 쓰레드 B가 임의로 변경하였다면, 다시 쓰레드 A가 제어권으를 받아서 나머지 작업을 마쳤을 때 원래 의도해던것과 다른 결과를 얻을 수 있다.
이러한 일이 발생하는 걸 방지하기 위해 한 쓰레드가 특정 작업을 끝마치기 전 까지 다른 쓰레드에 의해 방해받지 않도록 하는 것이 필요하다. 그래서 도입된게 임계영역(critical section) 과 잠금(락, lock) 이다.
공유 데이터를 사용하는 코드영역을 임계영역으로 지정해두고, 공유데이터(객체)가 가지고 있는 lock을 획득한 단 하나의 쓰레드만 이 영역 내의 코드를 수행하게 한다.
그리고 해당 쓰레드가 임계 여영ㄱ내의 모든 코드를 수행하고 벗어나서 lock을 반납해야만 다른 쓰레드가 반납된 lcok을 획득하여 임계영역의 코드를 수행할 수 있게 된다.
이처럼 한 쓰레드가 진행중인 작업을 다른 쓰레드가 간섭하지 못하도록 막는 것을 쓰레드의 동기화(synchronization)이라고 한다.
쓰레드의 동기화 - 한 쓰레드가 진행중인 작업을 다른 쓰레드가 간섭하지 못하게 막는 것.
wait()과 notify()
synchronized로 동기화해서 공유데이터를 보호하는것 까지는 좋은데, 특정 쓰레드가 객체의 락을 가진 상태로 오랜 시간을 보내지 않도록 하는것도 중요하다.
만약 계좌에 출금할 돈이 부족해서 한 쓰레드가 락을 보유한 채로 돈이 입금 될 때까지 기다리면, 다른 쓰레드들은 모두 해당 객체의 락을 기다리느라 다른 작업들도 원활히 되지 않을 것이다.
이러한 상황을 개선하기 위해 고안된 것이 wait과 notify이다 .동기화 된 임계영역의 코드를 수행하다가 작업을 더이상 진행할 상황이 아니면 일단 wait()을 호출하여 쓰레드가 락을 반납하고 기다리게 한다. 그러면 다른 쓰레드가 락을 얻어 해당 객체에 대한 작업을 수행할 수 있게 된다.
나중에 작업을 진행할 수 있는 상황이 되면 notify()를 호출해서 작업을 중단했던 쓰레드가 다시 락을 얻어 작업을 진행할 수 있게 된다.
wait(), notify(), notifyAll()
- object에 정의되어 있다.
- 동기화 블록(synchronized 블록) 내에서만 사용할 수 있다.
- 보다 효율적인 동기화를 가능하게 한다.
람다와 스트림
람다식
람다식은 간단히 말해서 메서드를 하나의 식(expression)으로 표현한 것이다. 람다식은 함수를 간략하면서도 명확하게 표현할 수 있게 해준다.
메서드를 람다식으로 표현하면 메서드의 이름과 반환값이 없어지므로 람다식을 익명함수라고도 한다.
스트림
지금까지 우리는 많은 수의 데이터를 다룰 때 컬렉션이나 배열에 데이터를 담고 원하는 결과를 얻기 위해 for문과 iterator를 이용해서 코드를 작성했다. 그러나 이런방식은 너무 길고 알아보기 어렵다. 그리고 재사용도 떨어진다.
또 데이터 소스마다 다른방식으로 다뤄야 한다. 예를 들어 리스트는 sort사용시 collection이나 iterator 같은 인터페이스로 컬렉션 다루는 방식을 표준화 했지만 각 컬렉션엔 같은 기능의 메서드가 중복되어 정의되어 있다. 예를 들어 List를 정렬할때 Collection.sort를 써야하고, 배열 정렬시엔 Array.sort를 쓰는 것 처럼
이러한 문제점을 해결하려 만든게 스트림이다. 스트림은 데이터 소스를 추상화 하고 데이터를 다루는데 자주 사용되는 메서드를 정의해놓았다. 데이터 소스를 추상화 했다는 것은 데이터 소스가 무엇이던 같은 방식으로 다룰수 있게 된 것과 코드의 재사용성이 높아진 것을 의미한다.
입출력
입출력(I/O)과 스트림(stream)
I/O란 입출력을 말하고 컴퓨터 내부 또는 외부의 장치와 프로그램간의 데이터를 주고 받는 것을 말한다.
예를 들면 sysout 을 이용해서 화면에 데이터를 출력한다거나 키보드로부터 데이터를 입력받는다든가 이러는게 가장 기본적인 입출력의 예이다.
스트림(Stream)
자바에서 입출력을 수행하려면 즉, 어느 한쪽에서 다른 쪽으로 데이터를 전달하려면 두 대상을 연결하고 데이터를 전송할 수 있는 무언가가 필요한데, 이것을 스트림이라고 정의했다.
위에서 정의한 Stream과 입출력에서의 스트림은 같은 용어를 쓰지만 다른개념이다.
스트림이란 데이터를 운반하는데 사용되는 연결통로이다.
스트림은 연속적인 데이터의 흐름을 물에 비유해서 붙여진 이름인데, 여러가지로 유사한 점이 많다. 물이 한쪽방향으로 흐르는 것과 같이 스트림은 단방향 통신만 가능하기 때문에 하나의 스트림으로 입력과 출력을 동시에 처리할 수 없다.
그래서 입력과 출력을 동시에 수행하려면 입력을 위한 입력 스트림(input stream)과 출력스트림(output stream), 모두 2개의 스트림이 필요하다.
스트림은 먼저보낸 데이터를 먼저 받게 되어있으며 중간에 건너뜀 없이 연속적으로 데이터를 주고 받는다.
큐와 같은 FIFO 구조로 되어있다고 생각하면 된다.
보조스트림
위에서 언급한 스트림 외에도 스트림의 기능을 보완하기 위한 보조스트림이 제공된다.
보조스트림은 실제 데이터를 주고받는 스트림이 아니여서 데이터를 입출력하는 기능은 없지만 스트림의 기능을 향상시키거나 새로운 기능을 추가할 수 있다.
보조스트림만으로는 입출력을 처리할 수 없고 스트림을 먼저 생성한 후에 이를 이용해서 보조스트림을 생성해야 한다.
문자 기반 스트림 - Reader, Writer
지금까지 위에서 본 스트림은 바이트 기반의 스트림이였다. 바이트 기반은 입출력 단위가 1바이트 라는 뜻이다. 이미 알고 있는 것과 같이 C언어와 달리 Java에서는 한 문자를 의미하는 char형이 1byte가 아니라 2byte이기 때문에 바이트 기반의 스트림으로 2byte인 문자를 처리하는 데에는 어려움이 있다. 이를 보완하기 위해 문자기반의 스트림이 제공된다.
문자 데이터를 입출력 할때는 바이트 기반의 스트림 대신 문자기반의 스트림을 사용한다.
표준 입출력 (Standard I/O)
표준 입출력은 콘솔(console, 도스창)을 통한 데이터 입력과 콘솔로의 데이터 출력을 의미한다.
자바에선 표준 입출력(standard I/O)를 위해 3가지 입출력 스트림, System.in, System.out, System.err를 제공하는 데 이 들은 자바 어플리케이션의 실행과 동시에 사용할 수 있게 자동적으로 생성되기 때문에 개발자가 별도로 스트림을 생성하는 코드를 작성하지 않고도 사용이 가능하다.
System.in : 콘솔로부터 데이터를 입력받는데 사용(표준 출력) System.out : 콘솔로 데이터를 출력하는데 사용(표준 입력) System.err: 콘솔로 데이터를 출력하는데 사용(표준 입력)
File
파일은 기본적이면서 가장 많이 쓰는 입출력 대상이기 떄문에 중요하다.
직렬화(Serialization)
- 직렬화란 객체를 데이터 스트림으로 만드는 것을 말한다.
다시 얘기하면 객체에 저장된 데이터를 스트림에 쓰기(write)위해 연속적인 (serial) 데이터로 변환시크는 것을 말한다.
반대로 스트림으로 부터 데이터를 읽어서 객체를 만드는 것을 역직렬화(deserialization)라 한다.
직렬화라는 용어 때문에 어렵게 느껴질 수 있는데 사실 객체를 저장하거나 전송하려면 이렇게 할 수 밖에 없다.
앞서 객체에 대해 설명했지만 여기서 객체란 무엇이고 객체를 저장한다는것은 무엇인가에 대해 정리하고 넘어가는 것이 좋다.
보통 예제 그림에서 이해를 돕기 위해 객체를 생성하면 인스턴스 변수와 메서드를 함께 그리곤 했지만 사실 객체에는 메서드가 포함되지 않는다.
인스턴스 변수는 인스턴스마다 다른 값을 가질 수 있어야 하기 떄문에 별도의 메모리 공간이 필요하지만 메서드는 변하는 것이 아니라 메모리를 낭비해가면서 인스턴스마다 같은 내용의 코드(메서드)를 포함시킬 필요는 없다.
그래서 객체를 저장한다는 것은 객체의 모든 인스턴스 변수의 값을 저장한다는 것과 같은 의미이다.
어떤 객체를 저장하고자 한다면 현재 객체의 모든 인스턴스 변수의 값을 저장하기만 하면 된다.
그리고 저장했던 객체를 다시 생성하려면 객체를 생성한 후에 저장했던 값을 읽어서 생성한 객체의 인스턴스 변수에 저장하면 된다.
네트워킹
네트워킹이란
- 두 대 이상의 컴퓨터를 케이블로 연결하여 네트워크를 구성하는 것을 말한다.
java.net패키지를 쓰면 네트워크 어플리케이션의 데이터 통신 부분을 쉽게 쓸 수 있으며 간단한 네트워크 어플리케이션은 단 몇줄의 자바 코드만으로 작성이 가능하다.
클라이언트와 서버
클라이언트/ 서버는 컴퓨터간의 관계를 역할로 구분하는 개념이다. 서버(server)는 서비스를 제공하는 컴퓨터이고, 클라이언트는 서비스를 사용하는 컴퓨터가 된다.
일반적으로 서버는 다수의 클라이언트에게 서비스를 제공하므로 고사양의 하드웨어가 일반적이나 사양으로 서버와 클라이언트를 구분하는게 아니고 서비스를 제공하는 소프트웨어가 실행되는 컴퓨터를 서버라고 한다.
서비스는 서버가 클라이언트로부터 요청받은 작업을 처리하여 그 결과를 제공하는 것을 뜻한다. 서버가 제공하는 서비스의 종류에 따라 파일서버(file server), 메일서버(mail server), 어플리케이션 서버 등이 있다.
서버에 접속하는 클라이언트의 수에 따라 하나의 서버가 여러가지 서비스를 제공하기도 하고 하나의 서비스를 여러대의 서버로 제공하기도 한다.
서버가 서비스를 제공하기 위해서는 서버 프로그램이 있어야 하고 클라이언트가 서비스를 제공받으려면 서버프로그램과 연결할 수 있는 클라이언트 프로그램이 있어야 하나다.
예를들어 웹서버에 접속해서 정보를 얻으려면 웹브라우저(클라이언트 프로그램)가 있어야 하고 FTP 서버에 접속해서 파일을 전송받기 위해서는 알FTP 와 같은 FTP 클라이언트 프로그램이 필요하다.
일반 PC의 경우 서버에 접속하는 클라이언트를 수행하지만, FTP Serv-U와 같은 FTP 서버프로그램이나 Tomcat과 같은 웹서버 프로그램을 설치하면 서버역할도 수행할 수 있다. 파일공유 프로그램인 토렌트 같은 프로그램은 클라이언트 프로그램과 서버프로그램을 하나로 합친 것으로 이를 설치한 컴퓨터는 클라이언트인 동시에 서버가 되어 다른 컴퓨터로 파일을 가져오는 동시에 또 다른 컴퓨터에 파일을 제공할 수 있다.
네트워크를 구성할 때 전용서버를 두는 것을 서버기반모델(server-based model)이라고 하고 별도의 전용서버 없이 각 클라이언트가 서버역할을 동시에 수행하는 것을 P2P(peer to peer) 모델이라고 한다.
서버 기반 모델 : 안정적인 서비스 제공이 가능 공유 데이터의 관리와 보안이 용이하다. 서버 구축비용과 관리 비용이 든다.
P2P 모델: 서버구축 및 운용비용을 절감할 수 있다. 자원의 활용을 극대화 할 수 있다. 자원의 관리가 어렵다. 보안이 취약하다.
IP 주소 (IP address)
IP 주소는 컴퓨터(host)를 구별하는데 사용되는 고유한 값으로 인터넷에 연결된 모든 컴퓨터는 IP 주소를 갖는다.
IP 주소는 4 byte(32비트) 의 정수로 구성되어 있으며, 4개의 정수가 마침표를 구분자로 ‘a,b,c,d’ 와 같은 형식으로 표현된다.
a,b,c,d는 부호없는 1byte 값, 0~255사이의 정수이다.
IP 주소는 다시 네트워크 주소와 호스트 주소로 나눌 수 있는데 32 bit (4byte)의 IP 주소중 네트워크 주소와 호스트 주소가 각각 몇 bit를 차지하는 지는 네트워크를 어떻게 구성하였는지에 따라 달라진다.
그리고 서로 다른 두 호스트의 IP 주소의 네트워크 주소가 같다는 것은 두 호스트가 같은 네트워크에 포함되어 있다는 것을 말한다.
윈도우 OS 에서 호스트 IP 주소를 확인하려면 콘솔에서 ipconfig.exe를 실행시키면 된다.
URL(Uniform Resource Locator)
URL은 인터넷에 존재하는 여러 서버들이 제공하는 자원에 접근할 수 있는 주소를 표현하기
‘프로토콜://호스트명:포트번호/경로명/파일명?쿼리스트링#참조’
의 참조 형태로 이루어져 있다. (URL에서 포트번호, 쿼리, 참조는 생략할 수 있다.)
http://www.codechobo.com:80/sample/hello.html?refere=codechobo#index1
프로토콜: 자원에 접근하기 위해 서버와 통신하는데 사용되는 통신 규약
- 호스트명: 자원을 제공하는 서버의 이름(www.codechobo.com)
- 포트번호: 통신에 사용되는 서버의 포트번호(80)
- 경로명: 접근하려는 자원이 저장된 서버상의 위치(/sample/)
- 파일명: 접근하려는 자원의 이름(hello.html)
- 쿼리(query): URL에서 ‘?’ 이후의 부분(refere=codechobo)
- 참조(anchor): URL에서 ‘#’ 이후의 부분(index1)
URLConnection 클래스: 어플리케이션과 URL 간의 통신 연결을 나타내는 클래스의 최상위클래스로 추상클래스다.
소켓 프로그래밍
소켓프로그래밍은 소켓을 이용한 통신을 의미한다.
소켓(socket)이란 프로세스 간의 통신에 사용되는 양쪽 끝단을 의미한다.
서로 멀리 떨어진 두 사람이 통신하기 위해서 전화기가 필요한 것 처럼, 프로세스 간의 통신을 위해서는 그 무언가가 필요하고 그것이 바로 소켓이다.
자바에서는 java.net패키지를 통해 소켓프로그래밍을 지원하는데, 소켓통신에 사용되는 프로토콜에 따라 다른 종류의 소켓을 구현하여 제공한다.
TCP와 UDP
TCP/IP 프로토콜은 이기종 시스템간의 통신을 위한 표준 프로토콜로 프로토콜의 집합이다. TCP와 UDP 모두 TCP/IP 프로토콜에 포함되어 있으며, OSI 계층의 전송계층(transport layer)에 해당하는 프로토콜이다.
TCP와 UDP는 전송방식이 다르며, 각 방식에 따른 장단점이 있다.
항목 | TCP | UDP |
---|---|---|
연결방식 | 연결기반(connection-oriented) | 비연결 기반 |
연결 후 통신(전화기), 1:1 통신 방식 | 연결없이 통신(소포), 1:1,1:n, n:n통신방식 | |
특징 | 데이터의 경계를 구분 안함(byte-stream), 신뢰성 있는 데이터 전송, 데이터의 전송 순서가 보장됨, 데이터의 수신여부를 확인함, 패킷을 관리할 필요가 없음, UDP보다 전송속도가 느림 | 데이터의 경계를 구분함(datagram), 신뢰성 없는 데이터 전송, 데이터의 전송순서가 바뀔 수 있음, 데이터의 수신여부를 확인 안함, 패킷을 관리해줘야함, TCP 보다 전송속도가 빠름 |
관련 클래스 | socket,serversocket | DatagramSocket, DatagramPacket, MulticastSocket |
TCP를 이용한 통신은 전화에, UDP를 이용한 통신은 소포에 비유된다. TCP는 데이터를 전송하기 전 먼저 상대편과 연결한 후에 데이터를 전송하며 잘 전송되었는지 확인하고 전송 실패했다면 해당 데이터를 재전송하므로 신뢰있는 데이터 전송이 요구되는 통신에 적합하다. 예를 들면 파일을 주고 받는데 적합하다.
UDP는 상대편과 연결하지 않고 데이터를 전송하며 데이터를 전송하지만 데이터가 바르게 수신되었는지 확인하지 않으므로 데이터가 전송되었는지 확인할 길이 없다. 또한 데이터를 보낸 순서대로 수신한다는 보장이 없다.
대신 이런 확인과정이 없으므로 TCP에 비해 빠른 전송이 가능하다.
게임이나 동영상 데이터 전송하는 경우와 같이 데이터가 중간에 손실되어 조금 끊겨도 빠른 전송이 필요할 때 적합하다 이때 전송순서가 바뀌어 늦게 도착한 데이터는 무시하면 된다.
각 장단점이 있으므로 필요에 따라 사용하면 된다.
TCP 소켓 프로그래밍
TCP 소켓 프로그래밍은 클라이언트와 서버간의 일대일 통신이다 먼저 서버프로그램이 실행되어 클라이언트 프로그램의 연결 요청을 기다리고 있어야 한다. 프로그램간 통신과정은 다음과 같다.
서버 프로그램에서는 서버소켓을 사용해서 서버 컴퓨터의 특정 포트에서 클라이언트의 연결 요청을 처리할 준비를 한다.
클라이언트 프로그램은 접속할 서버 IP 주소와 포트를 가지고 소켓을 생성해서 서버에 연결을 요청한다.
서버 소켓은 클라이언트의 연결 요청을 받으면 서버에 새로운 소켓을 생성해서 클라이언트의 소켓과 연결되도록 한다.
이제 클라이언트의 소켓과 새로 생성된 서버의 소켓은 서버소켓과 관계없이 일대일 통신을 한다.
서버 소켓은 포트와 결합되어 포트를 통해 원격 사용자의 연결요청을 기다리다가 연결요청이 올 떄마다 새로운 소켓을 생성하여 상대편 소켓과 통신할 수 있도록 연결한다.
여기까지가 서버소켓의 역할이고 실제 데이터 통신은 서버 소켓과 관계없이 소켓과 소켓간에 이뤄진다.
이는 마치 전화시스템과 유사해서 서버소켓은 전화교환기에, 소켓은 전화기에 비유할 수 있다.
전화교환기(서버소켓)은 외부 전화기(원격소켓)으로부터 걸려온 전화를 내부의 전화기(소켓)으로 연결해주고 실제 통화는 소켓대 소켓으로 이뤄진다.
즉, 서버소켓은 소켓간의 연결만 처리하고 실제 데이터는 소켓끼리 서로 주고받는다. 소켓들이 데이터를 주고받는 연결통로는 입출력 스트림이다.
소켓은 두개의 스트림, 입력스트림과 출력스트림을 가지고 있으며, 이 스트림들은 연결된 상대편 소켓의 스트림과 교차연결된다. 한 소켓의 입력스트림은 상대편 소켓의 출력스트림과 연결되고 출력스트림은 입력스트림과 연결된다.
그래서 한 소켓에서 출력스트림으로 데이터를 보내면 상대편 소켓에서는 입력스트림으로 받게 된다.
앞서 비유한 전화기와 비슷해서 소켓이 두개의 입출력스트림을 갖는 것 처럼 전화기 역시 입력과 출력을 위한 두개의 라인을 가지고 있다.
자바에서는 TCP를 이용한 소켓프로그래밍을 위해 Socket과 ServerSocket 클래스를 제공한다.
- Socket: 프로세스 간 통신을 담당하며 InputStream, OutputStream을 가ㅣㅈ고 있다.
이 두 스트림을 통해 프로세스간 통신(입출력)이 이뤄진다.
- ServerSocket: 포트와 연결(bind)되어있어 외부의 연결요청을 기다리다 연결요청이 들어오면 SOcket을 생성해서 소켓과 소켓간의 통신이 이ㅜ러지도록 한다 한 포트에 하나의 서버소켓만 연결할 수 있다.(프로토콜이 다르면 같은 포트를 공유할 수 있다.)
UDP 소켓 프로그래밍 - Client
TCP에선 Socket과 ServetSocket을 쓰지만 Udp 소켓프로그래밍에선 DatagramSocket과 DatagramPacket을 사용한다.
UDP는 연결지향적인 프로토콜이 아니므로 서버소켓이 필요하지 않다. UDP에서 쓰느 ㄴ소켓은 DatagramSocket이며 데이터를 DatagramPacket에 담아서 전송한다.
DatagramPacket은 헤더와 데이터로 구성되어 있으며, 헤더엔 DatagramPacket을 수신할 호스트의 정보(호스트의 주소와 포트)가 저장되어 있다.
소포에 수신할 상대편의 주소를 적어서 보내는 것과 같다고 이해하면 된다.
그래서 DatagramPacket을 전송하면 DatagramPacket에 지정된 주소(호스트의) DatagramSOcket에 도착한다.