개인참고자료/자바(네트워크)

UDP 프로그래밍 - 타임 서버와 클라이언트 작성

경진 2008. 7. 19. 18:11
 UDP를 이용한 타임 서버와 클라이언트 작성

타임 서버는 에코 서버와 동작하는 방법이 거의 비슷하다. 다른 점이 있다면 에코 서버의 경우에는 클라이언트가 전송할 데이터를 그대로 반송하는 반면, 타임 서버는 클라이언트가 시간에 대해서 요청을 보내면 서버의 시간을 구해서 시간에 대한 요청을 보낸 클라이언트에게 현재 시간에 대한 정보를 전송하게 된다.

타임 서버와 클라이언트의 경우에는 TCP보다는 UDP를 이용하는 것이 알맞다. 그 이유는 전송하는 데이터가 간단하고 빠르게 반응하게 하기 위해서인데, 강조한대로 TCP의 경우는 연결지향성이기 때문에 UDP보다 초기 응답시간이 느리기 때문이다.

컴퓨터 여러 대를 운용할 경우, 각각의 컴퓨터 시간이 항상 같게 설정하려면 여간 번거로운 일이 아니다. 이런 것을 해결하기 위해 타임 서버를 사용한다. 타임 서버는 정확한 시간을 클라이언트에게 제공하는 서버로써 클라이언트는 타임 서버로부터 시간을 읽어와서 시간을 동기화 시키는 것이다.

보통 타임 서버는 UDP 방식으로 통신하며, 123번 포트에서 동작한다. 이번 예제는 실제 타임 서버와는 다른 값을 주고받지만, 타임 서버라는 것이 어떤 방식으로 동작 하는지는 쉽게 이해할 수 있다.

타임 서버 프로그래밍

타임 서버는 먼저 클라이언트로 부터 DatagramPacket을 전송 받아야 한다. 이때 DatagramPacket에 저장한 후, 클라이언트에게 전송한다.

import java.net.*;           
import java.util.*;            
           
public class UDPTimeServer {           
    public static void main(String[] args) {       
        if(args.length != 1){   
            System.out.println("사용법 : java UDPEchoServer port");
            System.exit(1);
        }   
        int port = 0;   
        try{   
            port = Integer.parseInt(args[0]);
        }catch(Exception ex){   
            System.out.println("port 번호는 양의 정수로 입력하여 주세요.");
            System.exit(1);       
        }           
        DatagramSocket dsock = null;           
        try{           
            System.out.println("접속 대기상태입니다.");       
            dsock = new DatagramSocket(port);       
            String line = null;       
            while(true){       
                // 받기   
                byte[] buffer = new byte[1024];   
                DatagramPacket receivePacket = new DatagramPacket(buffer, buffer.length);   
                dsock.receive(receivePacket);   
                java.text.SimpleDateFormat format   
                    = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss a");
                String sdate = format.format(new Date());   
                System.out.println(receivePacket.getAddress().getHostAddress() + " 에 현재시간 " + sdate + " 을 전송합니다.");   
                DatagramPacket sendPacket = new DatagramPacket(sdate.getBytes(), sdate.getBytes().length, receivePacket.getAddress(), receivePacket.getPort());   
                dsock.send(sendPacket);   
            } // while       
        }catch(Exception ex){           
            System.out.println(ex);       
        }finally{           
            if(dsock != null)       
                dsock.close();   
        }           
    } // main               
} // class                   

클라이언트에게 DatagramPacket을 전송하거나 수신하기 위해서 DatagramSocket을 생성한다. DatagramSocket을 생성했다면, 클라이언트의 요청을 받아들이기 위해서 receive() 메소드를 호출한다. 이때 receive() 메소드에는 비어있는 값을 가진 DatagramPacket 객체를 인자로 지정하게 된다.

……
            dsock = new DatagramSocket(port);       
            String line = null;       
            while(true){       
                // 받기   
                byte[] buffer = new byte[1024];   
                DatagramPacket receivePacket = new DatagramPacket(buffer, buffer.length);   
                dsock.receive(receivePacket);   
……

현재 시간을 "2008-07-19 07:07:08 오전" 형식으로 클라이언트에게 전송하려고 현재 시간의 정보를 가진 Data 객체를 생성한 후, java.text.SimpleDateFormat 객체를 이용해서 형식을 바꾸었다.

                java.text.SimpleDateFormat format   
                    = new java.text.SimpleDateFormat("yyyy-MM-dd HH:mm:ss a");
                String sdate = format.format(new Date());   

서버에게 전달된 receivePacket을 통해서 클라이언트의 InetAddress와 port 값을 구하고 클라이언트에게 보낼 DatagramPacket에 지정했다. 물론, 위에서 구한 현재 시간에 대한 정보도 바이트 배열 형태로 지정했다.

이렇게 지정된 DatagramPacket은 send() 메소드를 이용해서 클라이언트에게 전송하게 된다.

                DatagramPacket sendPacket = new DatagramPacket(sdate.getBytes(), sdate.getBytes().length, receivePacket.getAddress(), receivePacket.getPort());   
                dsock.send(sendPacket);   

타임 클라이언트 프로그래밍

UDP 방식을 이용한 타임 클라이언트는 가장 먼저 서버에게 DatagramPacket을 전송하는 일부터 시작한다. 그 이유는 서버 쪽에게 클라이언트의 IP를 알리도록 하기 위해서다. 이후에 서버에서 현재 시간에 대한 정보를 담고 있는 DatagramPacket을 전송하면 데이터를 읽어 들여 화면에 출력하게 된다

import java.net.*;            
           
public class UDPTimeClient {           
    public static void main(String[] args) {       
        if(args.length != 2){   
            System.out.println("사용법 : java UDPEchoClient ip port");
            System.exit(1);
        }   
        String ip = args[0];   
        int port = 0;   
        try{   
            port = Integer.parseInt(args[1]);
        }catch(Exception ex){   
            System.out.println("port 번호는 양의 정수로 입력하여 주세요.");
            System.exit(1);
        }       
        InetAddress inetaddr = null;       
        try {       
            inetaddr = InetAddress.getByName(ip);   
        } catch (UnknownHostException e) {       
            System.out.println("잘못된 도메인이나 ip입니다.");   
            System.exit(1);   
        }       
        DatagramSocket dsock = null;       
        try{       
            dsock = new DatagramSocket();   
            String line = null;   
            // 전송   
            DatagramPacket sendPacket = new DatagramPacket("".getBytes(), "".getBytes().length, inetaddr, port);   
            dsock.send(sendPacket);   
               
            byte[] buffer = new byte[200];   
            DatagramPacket receivePacket = new DatagramPacket(buffer, buffer.length);   
            dsock.receive(receivePacket);   
            // 받은 결과 출력.   
            String msg = new String(receivePacket.getData(), 0, receivePacket.getData().length);   
            System.out.println("서버로 부터 전달받은 시간 :" + msg.trim());   
        }catch(Exception ex){       
            System.out.println(ex);   
        }finally{       
            if(dsock != null)   
                dsock.close();
        }       
    } // main           
} // class               

서버에게 DatagramPacket을 전송한다. 서버에게 클라이언트의 IP 주소를 알리는 것이 목적이기 때문에 전송하는 데이터는 아무런 값이 없는 문자열을 바이트 배열 형태로 지정해서 전송했다.

            DatagramPacket sendPacket = new DatagramPacket("".getBytes(), "".getBytes().length, inetaddr, port);   
            dsock.send(sendPacket);   

서버로부터 현재 시간에 대한 정보를 읽어 들여 화면에 출력한다.

            byte[] buffer = new byte[200];   
            DatagramPacket receivePacket = new DatagramPacket(buffer, buffer.length);   
            dsock.receive(receivePacket);   
            // 받은 결과 출력.   
            String msg = new String(receivePacket.getData(), 0, receivePacket.getData().length);   
            System.out.println("서버로 부터 전달받은 시간 :" + msg.trim());   

클라이언트와 서버 프로그래밍 실행

타임 서버 실행 화면

타임 서버 실행 화면

타임 클라이언트 실행 화면

타임 클라이언트 실행 화면

타임 서버와 클라이언트 프로그래밍 응용

UDP를 이용한 타임 서버와 클라이언트는 서버로부터 시간을 구해서 출력하는 예제였다. 이런 방식을 이용해서 클라이언트의 버전 관리를 할 수 있다.

요즘 배포되는 프로그램들을 보면 자동으로 업데이트를 하는 프로그램이 많다. 자바에서도 이런 애플리케이션을 만들 수 있다. 최신 버전에 대한 정보를 전달하는 서버를 작성해서 실행한 후, 클라이언트에서 서버로부터 최신 버전이 어떤 값을 가지고 잇는지 알아내도록 해서 업데이트하게 할 수 있는 것이다.

이때 최신 버전이 어떤 값을 가지고 있는지를 알아낼 때에는 반응 속도가 빠른 것이 좋기 때문에 UDP를 이용한 방법을 사용하고, 현재 버전이 최신 버전이 아닐 경우에는 클래스를 업데이트하기 위해서 TCP 방법을 이용해서 신뢰성 있는 통신 방법을 사용하는 편이 좋다.