네트워크 통신하기

네트워크 통신 방식에는 Unicast,BroadCast,Multicast,Anycast가 존재한다.

Unicast

  • 출발지와 목적지가 1:1 통신

BroadCast

  • 같은 네트워크에 존재하는 모든 host가 목적지 (1: 모든 통신)
  • local 네트워크 내에서 모든 host에게 패킷을 전달할 때 사용됨
  • IPv6에서는 브로드 캐스트가 존재하지 않고, 링크 로컬 멀티캐스트로 대체되어 사용되고 있다.

Multicast

  • 다수의 특정 목적지로 데이터 전송 (1: 멀티캐스트 구독 호스트들)

AnyCast

  • 가장 가까운 호스트 (1:1 통신인데, 목적지는 동일 그룹내 1개 호스트 )
  • 가장 가까운 DNS 서버 , Gateway를 찾는데 사용하기도 함
  • Unicast와 다르게 anycast는 같은 목적지 주소를 가진 서버가 여러대이다.

BUM 트래픽

  • B(broadcast) , U(Unknown unicast) , M(multicast) 을 지칭 , BUM 트래픽이 많으면 네트워크 성능 저하
  • unknown unicast는 스위치가 목적지 주소를 몰라 , 모든 포트로 전송하는데 이러한 unicast를 unknown unicast라고 함 (unicast지만, 마치 broadcast와 유사한 동작 )
  • 이더넷 환경에서는 ARP broadcast를 먼저 보내고 통신을 시작하므로 BUM 트래픽이 많이 발생하지 않음
Read more

Item54. null이 아닌 빈 collection 이나 배열을 반환하라

다음은 주변에서 흔히 볼 수 있는 method이다.

1
2
3
4
5
private final List<Cheese> cheeseInStock = null;

public List<Cheese> getCheese(){
return cheeseInStock.isEmpty() ? null : new ArrayList<>(cheeseInStock);
}

null을 반환해야할 특별한 이유가 있는 게 아니라면 위와 같이 빈 collection 대신에 null을 반환하는 코드는 이를 사용하는 client가 아래와 같이 null처리 로직을 작성하게끔 만든다.

1
2
3
4
List<Cheese> cheese = shop.getCheese();
if(cheese != null){
// ...
}

collection이나 배열같은 container가 비었을 떄 null을 반환하는 method를 사용할 때면 항상 이와 같은 방어 코드를 넣어주어야 한다.

null을 반환하는 족에서는 빈 container 를 만드는데도 비용이 드니 null을 반환하는게 낫다는 주장도 있지만 이는 잘못된 주장이다. 빈 container를 만드는데는 성능 차이가 미미하며, 굳이 빈 collection 과 배열은 새로 할당하지 않고도 반환할 수 있다.

Read more

Item53. 가변인수는 신중히 사용하라

가변인수

가변인수 method는 명시한 타입의 인수를 0개 이상 받을 수 있다.
가변인수 method를 호출하면 가장 먼저 인수의 개수와 길이가 같은 배열을 만들고, 인수들을 이 배열에 저장하여 가변인수 method에 건네준다.

1
2
3
4
5
6
7
static int sum(int... args){
int sum = 0;
for (int arg : args) {
sum += arg;
}
return sum;
}

가변인수 개수가 1개 이상이여야만 할떄가 있다. 예를 들면 최솟값을 찾는 method에서 인수0개가 들어오면 exception이 터져야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
static int min(int... args){
if(args.length == 0){
throw new IllegalArgumentException("인수가 1개 이상 필요합니다.");
}
int min = args[0];
for(int i = 1; i< args.length ; i++) {
if (args[i] < min) {
min = args[i];
}
}
return min;
}

위 방식의 문제점은 가변인수의 인수를 넣지 않아도 호출되어 runtime에 실패한다는 점이다. 또한 가변인수에 대한 유효성 검사도 명시적으로 해야 한다.

이를 더 나은 방법으로 runtime이 아닌 compile time에 에러가 나오도록 수정하면 다음과 같다.

1
2
3
4
5
6
7
8
9
static int min(int firstArg , int... remainingArgs){
int min = firstArg;
for (int arg : remainingArgs) {
if(arg<min){
min = arg;
}
}
return min;
}

매개변수를 2개 받아서, 첫번째 매개변수는 필수여야하는 인수를 넣고, 두번째는 가변인수로 받아서 인수가 1개 미만일떄는 compile error가 터진다

Read more

네트워크 연결과 구성요소

네트워크 연결 구분

네트워크는 규모에 따라 LAN,MAN,WAN 3가지로 구분된다.

1.LAN(Local Area Network) : 사용자 내부 네트워크 (근거리 통신망으로 주로 집 , 사무실 단위)
2.MAN(Metro Area Network) : 한 도시 정도를 연결하고 관리하는 네트워크
3.WAN(Wide Area Network) : 멀리 떨어진 LAN을 연결해주는 네트워크

네트워크 회선

인터넷 회선

  • 인터넷 접속을 위해 통신사업자와 연결하는 회선

인터넷 가입자와 통신사업자간에는 직접 연결되는 구조가 아니라 전송 선로 공유 기술을 사용한다. 예를 들어 아파트 광랜은 아파트에서 통신사업자까지 연결한 회선을 아파트 가입자가 공유하는 구조임. 즉, 전송 선로를 공유하므로 전용 회선과 다르게 속도가 보장되지 않는다 (주변 사용량에 따라 속도가 느려질 수도 있다. )

인터넷 회선 종류는 다음과 같다.

  • 광랜(이더넷) , FTFH , 동축 케이블 인터넷 , xDSL

전용 회선

  • 가입자와 통신사업자 간에 대역폭(bandwith,링크 용량)을 보장해주는 서비스.

가입자와 통신사업자 간에는 전용 케이블로 연결되어 있고, 통신사업자 내부에서 TDM(Time Division Mulitiplexing) 같은 기술로 마치 직접 연결한 것처럼 통신 품질을 보장해준다.

인터넷 전용 회선

  • 인터넷 회선에 대한 통신 대역폭을 보장해주는 서비스

가입자가 통신사업자와 연결되고 이 연결이 다시 인터넷과 연결되는 구조이다.

VPN (Virtual private network , 가설 사설망 )

  • 물리적으로는 전용선이 아니지만, 가상으로 직접 연결한 것 같은 효과가 나도록 만들어주는 네트워크 기술

ex) 재택근무중인데 회사 사설망에 연결되어있는 것처럼 업무 가능

  • 통신사업자 VPN : VPN 서비스를 ISP에서 제공하며 전용선의 거리에 비례하는 비용문제가 있다.
  • 가입자 VPN : 일반 인터넷망을 이용해 가상 네트워크를 구성할 수 있다.

네트워크 구성 요소

네트워크 인터페이스 카드 (NIC)

흔히 랜카드,네트워크 카드, 네트워크 인터페이스 컨트롤러로도 불리는 네트워크 인터페이스 카드는 컴퓨터를 네트워크에 연결하기 위한 하드웨어 장치이다.

노트북과 데스크탑 PC에서는 기본적으로 온보드 형태로 장착됨으로 별도로 추가할 필요는 없으나, 서버의 경우에 여러 네트워크에 동시에 연결하거나 혹은 더 높은 대역폭이 필요한 경우, 네트워크 인터페이스 카드를 추가로 장착한다.

네트워크 인터페이스 카드의 주요 역할은 다음과 같다.

  • 직렬화 (Serialization) : 전기적 신호를 bit열의 데이터 신호 형태로 , bit열의 데이터 신호 형태를 전기적 신호 형태로 변환시켜주는데, 이러한 상호 변환작업을 직렬화라고 한다.

  • MAC 주소 : 네트워크 인터페이스 카드는 고유한 MAC주소를 가지고 있어서, 받은 패킷의 도착지 MAC주소가 자신의 MAC주소가 아니면 폐기하고 맞으면 전달한다.

  • 흐름 제어 (Flow control) : 이미 통신 중인 데이터 처리 떄문에 새로운 데이터를 받지 못하는 상황에서 데이터 유실 방지를 위해 상대방에서 통신 중지를 요청할 수 있다.

Read more

OSI 7 계층과 TCP/IP

Protocol

  • 네트워크에서는 통신할떄의 규약을 protocol이라고 함.
  • 최근 이더넷 - TCP/IP 기반 protocol로 획일화되고 있는 추세이다.
    • 물리적인 측면 : 데이터 전송 매체 , 신호 규약 , 회선 규격 등 이더넷이 널리 쓰인다.
    • 논리적인 측면 : 장치들간에 통신하기 위한 protocol 규격으로 TCP/IP가 널리 쓰인다.

과거에는 네트워크 환경과 컴퓨팅 환경이 열악해 한정된 자원으로 최대한 효율적인 protocol을 정의하고 사용해야 했음. 따라서 대부분의 protocol이 문자 기반이 아닌 2진수 bit 기반으로 만들어졌음. application level의 protocol은 bit 기반이 아닌 문자기반으로 많이 사용되고 있음. ex) HTTP,SMTP

문자기반으로 사용할 경우, 효율성은 bit 기반 protocol보다 많이 떨어지지만 다양한 확장이 가능하다.

TCP/IP protocol stack

  • TCP(transport 계층)와 IP(network 계층)는 별도의 계층에서 동작하는 protocol 이지만 함께 사용하고 있는데 , 이런 프로토콜의 묶음을 protocol stack이라고 부름

  • TCP/IP protocol stack은 총 4개 부분으로 나뉜다.

OSI 7계층과 TCP/IP

  • OSI 7계층은 네트워크의 주요 reference model로 활용되고 있지만, 현재는 대부분의 protocol이 TCP/IP protocol stack 기반으로 되어 있다.

  • 계층별로 표준화된 protocol을 개발함으로서 네트워크 구성 요소들을 모듈화 할 수 있다.
  • 모듈화함으로서 기존에 다른 계층의 protocol들과 연동해 사용할 수 있다.

OSI 7계층은 다시 2가지 계층으로 나뉜다.

  1. Data Flow layer (Lower Layer) : 1~4 계층
  2. Application Layer (Upper Layer) : 5~7 계층

Data flow layer 는 데이터를 상대방에게 잘 전달하는 역할을 가지고 있으며 , application layer는 데이터를 잘 표현하는데 역할을 가지고 있다. 따라서 네트워크 엔지니어는 Data flow layer 을 고려하고, application 개발자는 application layer를 고려한다.

OSI 7계층과 TCP/IP protocol stack의 차이점

  • OSI reference model은 7 계층으로 이루어진 반면, TCP/IP 모델은 4계층으로 구분된다.

  • TCPI/IP 모델은 상위 3개 계층 (application,presentation,session) 은 하나의 application 계층으로 묶고, 하위 2개 계층 (physical , data link) 계층을 하나의 network access 계층으로 분류한다.
Read more

Item52. 다중정의는 신중히 사용하라

매개변수가 동일한 부모 타입일떄 overloading의 문제점

다음과 같이 collection을 집합, 리스트 , 그외로 구분하고자 만든 CollectionClassifier 프로그램이 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class CollectionClassifier {

public static String classify(Set<?> s){
return "set";
}
public static String classify(List<?> lst){
return "list";
}
public static String classify(Collection<?> c){
return "others";
}

public static void main(String[] args) {
Collection<?>[] collections = {
new HashSet<String>(),
new ArrayList<BigInteger>(),
new HashMap<String,String>().values()
};

for (Collection<?> c : collections) {
System.out.println(classify(c));
}
}
}

collection에 set,list,map을 넣어주면 예상결과는 set,list,others인데 실제로는 others만 세번 출력된다.
그 이유는 다중정의(overloading)된 classify증에 어느 method를 실행할지가 compile time에 정해지기 떄문이다. compile time에 for문안의 c는 항상 Collection<?> 타입이다. runtime에는 타입이 매번 달라지지만 호출할 method를 선택하는데는 영향을 주지 못한다.

overloading vs overriding

재정의한 method는 동적으로 runtime에 선택되고, 다중정의한 method는 정적으로 compile time에 선택된다. method를 재정의했다면 해당 객체의 runtime 타입이 어떤 method를 호출할지의 기준이 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Wine{
String name() {return "Wine";}
}

class SparklingWine extends Wine{
@Override
String name() {return "SparklingWine";}
}
class Champagne extends SparklingWine{
@Override
String name() {return "Champagne";}
}

public class Overriding {

public static void main(String[] args) {
List<Wine> wineList = List.of(new Wine(), new SparklingWine(), new Champagne());
for (Wine wine : wineList) {
System.out.println("wine.name() = " + wine.name());
}
}
}
1
2
3
4
//출력 결과 
wine.name() = Wine
wine.name() = SparklingWine
wine.name() = Champagne

위와 같이 runtime에 객체 타입을 보고 재정의한 method가 호출된다. 반면에 다중정의한 method사이에서는 객체의 runtime 타입은 전혀 중요치 않다. 선택은 compile time에 오직 매개변수의 compile time 타입에 의해 이뤄진다.

CollectionClassifier program이 runtime에 객체 타입에 따라 반환결과를 다르게 하고 싶다면 아래와 같이 classify method를 모두 하나로 합친 뒤, instanceof 로 명시적으로 runtime에 수정해야 한다.

1
2
3
public static String classify(Collection<?> c){
return c instanceof Set ? "set" : c instanceof List ? "list" : "others";
}
  • overloading시 compile time에 호출 method가 매개변수 타입을 보고 결정되므로, overriding 과는 동작방식이 다르다. 따라서 client가 사용했을때, 혼동을 일으키는 상황을 되도록 주지 않는 것이 좋다.
Read more

Item 47. 반환 타입으로는 stream보다는 collection이 낫다.

stream은 반복을 지원하지 않으므로, API를 stream만 반환하도록 짜놓으면 반환된 stream을 for-each로 반복하길 원하는 사용자는 사용이 힘들다

stream은 Iterable 인터페이스가 정의한 추상 method를 모두 포함할뿐 아니라, Iterable 인터페이스가 명시한 스펙대로 동작하지만 Iterable을 확장하고 있지는 않기 때문에 for-each문으로 반복이 불가능하다.

따라서 stream의 iterator method에 method 참조를 건네고, 이를 매개변수화된 Iterable로 적절히 형변환하면 동작은 한다.

1
2
3
for(ProcessHandle ph: (Iterable<ProcessHandler>) ProcessHandle.allProcesses()::iterator){
// process 처리
}

adapter method

하지만 이렇게 stream을 사용하기는 가독성이 떨어지므로, adapter method를 사용하는 방안이 있다.

1
2
3
4
// Stream<E> 를 Iterable<E>로 중개해주는 adapter 
public static <E> Iterable<E> iterableOf(Stream<E> stream){
return stream::iterator;
}

adapter method를 사용하면 어떤 stream도 for-each문으로 반복할 수 있다.

1
2
3
for(ProcessHandle p : iterableOf(ProcessHandle.allProcesses())){

}

반대의 상황으로서 API가 iterable만 반환하면 이를 stream 으로 사용하고자 하는 사용자는 사용이 힘들다. 따라서 adapter method를 제공하는 방안이 있다.

1
2
3
4
// Iterable<E>를 Stream<E>로 중개해주는 adapter 
public static <E> Stream<E> streamOf(Iterable<E> iterable){
return StreamSupport.stream(iterable.spliterator(),false);
}
Read more

Scheduling

FIFO (First in, First out)

FIFO(First-in-First-out,선입선출) 또는 FCFS(First-Come-First-Served,FCFS) 스케쥴링이다.

말그대로 먼저 도착한 process부터 cpu를 할당받는 것이다.

FIFO방식의 가장 큰 문제점은 작업 실행 시간이 긴 process가 먼저 왔을때, 뒤에 있는 process들이 이를 끝나기를 기다려야 한다는 점이다. 이를 convoy effect라고 부른다.

SJF(Shortest Job First)

가장 짧은 실행 시간을 가진 작업을 먼저 실행시킨다.

FIFO에서 가지는 convoy-effect문제를 해결할 수 있으나, 모든 작업이 동시에 도착한다는 가정하에서다. 즉 긴 작업이 짧은 작업보다 먼저 도착한다면 여전히 convoy-effect문제가 발생할 수도 있다.

예를 들면 위처럼 작업시간이 긴 A라는 process가 먼저 도착했고, 그 뒤에 작업 시간이 짧은 B,C가 도착하였다. SJF는 비선점형이므로, A가 끝날떄까지 기다려야 한다.

위의 FIFO와 SJF는 비선점형 scheduler로 각 작업이 종료될떄까지 계속 수행된다. 이제부터 정리할 스케쥴링 알고리즘은 선점형 방식으로 작업이 종료되기 전에 다른 작업으로 전환이 가능하다

Read more

Item51. method 시그니처를 신중히 설계하라

이번 아이템에서는 API 설계 권고 사항들을 정리하였다.

method 이름을 신중히 짓자.

  • 항상 표준 명명 규칙 (Item68)을 따라야 한다.
  • 같은 패키지에 속한 다른 이름들과 일관되게 짓는게 최우선이다.
  • 개발자 커뮤니티에서 널리 받아 들여지는 이름을 사용하자
  • 긴 이름은 피하자

편의 method를 너무 많이 만들지 말자

  • method가 너무 많은 class는 익히고 , 사용하고, 문서화하고 , 테스트하고 유지보수하기 어렵다. 따라서 아주 자주 쓰일때만 편의 method로 만들자

매개변수 목록은 짧게 유지하자

  • 매개변수가 4개를 넘어가면 가독성이 떨어진다.
  • 같은 type의 매개변수가 여러개 연달아 나오는 경우에는 특히 순서가 변경되어도 그대로 실행됨으로 좋지 않다.
Read more

Item50. 적시에 방어적 복사본을 만들라

클래스 불변식을 클라이언트가 꺠트릴 수 없도록 방어적으로 프로그래밍해야 한다.

다음과 같이 클래스 불변식을 유지하고자 하는 class가 있다고 가정하자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public final class Period {

private final Date start;
private final Date end;

/***
* @param start 시작 시간
* @param end 종료 시각 , 시작 시간보다 뒤여야 한다.
* @throws IllegalArgumentException : 시작 시간이 종료시각보다 늦을떄 발생한다.
* @throws NullPointerException : start나 end가 null이면 발생한다.
*/
public Period(Date start, Date end) {
if(start.compareTo(end) > 0){
throw new IllegalArgumentException(start + " after " + end);
}
this.start = start;
this.end = end;
}

public Date start(){
return start;
}

public Date end(){
return end;
}
}

Period class는 한번 값이 설정되면 변경되지 않는 것 처럼 보이나 Date class 자체가 가변이라는 사실을 이용하면 클래스 불변식을 꺠트릴 수 있다.

1
2
3
4
5
6
public static void main(String[] args) {
Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
end.setYear(68); // p 객체의 값이 변경된다.
}

따라서 Period class를 불변으로 유지하려면 불변 필드인 Instant를 사용해야 한다.

방어적 복사 (defensive copy)

외부 공격으로부터 class 불변식을 유지하려면 생성자에서 받은 가변 매개변수는 모두 방어적으로 복사해야 한다.

예시로든 Period class에 방어적 복사를 적용하면 다음과 같이 생성자에서 복사본을 필드로 가지게 만들고, 접근자에서도 복사본을 반환함으로서 클래스 불변식을 유지할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public Period(Date start, Date end) {
this.start = new Date(start.getTime());
this.end = new Date(end.getTime());
if(this.start.compareTo(this.end) > 0){
throw new IllegalArgumentException(start + " after " + end);
}
}

public Date start(){
return new Date(start.getTime());
}

public Date end(){
return new Date(end.getTime());
}

Read more