Java

Java 네트워킹

코드깎는머슴 2024. 2. 27. 15:37
728x90
반응형

네트워킹(Networking)

두 대 이상의 컴퓨터를 케이블로 연결하여 네트워크(network)를 구성하는 것을 말한다.

네트워킹의 개념은 컴퓨터들을 서로 연결하여 데이터를 손쉽게 주고받거나, 
자원프린터와 같은 주변기기를 함께 공유하고자 하는 노력에서 시작되었다.

메신저나 온라인게임과 같은 인터넷을 이용하는 다양한 네트워크 어플리케이션이 많이 생겨났고,
자바에서 제공하는 java.net패키지를 사용하면 이러한 네트워크 어플리케이션의 데이터 통신 부분을 쉽게 작성할 수 있으며,
간단한 네트워크 어플리케이션은 단 몇 줄의 자바코드만으로도 작성이 가능하다.
    

클라이언트/서버

클라이언트/서버는 컴퓨터간의 관계를 역할로 구분하는 개념이다.

일반적으로 서버는 다수의 클라이언트에게 서비스를 제공하기 때문에 고사양의 하드웨어를 갖춘 컴퓨터지만,
하드웨어의 사양으로 서버와 클라이언트를 구분하는 것이 아니기 때문에, 
하드웨어의 사양에 관계없이 서비스를 제공하는 소프트웨어가 실행되는 컴퓨터를 서버라 한다.

서비스는 서버가 클라이언트로부터 요청받은 작업을 처리하여 그 결과를 제공하는 것을 뜻하며,
제공하는 서비스의 종류에 따라 파일서버(file server), 메일서버(mail server), 어플리케이션 서버(application server) 등이 있다.

예를 들어 파일서버는 클라이언트가 요청한 파일을 제공하는 서비스를 수행한다.

서버에 접속하는 클라이언트 수에 따라 하나의 서버가 여러가지 서비스를 제공하기도 하고,
하나의 서비스를 여러 대의 서버로 제공하기도 한다.

서버가 서비스를 제공하기 위해서는 서버프로그램이 있어야 하고,
클라이언트가 서비스를 제공받기 위해서 서버프로그램과 연결할 수 있는 클라이언트 프로그램이 있어야 한다.

일반 PC의 경우 주로 서버에 접속하는 클라이언트 역할을 수행하지만,
FTP Serv-U와 같은 FTP서버프로그램이나 Tomcat과 같은 웹서버프로그램을 설치하면 서버역할도 수행할 수 있다.

파일공유프로그램인 소리바다나 푸르나와 같은 프로그램은 클라이언트프로그램과 서버프로그램을 하나로 합친 것으로,
이를 설치한 컴퓨터는 클라이언트인 동시에 서버가 되어 다른 컴퓨터로부터 파일을 가져오는 동시에 또 다른 컴퓨터에게 파일을 제공할 수 있다.

네트워크를 구성할 때 전용서버를 두는 것을 서버기반모델(server-based model)이라 하고,
별도의 전용서버 없이 각 클라이언트가 서버역할을 동시에 수행하는 것을 P2P(peer-to-peer)모델이라 한다
        

서버기반 모델

            안정적인 서비스 제공이 가능하다.
            공유 데이터의 관리와 보안이 용이하다.
            서버구축비용과 관리비용이 든다.
        

P2P모델

            서버구축 및 운용비용을 절감할 수 있다.
            자원의 활용을 극대화할 수 있다.
            자원의 관리가 어렵다.
            보안이 취약하다.

    

IP주소(IP address)

컴퓨터(호스트, host)를 구별하는데 사용되는 고유한 값으로 인터넷에 연결된 모든 컴퓨터는 IP주소를 갖는다.

IP주소는 4 byte(32 bit)의 정수로 구성되어 있으며,
4개의 정수가 마침표를 구분자로 'a.b.c.d'와 같은 형식으로 표현된다.
여기서 a, b, c, d는 부호없는 1byte값, 즉 0 ~ 255 사이의 정수다.

네트워크주소와 호스트주소로 나눌 수 있는데,
32 bit(4 byte)의 IP주소 중에서 네트워크주소와 호스트주소가 각각 몇 bit를 차지하는 지는 네트워크를 어떻게 구성하였는지에 따라 달라진다.

그리고 서로 다른 두 호스트의 IP주소의 네트워크주소가 같다는 것은,

두 호스트가 같은 네트워크에 포함되어 있다는 것을 의미한다.

윈도우즈 OS에서 호스트의 IP주소를 확인하려면 콘솔에서 ipconfig.exe를 실행시키면 된다.

C:\Documentsand Settings\Administrator>ipconfig

Windows IP Configuration

Ethernet adapter 로컬 영역 연결:

Connection-specific DNS Suffix  .   :
IP Address. . . . . . . . . . . . : 192.168.10.100
Subnet Mask . . . . . . . . . . . : 255.255.255.0
Default Gateway . . . . . . . . . : 192.168.10.1

C:\Documents and Settings\Administrator>


위 결과에서 얻은 IP주소와 서브넷 마스크를 2진수로 표현하면 다음과 같다.

IP주소
192         168         10          100
11000000    10101000    00001010    01100100
네트워크 주소            호스트 주소

서브넷 마스크
255         255         255         0
11111111    11111111    11111111    00000000


IP주소와 서브넷 마스크를 비트연산자 '&'로 연산하면 IP주소에서 네트워크 주소만을 뽑아낼 수 있다.

 11000000    10101000    00001010    01100100
&11111111    11111111    11111111    00000000
>>
11000000     10101000    00001010    00000000


& 연산자는 bit의 값이 모두 1일 때만 1을 결과로 얻기 때문에 IP주소의 마지막 8bit는 모두 0이 되었다.

이 결과로 IP주소 192.168.10.100의 네트워크 주소는 24bit(192.168.10)이라는 것과 호스트 주소는 마지막 8bit(100)라는 것을 알 수 있다.

IP주소에서 네트워크주소가 차지하는 자리수가 많을수록 호스트 주소의 범위가 줄어들기 때문에 네트워크의 규모가 작아진다. << 서로 다른 호스트가 네트워크주소가 같을 때 같은 네트워크이기 때문에

 

이 경우 호스트 주소의 자리수가 8자리이기 때문에 256개(2^8)의 호스트만 이 네트워크에 포함될 수 있다.

호스트 주소가 0인 것은 네트워크 자신을 나타내고,
255는 브로드캐스트 주소로 사용되기 때문에 실제로 네트워크에 포함 가능한 호스트 개수는 254개이다.

이처럼 IP주소와 서브넷 마스크를 '&' 연산하면 네트워크 주소를 얻어낼 수 있어서, 
서로 다른 두 호스트의 IP주소를 서브넷 마스크로 '&' 연산을 수행해서 비교하면 이 두 호스트가 같은 네트워크 상에 존재하는지 쉽게 알 수 있다.
    
    

InetAddress

        자바는 IP주소를 다루기 위한 클래스로 InetAddress를 제공한다.

        하나의 도메인명(http://www.naver.com)에 여러 IP주소가 맵핑될 수도 있고,
        그 반대의 경우도 가능하기 때문에 전자의 경우 getAllByName()을 통해 모든 IP주소를 얻을 수 있다.

        그리고 getLocalHost()를 사용하면 호스트명과 IP주소를 알아낼 수 있다.

    

URL(Uniform Resource Locator)

인터넷에 존재하는 여러 서버들이 제공하는 자원에 접근할 수 있는 주소를 표현하기 위한 것

'프로토콜://호스트명:포트번호/경로명/파일명?쿼리스트링#참조'의 형태로 이루어져있다.

참고            URL에서 포트번호, 쿼리, 참조는 생략할 수 있다.

http://www.codechobo.com:80/sample/hello.html?referer=codechobo#index1


프로토콜        자원에 접근하기 위해 서버와 통신하는데 사용되는 통신규약(http)
호스트명        자원을 제공하는 서버의 이름(http://www.codechobo.com)
포트번호        통신에 사용되는 서버의 포트번호(80)
경로명          접근하려는 자원이 저장된 서버상의 위치(/sample/)
파일명          접근하려는 자원의 이름(hello.html)
쿼리(query)     URL에서 '?' 이후의 부분(referer=codechobo)
참조(anchor)    URL에서 '#' 이후의 부분(index1)

참고            HTTP프로토콜에서는 80번 포트를 사용하기 때문에 URL에서 포트번호를 생략하는 경우 80으로 간주한다.
                        각 프로토콜에 따라 통신에 사용하는 포트번호가 다르며 생략되면 각 프로토콜의 기본 포트가 사용된다.



자바는 URL을 다루기 위한 클래스로 URL클래스를 제공한다.

URL 객체를 생성하는 방법은 다음과 같다.

URL url = new URL("http://www.codechobo.com/sample/hello.html");
URL url = new URL("http://www.codechobo.com", "/sample/hello.html");
URL url = new URL("http", "http://www.codechobo.com", 80, "/sample/hello.html");


            

URLConnection

어플리케이션과 URL간의 통신연결을 나타내는 클래스의 최상위 클래스로 추상클래스이다.

URLConnection을 상속받아 구현한 클래스로는 HttpURLConnection과 JarURLConnection이 있으며,
URL의 프로토콜이 http프로토콜이라면 openConnection()은 HttpURLConnection을 반환한다.

URLConnection을 사용해서 연결하고자하는 자원에 접근하고 읽고 쓰기를 할 수 있다.
그 외에 관련된 정보를 읽고 쓸 수 있는 메서드가 제공된다.

참고            openConnection()은 URL의 클래스의 메서드이다.
                   HttpURLConnection은 sun.net.http://www.protocol.http패키지에 속해있다.

URL이 유효하지 않으면 Malformed- URLException이 발생


소켓 프로그래밍

    소켓을 이용한 통신 프로그래밍

    소켓(socket)이란 프로세스간의 통신에 사용되는 양쪽 끝단(endpoint)을 의미한다.

    서로 멀리 떨어진 두 사람이 통신하기 위해 전화기가 필요한 것처럼,
    프로세스산의 통신을 위해서는 그 무언가가 필요하고 그것이 소켓이다.

    자바는 java.net패키지를 통해 소켓 프로그래밍을 지원하는데, 
    소켓통신에 사용되는 프로토콜에 따라 다른 종류의 소켓을 구현하여 제공한다.
    

 

TCP, UDP

        TCP/IP 프로토콜은 이기종 시스템간의 통신을 위한 표준 프로토콜로 프로토콜의 집합이다.

        TCP, UDP 모두 TCP/IP 프로토콜(TCP/IP protocol suites)에 포함되어 있으며,
        OSI 7계층의 전송계층(transport layer)에 해당하는 프로토콜이다.

        TCP와 UDP는 전송 방식이 다르며, 각 방식에 따른 장단점이 있다.

        어플리케이션의 특징에 따라 적절한 프로토콜을 선택하여 사용하도록 하자.
            

TCP

         연결방식
                연결기반(connection-oriented)
                        - 연결 후 통신(전화기)
                        - 1:1 통신방식
                
        특징
                데이터의 경계를 구분안함(byte-stream)
                신뢰성 있는 데이터 전송
                        - 데이터의 전송순서가 보장됨
                        - 데이터의 수신여부를 확인함(데이터가 손실되면 재전송됨)
                        - 패킷을 관리할 필요가 없음
                UDP보다 전송속도가 느림

        관련 클래스
                Socket
                ServerSocket

            

UDP

        연결방식
                비연결기반(connectionless-oriented)
                        - 연결 없이 통신(소포)
                        - 1:1, 1:n, n:n 통신방식

        특징
                데이터의 경계를 구분함(datagram)
                신뢰성 없는 데이터 전송
                        - 데이터의 전송순서가 바뀔 수 있음
                        - 데이터의 수신여부를 확인 안함(데이터가 손실되어도 알 수 없음)
                        - 패킷을 관리해주어야 함
                TCP보다 전송속도가 빠름
                
        관련 클래스
                DatagramSocket
                DatagramPacket
                MulticastSocket

TCP를 이용한 통신은 전화에, UDP를 이용한 통신은 소포에 비유된다.

TCP는 데이터를 전송하기 전에 먼저 상대편과 연결을 한 후에 데이터를 전송하며, 
잘 전송되었는지 확인하고 전송에 실패했다면 해당 데이터를 재전송하기 때문에 신뢰 있는 데이터의 전송이 요구되는 통신에 적합하다.
예를 들면 파일을 주고받는데 적합

UDP는 상대편과 연결하지 않고 데이터를 전송하며,
데이터를 전송하지만 바르게 수신되었는지 확인하지 않기 때문에 데이터가 전송되었는지 확인할 길이 없다.
데이터를 보낸 순서대로 수신한다는 보장도 없다.
대신 확인과정이 필요 없기 때문에 TCP에 비해 빠른 전송이 가능하다.
게임이나 동영상의 데이터를 전송하는 경우와 같이, 
데이터가 중간에 손실되어 좀 끊기더라도 빠른 전송이 필요할 때 적합하다.
이 때 전송 순서가 바뀌어 늦게 도착한 데이터는 무시하면 된다.
    
    

TCP소켓 프로그래밍

클라이언트와 서버간의 일대일 통신

먼저 서버 프로그램이 실행되어 클라이언트 프로그램의 연결요청을 기다리고 있어야 한다.

 

서버 프로그램과 클라이언트 프로그램간의 통신과정을 단계별로 보면 다음과 같다.

1. 서버 프로그램에서는 서버소켓을 사용해서 서버 컴퓨터의 특정 포트에서 클라이언트의 연결요청을 처리할 준비를 한다.
2. 클라이언트 프로그램은 접속할 서버의 IP주소와 포트 정보를 가지고 소켓을 생성해서 서버에 연결을 요청한다.
3. 서버 소켓은 클라이언트의 연결요청을 받으면 서버에 새로운 소켓을 생성해서 클라이언트의 소켓과 연결되도록 한다.
4. 이제 클라이언트의 소켓과 새로 생성된 서버의 소켓은 서버소켓과 관계없이 일대일 통신을 한다.

서버소켓은 포트와 결합되어 포트를 통해 원격 사용자의 연결요청을 기다리다가,
연결 요청이 올 때마다 새로운 소켓을 생성하여 상대편 소켓과 통신할 수 있도록 연결한다.
여기까지가 서버소켓의 역할이고, 
실제적인 데이터 통신은 서버소켓과 관계없이 소켓과 소켓 간에 이루어진다.

여러 개의 소켓이 하나의 포트를 공유해서 사용할 수 있지만, 서버소켓은 포트를 독점한다.
만약 한 포트를 둘 이상의 서버소켓과 연결하는 것이 가능하다면,
클라이언트 프로그램이 어떤 서버소켓과 연결되어야하는지 알 수 없을 것이다.

포트는 호스트(컴퓨터)가 외부와 통신을 하기 위한 통로로, 
하나의 호스트가 65536개의 포트를 가지고 있으며 포트는 번호로 구별된다.

포트의 번호는 0 ~ 65535의 범위에 속하는 값인데, 
보통 1023번 이하의 포트는 FTP나 Telnet과 같은 기존의 다른 통신 프로그램들에 의해서 사용되는 경우가 많기 때문에, 
1023번 이상의 번호 중에서 사용하지 않는 포트를 골라서 사용해야 한다.

참고            

두 서버소켓이 서로 다른 프로토콜을 사용하는 경우에는 같은 포트를 사용할 수 있다.
포트는 같아도 클라이언트 프로그램이 사용하는 프로토콜로 어떤 서버소켓과 연결되어야 하는지 구별할 수 있기 때문이다.
그래도 가능하면 하나의 포트는 하나의 서버소켓만 사용하도록 하는 것이 바람직하다.

정리하면, 서버소켓은 소켓간의 연결만 처리하고 실제 데이터는 소켓들끼리 서로 주고받는다.

소켓들이 데이터를 주고받는 연결통로가 입출력스트림이다.

소켓은 두 개의 스트림, 입력스트림과 출력스트림을 가지고 있으며,
이 스트림들은 연결된 상대편 소켓의 스트림들과 교차연결된다.

한 소켓의 입력스트림은 상대편 소켓의 출력스트림과 연결되고, 출력스트림은 입력스트림과 연결된다.

그래서 한 소켓에서 출력스트림으로 데이터를 보내면 상대편 소켓에서는 입력스트림으로 받게 된다.

이것 역시 앞서 비유한 전화기와 비슷해서 소켓이 두 개의 입출력스트림을 갖는 것처럼,
전화기 역시 입력과 출력을 위한 두 개의 라인을 갖고 있다.

자바에서는 TCP를 이용한 소켓프로그래밍을 위해 Socket과 ServerSocket클래스를 제공하고 있다.

Socket          

프로세스간의 통신을 담당하며, InputStream과 OutputStream을 가지고 있다.
이 두 스트림을 통해 프로세스간의 통신(입출력)이 이루어진다.

ServerSocket    

포트와 연결되어 외부의 연결요청을 기다리다 연결요청이 들어오면 Socket을 생성해서 소켓과 소켓간의 통신이 이루어지게 한다.
한 포트에 하나의 ServerSocket만 연결할 수 있다.(프로토콜이 다르면 같은 포트를 공유할 수 있다.)

import java.net.*;
import java.io.*;
import java.util.Date;
import java.text.SimpleDateFormat;

public class TcpIpServer{
    public static void main(String args[]){
        ServerSocket serverSocket = null;

        try{
            // 서버소켓을 생성하여 7777번 포트와 결합시킨다.
            serverSocket = new ServerSocket(7777);
            System.out.println(getTime() + "서버가 준비되었습니다.");
        } catch(IOException e){
            e.printStackTrace();
        }

        while(true){
            try{
                System.out.println(getTime() + "연결요청을 기다립니다.");
                // 서버소켓은 클라이언트의 연결요청이 올 때까지 실행을 멈추고 계속 기다린다.
                // 클라이언트의 연결요청이 오면 클라이언트 소켓과 통신할 새로운 소켓을 생성한다.
                Socket socket = serverSocket.accept();                  // 클라이언트로부터 요청이 오면 accept()로 수락, 통신용 socket 생성
                System.out.println(getTime() + socket.getInetAddress() + "로부터 연결요청이 들어왔습니다.");

                // 소켓의 출력 스트림을 얻는다
                OutputStream out = socket.getOutputStream();
                DataOutputStream dos = new DataOutputStream(out);

                // 원격 소켓(remote socket)에 데이터를 보낸다.
                dos.writeUTF("[Notice] Test Message1 from Server.");
                System.out.println(getTime() + "데이터를 전송했습니다.");

                // 스트림과 소켓을 닫아준다.
                dos.close();
                socket.close();
            } catch(IOException e){
                e.printStackTrace();
            }
        }
    }

    static String getTime(){
        SimpleDateFormat f = new SimpleDateFormat("[hh:mm:ss]");
        return f.format(new Date());
    }
}
결과
C:\jdk1.8\work\ch16>java TcpIpServer
        [01:24:31]서버가 준비되었습니다.
    [01:24:31]연결요청을 기다립니다.
    [01:24:57]/127.0.0.1로부터 연결요청이 들어왔습니다.
    [01:24:57]데이터를 전송했습니다.
    [01:24:57]연결요청을 기다립니다.
    ^C
C:\jdk1.8\work\ch16>


TCP/IP서버를 구현한 것이다.

이 예제를 실행하면 서버소켓이 7777번 포트에서 클라이언트 프로그램의 연결요청을 기다린다.
클라이언트의 요청이 올 때까지 진행을 멈추고 계속 기다린다.

클라이언트 프로그램이 서버에 연결을 요청하면, 
서버소켓은 새로운 소켓을 생성하여 클라이언트 프로그램의 소켓(원격소켓)과 연결한다.

새로 생성된 소켓은 "[Notice] Test Message1 from Server."라는 데이터를 원격소켓에 전송하고 연결 종료

그리고 서버소켓은 다시 클라이언트 프로그램의 요청을 기다린다.

위 실행결과는 서버 프로그램(TcpIpServer.java)을 실행시킨 후 클라이언트 프로그램(TcpIpClient.java)를 실행시키고,
바로 컨트롤 + C로 서버 프로그램을 종료시킨 것이다.

클라이언트 프로그램의 요청을 지속적으로 처리하기 위해 무한 반복문을 사용했기 때문에,
서버 프로그램을 종료시키려면 컨트롤 + C를 눌러서 강제종료 시켜야 한다.

import java.net.*;
import java.io.*;

public class TcpIpClient{
    public static void main(String args[]){
        try{
            String serverIp = "127.0.0.1";

            System.out.println("서버에 연결중입니다. 서버 Ip :" + serverIp);
            // 소켓을 생성하여 연결을 요청한다.
            Socket socket = new Socket(serverIp, 7777);

            // 소켓의 입력스트림을 얻는다.
            InputStream in = socket.getInputStream();
            DataInputStream dis = new DataInputStream(in);

            // 소켓으로부터 받은 데이터를 출력한다.
            System.out.println("서버로부터 받은 메시지 :" + dis.readUTF());
            System.out.printlb("연결을 종료합니다.");

            // 스트림과 소켓을 닫는다.
            dis.close();
            socket.close();
            System.out.println("연결이 종료되었습니다.");

        } catch (ConnectException e){
            e.printStackTrace();
        } catch (IOException e){
            e.printStackTrace();
        } (Exception e){
            e.printStackTrace();
        }
    }
}
결과
C:\jdk1.8\work\ch16>java TcpIpClient
서버에 연결중입니다. 서버Ip :127.0.0.1
서버로부터 받은 메시지 :[Notice] Test Message1 from Server.
연결을 종료합니다.
연결이 종료되었습니다.


이전 예제인 TCP/IP서버와 통신하기 위한 클라이언트 프로그램이다.
연결하고자 하는 서버의 IP와 포트번호를 가지고 소켓을 생성하면 자동적으로 서버에 연결요청을 하게 된다.

서버프로그램이 실행되고 있지 않거나, 서버의 전원이 꺼져있어서 서버와 연결을 실패하면 ConnectException이 발생한다.            

서버와 연결되면 소켓의 입력스트림을 얻어서 서버가 전송한 데이터를 읽을 수 있다.
그리고 서버와의 작업이 끝나면 소켓과 스트림을 닫아야 한다.

위 예제에서는 한 대의 호스트에서 서버 프로그램과 클라이언트 프로그램을 테스트할 수 있도록 서버의 IP를 127.0.0.1로 설정하였지만,
원래는 서버가 실제로 사용하고 있는 IP를 지정해주어야 한다.

서버소켓이 x번 포트를 사용하고 있어도, 서버소켓이 아닌 소켓은 x번 포트를 사용할 수 있다.

클라이언트 프로그램의 소켓이 사용하는 포트는 사용가능한 임의의 포트가 선택된다.

ServerSocket클래스의 setSoTimbeout(int timeout)을 사용해서 서버소켓의 대기시간을 지정할 수 있다.
timeout의 값은 천분의 일초단위이며 0을 입력하면 제한시간 없이 대기하게 된다.
지정한 대기시간이 지나면 accept()에서 SocketTimeoutException이 발생하므로 catch문에서 적절한 처리를 할 수 있다.
            
서버에 접속하는 클라이언트의 수가 많을 때는 쓰레드를 이용해서 클라이언트의 요청을 병렬적으로 처리하는 것이 좋다.

그렇지 않으면 서버가 접속을 요청한 순서대로 처리하기 때문에 늦게 접속을 요청한 클라이언트는 오랜 시간을 기다릴 수 있다.

멀티 채팅 서버 프로그램 예시 보고 싶으면 p.976

    

UDP 소켓 프로그래밍

TCP 소켓 프로그래밍에서는 Socket과 ServerSocket을 사용하지만,
UDP 소켓 프로그래밍에서는 DatagramSocket과 DatagramPacket을 사용한다

UDP는 연결지향적인 프로토콜이 아니기 때문에 ServerSocket이 필요하지 않다.

UDP 통신에서 사용하는 소켓은 DatagramSocket이며 데이터를 DatagramPacket에 담아서 전송한다.

DatagramPacket은 헤더와 데이터로 구성되어 있으며,
헤더에는 DatagramPacket을 수신할 호스트의 정보(호스트의 주소와 포트)가 저장되어 있다.
소포(packet)에 수신할 상대편의 주소를 적어서 보내는 것과 같다고 이해하면 된다.

728x90
반응형