자바 기초 문법, OOP 기초, 중급이 다 끝나고 오늘은 네트워크 프로그래밍에 들어갔다.
사실 네트워크 프로그래밍이라 그래도, 그렇게 대단한 것은 아니다.
현재 수준에선 기존에 주로 실습했던 ‘대화형 프로그램’에 서버-클라이언트 모델을 더한 것이다. 그리고 서버-클라이언트가 구현되려면 결국 ‘네트워크’ 통신기술이 끼게 된다.
그럼 본격적인 프로그램에 들어가기 전에, 네트워크를 다루기 위한 기본 지식을 알아보자.
- IP주소
- PORT 번호
- 서버-클라이언트
- Socket과 Stream
우리가 물건을 사고, 택배를 받으려면 결제 시 주소를 꼭 입력해줘야 한다.
마찬가지로 인터넷을 통해 데이터를 내려받으려면 우리의 주소를 알려줘야 한다.
: 네트워크 상에서 각각의 PC를 구분짓기 위한 고유 주소값.
같은 장소에 있는 여러 PC가 인터넷에 접속해 서로 다른 요청을 하더라도 인터넷은 각 요청을 구별하여 정확히 PC에 전달한다. 이게 가능한 이유가 IP주소다.
IP는 Internet protocol 의 약자로 쉽게 말하면 인터넷 네트워크를 사용하는 호스트 간의 통신 규칙을 의미한다. 그리고 이 규칙 안에서 인터넷 네트워크를 사용하려면 각 PC를 구분할 수 있는 정보가 필요하다.
따라서 인터넷 상에서 서로를 구분해주는 정보, 곧 주소를 ‘IP주소’라고 한다.
IP주소는 “ICANN” 이라는 국제인터넷주소관리기구에서 각 국가에 배당, 관리하고 있으며 배당 받은 IP주소는 국가별로 ISP라는 통신사에 맡겨 다시 관리한다.
현 IP주소는 ‘ipv4’라는 주소체계를 택해 약 42억 개 이상이 있는데, 실제 우리가 네트워크를 이용하는 기기의 수는 ip주소의 총량를 능가한다.
그러나 그 수보다 많은 기기가 존재하는데 어떻게 우리는 인터넷에 접속할 수 있을까?
그건 ‘공유기’라는 기술 때문이다.
모든 기기마다 ip가 배당될 수 있지만, 무한정 증가하는 수를 관리하는 것은 비효율적이다.
따라서 ip주소를 공유기에만 배당해, PC에서 인터넷을 접속하려면 공유기를 지나가서 해당 ip를 부여받게 한 것이다.
그런 점에서 IP주소는 인터넷을 접속하게 하는 공인ip, 각 컴퓨터를 구분하는 사설 ip로 구분된다.
사설 ip는 공유기에서 각 개체를 식별하기 위한 가상 ip이다. 따라서 인터넷에서 공유기로 흘러들어오는 정보는 가상 ip를 통해 각 pc를 구분한다.
우리가 인터넷에 접속 요청을 하려면 컴퓨터에서 전기신호를 통해 다양한 정보들을 제출해야 한다.
이 정보엔 양식이 존재하는데, 그 중 하나가 ip주소이다.
그래서 pc는 사설 ip를 입력하여 전달하는데, 이를 공유기에서 공인ip으로 교환하여 인터넷 네트워크로 보낸다. 다만 우리가 cmd
를 이용해 아이피 주소를 확인할 때, 나오는 ‘게이트웨이 IP’는 LAN에서 사용되는 사설 IP이다.
: 하나의 장비에서 제공되는 서비스의 고유번호
IP가 인터넷 안에서 각 PC를 구분하는 주소라면 포트 번호는 각 PC에서 서비스를 구분하는 번호이다.
쉽게 말하면 우리가 택배를 받을 때, ‘철문 앞, 경비실, 정문’ 등의 선택지가 포트 번호이다.
즉, 이 포트 번호를 통해 네트워크에서 넘어오는 데이터를 받게 된다.
그래서 우리가 PC에서 사용하는 카톡, 라인, 디스코드 등의 프로그램들은 이미 내부적으로 포트를 사용 중이다.
포트 번호는 0~65535 사이에 수로 정해져 있으며 0~1023번은 Well Know(잘 알려진) 포트여서 사용을 피하는게 좋다.
추가로 각 서비스의 포트 번호는 겹쳐선 안 된다. 물론 택배는 같은 곳으로 받을 수 있지만, 데이터는 다르니까 말이다. 만약 포트 번호가 겹치는 프로그램을 실행하게 되면, 먼저 실행된 프로그램이 해당 포트를 사용한다.
이런 포트는 외부 포트 와 내부 포트 로 나뉘어 진다.
네트워크는 WAN이라는 외부 네트워크와 Local이라는 내부 네트워크가 있다.
따라서 인터넷을 통해서 우리에게 오는 정보는 일차적으로 WAN을 통해서 공인 IP를 따라온다.
하지만 해당 공인 IP를 사용하는 PC가 여러 대일 것이다.
이때, ‘포트 포워딩’이라는 설정을 통해 데이터가 들어올 외부 포트 번호와 사설 IP와 내부 포트 번호를 알려줘야 한다. 그러면 데이터는 해당 공인 IP를 가진 공유기의 외부 포트로 들어오게 되고, 이를 LAN에서 사용하기 위해 외부 포트와 연결된 사설 IP - 내부 포트 로 끌어 온다.
따라서 우리가 앞으로 만들 네트워크 프로그램에서도 포트 번호를 설정해야, 서버와 클라이언트 간의 통신이 가능하다.
네트워크 위에서 통신을 위해 우리는 앞으로 서버 프로그램이랑 클라이언트 프로그램을 두 개를 만들고자 한다.
먼저 서버는 ‘서비스 제공자’로서 사용자들에게 자신들이 만든 기능(서비스)를 제공한다.
이때, 서버는 보편적으로 1:n 통신을 제공한다.
반대로 클라이언트는 ‘서비스 사용자’이다. 이들은 서버에서 제공되는 서비스를 사용하는 이들로 1:1을 통신을 한다.
네트워크 상에서 컴퓨터를 구분하는 IP
네트워크로부터 데이터를 전달받는 장소인 Port
서비스를 제공하는 서버
서비스를 사용하는 클라이언트
통신을 위해 이렇게 네 가지의 기초 조건을 배워봤다. 하지만 기초만 가지고 네트워크 프로그램을 만들어낼 수 없다.
지금 상황을 굳이 비유하자면, 출발장소와 목적지는 정해져 있는데 자동차와 길이 없다는 것이다.
따라서 네트워크 통신을 가능하게 하는 자동차와 길에 대해서 알아보자.
소캣은 통신을 위한 논리적 단말기로서, 네트워크 프로그램은 이 소캣이 있어야 서로 통신이 가능하다.
위 비유에서 자동차가 바로
Socket
이다.
Socket socket = new Socket(“IP주소”, 포트 번호);
자바에서는 Socket
클래스를 이용해서 쉽게 사용이 가능하다.
하지만 소캣은 1대 1 통신만을 위해서 만들어져 있다. 이로 인해 서버의 경우는 소캣을 1개만을 가지고 있을 수 없다.
앞서 말한 것처럼 서버는 1대 다의 통신을 주로 하는데, 소캣이 하나만 있다면 다음 접속자는 앞선 접속자가 나갈 때까지 기다리기만 해야되는 것이다.
따라서 서버의 경우는 접속자가 있을 때마다 소캣을 만들어 낸다.
이를 ServerSocket
이라고 하며, 클라이언트의 접속요청 시 소캣을 하나씩 만들어
통신 연결을 해준다.
ServerSocket server = new ServerSock(포트번호);
Socket socket = server.accept();
ServerSocket
클래스는 접속이 있을 때마다 소캣을 생성해주는 accept()
라는 메서드가 있다.
해당 메서드는 클라이언트로부터 접속이 올 때까지, 무한루프를 돌리고 있고 접속이 들어오면 포트로 연결된 소캣을 생성, 반환하면서 무한루프를 끝낸다.
이렇게 되면 클라이언트는 서버의 새로운 소캣과 연결된 상태로 서비스를 사용하게 된다.
Stream
은 데이터가 오고갈 수 있는 논리적 통로이다.
곧, 자동차가 오고 갈 도로이다.
따라서 서버와 클라이언트 모두 Stream이 존재해야 한다.
Stream은 두 가지 방향을 가지는데, 그 기준은 바로 RAM이다.
1) 데이터를 보내는 OutputStream
2) 데이터를 받는 InputStream
기준과 방향을 따라서 우리가 데이터를 상대 PC로 보낼 때는 우리의 RAM에서 나가는 것이기 때문에 OutputStream
을 열어준다.
Socket socket = new Socket(“IP”, 포트번호);
OutputStream os = socket.getOutputStream(); // OutputStream 생성
os.write(); // 데이터 전송
그러나 이 경우, OutputStream
은 열려 있지만, 입력하여 보낼 수 있는 메서드가 한정되어 있다. 한 마디로 ‘아직은 비포장도로이다.’
따라서 포장도로로 바꿔주자. DataOutputStream
생성
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
dos.writeInt();
dos.wirteLong();
dos.wirteUTF();
이제 다양한 종류의 데이터들을 손쉽게 보낼 수 있는데, 보낼 때 규칙은 ‘Buffer’가 가득 차거나 dos.flush();
로 강제로 ‘Buffer’를 비워버리면 된다.
반대로 우리가 데이터를 RAM으로 받으려면 다음과 같이 사용하면 된다.
Socket socket = new Socket(“IP”, 포트번호);
InputStream is = socket.getInputStream(); // InputStream 생성
DataInputStream dis = new DataInputStream(is);
dis.readInt();
dis.readLong();
dis.readUTF;
이때, 주의할 것은 네트워크 프로그램은 서버와 클라이언트 간에 데이터를 주고-받는 것이 서로 대응되어야 한다는 것이다.
즉, 상대가 데이터를 보낸다면 우리는 데이터를 받는 코드를 작성해줘야 한다.
만약 그 대치가 맞지 않으면 프로그램이 정지하게 되는데, 이를 위해 서로 주고-받음을 맞추는 것은 ‘네트워크 프로토콜’이라고 한다.