TCP 클라이언트와 TCP 서버의 간단한 구조

 

TCP 네트워크에서 읽고,쓰기의 기본적인 모델인 클라이언트, 서버 모델을 이용해서

TCP를 이용한 읽고,쓰기를 해보았습니다.

 

일반적으로 서버의 경우에는 불특정 다수인 클라이언트에게 서비스를 제공하는 입장입니다.

서버는 클라이언트가 요청하는 서비스를 언제든 제공할 수 있도록 준비가 되어 있는게 보통

입니다. 서버가 서비스 할 준비되어 있지 않다면 당연한 얘기로 클라이언트는 서버가 제공

하는 서비스를 받지 못하니까요.

 

지금까지 만들어본 서버 중에 NameServer CopyServer는 단 한번만, 단 하나의

클라이언트에게 서비스를 제공합니다. 단 한번 서비스를 하고 종료를 해버리기 때문입니다.

클라이언트에게 서비스를 제공하고 있는 프로그램이므로 서버라고 불리기는 하지만 사실

이런 서비스 구조로는 서버라고 불리기도 부끄럽지요.

 

 일반적으로 서버를 구현할때는 클라이언트가 서비스 요청하면 언제든 서비스를 받을 수

있도록 반복적으로 수행되는 부분이 서버에 존재합니다.

특별한 경우가 아니면 서버는 종료하지 않습니다.

여기서는 종료하지 않으면서 반복적으로 서비스하는 서버를 만들어 보겠습니다.

가장 기본적인 형태의 TCP 서버입니다.

 

TCP 네트워크 서버의 서비스 객체

 

TCP서버가 제공하는 서비스의 내용은 스타(장나라,이효리,이나영)의 이름을 얻으면,

그 스타의 인사말을 전해주는 건데, 이를 구현한 간단한 객체 Star를 소개합니다.

 

package tcp;                                                                                                                               

 

import java.io.*;

import java.net.*;

import java.util.*;

 

/**

* Socket InputStream으로 스타의 이름을 받으면, Socket OutputStream으로 

* 스타의 인사말을 보내줍니다.

* Star는 스타 이름과 스타의 인사말을 Map에 등록합니다.

*/

public class Star {

 

             private Socket s = null;

             /* 스타의 이름과 스타의 인사말을 Map에 저장합니다.*/

             private Map greetings = new HashMap();                                  

            

            

             public Star(Socket s){                                                                                                                  

                           this.s = s;

                           greetings.put("장나라","장나라예요. 사랑해요 여러부운~");                                

                           greetings.put("이효리","효리에요. 전화주세요");

                           greetings.put("이나영","바쁜데 왜 그래요 자꾸...");

             }

            

             /**

             * Socket InputStream으로 부터 스타의 이름을 읽고, 해당 스타의 등록된

             * 인사말을 OutputStream으로 보내줍니다.

             *

             * @param s TCP 연결된 통신채널

             */

             public void hello() {

                                                    

                           BufferedReader br = null;

                           PrintWriter pw = null;

                           try {

                                        /*

                                        Socket으로부터 InputStream을 얻어 public String readLine() 메소드를

                                        사용할 수 있는 BufferedReader로 변환합니다.*/

                                       

                                        br = new BufferedReader(new InputStreamReader(s.getInputStream()));     

                                        String name = br.readLine();                                                                  

                          

                                        String msg = (String)greetings.get(name);                     

                                        if (msg == null)

                                                     msg = "그런 사람 없어요.";                                                                  

                                        /*

                                        Socket으로부터 OutputStream을 얻어 public void println(String )메소드를

                                        사용할 수 있는 PrintWriter로 변환합니다.*/

                                        pw = new PrintWriter(new OutputStreamWriter(s.getOutputStream()));         

                                        pw.println(msg);                                                                                               

                                        pw.flush();                                                                                                                                 

                           }

                           catch(IOException ie) {

                                        ie.printStackTrace();

                          }

                           finally {

                                        try {

                                                     if (br != null)

                                                                  br.close();

                                                     if (pw != null)

                                                                  pw.close();

                                                     s.close();

                                        }

                                        catch(Exception e) {

                                                     e.printStackTrace();

                                        }

                           }

             }

}           

예제 11 - 6 Star.java

 

Star 객체는 tcp 기반의 Socket으로부터 데이터를 읽고,씁니다.

Star가 가진 의미있는 메소드는 public void hello() 입니다.

TCP 서버가 제공하는 서비스를 구현한 Star TCP 네트워크로부터 정보를 읽고,써야 하는

처지입니다. Star와 같은 네트워크 서비스 객체는 Socket 객체로부터 얻을 수 있는

InputStream,OutputStream을 이용해서 네트워크 클라이언트와 직접 부딪히면서 서비스를

제공하는 가장 최전방 일꾼입니다.

TCP 네트워크 서버가 제공하는 서비스는 Socket 객체로부터 얻어지는 InputStream,

OutputStream을 읽고,쓰는 것으로 시작합니다.

 

Star public String hello() Socket객체로부터 InputStream을 얻고, InputStream으로부터

BufferedReader를 얻은후 public String readLine()으로 네트워크 클라이언트로부터 한줄의

데이터를 읽습니다. , Star는 클라이언트가 TCP 네트워크로 보낸 데이타를 읽게 되는데

그 데이터는 반드시 newline으로 끝이 나야합니다.

클라이언트가 보낸 데이터가 newline으로 끝나지 않을 경우에는 public String readLine()

리턴되지 않고 public Stirng readLine()을 호출한 상태에서 프로그램은 멈춰있습니다.

public String readLine() 메소드는 newline을 읽거나 EOF를 읽어야만 리턴이 되므로,

클라이언트가 정상적으로 newline을 보내던가 혹은 TCP 네트워크 연결을 끊어야만

(Socket close) public String readLine()이 리턴됩니다.

 

Star는 아주 단순한 프로토콜을 가지고 있습니다.

웹브라우저와 웹서버간에 데이타를 주고 받는 프로토콜인 HTTP에 비하면 엄청 간단합니다.

TCP 네트워크로부터 데이타를 읽는데, newline을 읽어 낼 때 까지 읽습니다.

그리고 결과 데이터를 TCP 네트워크로 보내고 마지막에 newline을 보냅니다.

 

Star TCP 네트워크로 부터 읽은 스타의 멘트가 있는지 확인하고, 멘트가 있다면

해당 멘트와 newline을 함께 보내고 멘트가 없다면 "그런 사람 없어요" 멘트와 newline

보냅니다. PrintWriter println()이라는 메소드가 있어서 public void println(String msg)

쓸 수 있습니다.

public void println(String msg)msg를 스트림으로 출력할 때 newline을 추가합니다.

 

Iterative TCP 네트워크 서버

 

이제 Star가 갖고 있는 서비스를 TCP 네트워크 클라이언트에게 제공할 수 있도록

TCP 네트워크 서버를 만들겠습니다.

이 서버는 Iterative 서버입니다.

 

package tcp;                                                                                                                               

 

import java.io.*;

import java.net.*;

import java.util.*;

 

/**

* TCP 네트워크 클라이언트에게 스타의 인사말 서비스를 제공합니다.

*/

public class IterativeServer {            

             /**

             * 클라이언트가 TCP 포트 8989 Listen하면서 TCP 연결을 한후,

             * 스타의 인사말을 요청하면 해당 스타의 인사말을 보내줍니다.

             */

             public static void main(String[] args) {

                           try {

                                        /* TCP 포트 8989 Listen 합니다.*/

                                        ServerSocket ss = new ServerSocket(8989);    

                                       

                                        /* 서비스 중지하지 않고 계속 서비스 합니다.

                                           서비스를 하는 서버가 단일 Thread이므로 여러 클라이언트가

               동시에 서비스를 요청하더라도 순차적으로 하나씩 서비스 합니다.  */

              

                                        while (true) {                                 

                                                     /* 클라이언트가 TCP 연결을 할때까지 기다립니다.

                                                     Star객체와 Socket을 이용해서 서비스합니다.*/                                                               

                                                     Socket s = ss.accept();                                                           

                                                     Star star = new Star(s);                                                           

                                                     star.hello();                                                                                         

                                        }

                           }

                           catch(IOException io) {

                                        io.printStackTrace();

                           }

             }

}

예제 11 - 7 IterativeServer.java

 

IterativeServer public static void main(String[] args)를 살펴보면

ServerSocket 8989포트로 만듭니다. TCP포트의 8989를 통해서 서비스하려고 합니다.

IterativeServerStar 서비스를 받고자 하는 클라이언트는 TCP 8989포트로 TCP 연결을

시도합니다. IterativeServer 프로그램은 절대 종료하지 않는 반복문의 구조를 갖고 있습니다.

(while문안의 코드를 끊임없이 반복합니다. )

서버가 365 24시간 서비스하고자 한다면 절대로 종료되어서는 안됩니다.

대부분의 TCP 네트워크 서버는 종료하지 않는 이 구조를 공통적으로 가지고 있습니다.

 

TCP 서버가 수행하는 반복되는 서비스 코드는 while 문안에 구현되어 있습니다.

서버는 우선 클라이언트가 TCP 네트워크 연결을 할 때까지 줄창 기다립니다.

그러다가 일단 클라이언트가 TCP 연결을 하면 그때서야 서버는 Socket 객체를 얻습니다.

그후 Socket을 이용해서 Star서비스의 핵심인 Star객체를 생성하고 public void hello()

메소드를 호출하여 클라이언트에게 서비스를 제공합니다.

 

이 단순한 반복 구조가 네트워크기반의 서버가 가지는 대표적인 구조입니다.

반복문 바깥에서 ServerSocket을 생성하고 반복문안에서 TCP 연결을 기다리고

TCP 연결이 된후에서는 만들어진 Socket객체를 이용해서 서비스 객체를 만들고

서비스 객체로 하여금 클라이언트를 서비스하게 합니다.

간단하죠? 실제로도 그렇습니다.

그림 11 - 8 tcp.IterativeServer를 실행한 모습입니다.

 

사용자 삽입 이미지

그림 11 - 8

 

TCP 네트워크 클라이언트

 

이제 스타의 인사말 서비스를 이용하는 TCP 네트워크 클라이언트를 만들어 보겠습니다.

 

package tcp;       

                                                                               

import java.io.*;

import java.net.*;

/**

* 스타인사말 서비스를 이용하는 TCP 네트워크 클라이언트입니다.

*/

public class Fan {

 

             /**

    인사말을 듣고 싶은 스타의 이름을 입력하면 스타의 인사말을 리턴합니다.

    스타의 인사말은 스타 인사말 서비스를 제공하는 TCP 네트워크 서버로부터

    얻습니다.

    * @param name 인사말 듣고 싶은 스타이름

    * @return 스타의 인사말

    */

             public String greeting(String name) {

                           Socket s = null;

                           PrintWriter pw = null;

                           BufferedReader br = null;

                           String msg = "";

 

                           /*

        스타인사말 서비스를 제공하는 TCP 네트워크 서버에 연결한후

        인사말을 듣고 싶은 스타의 이름과 newline을 서버에 준후

        서버가 알려주는 스타의 인사말과 newline을 읽고, 그 인사말을 읽습니다. */

 

                           try {

                                        s = new Socket("localhost",8989);                               

                                        pw = new PrintWriter(new OutputStreamWriter(s.getOutputStream()));         

                                        pw.println(name);                                                                                                           

                                        pw.flush();                       

                                                                                                                                                              

                                        br = new BufferedReader(new InputStreamReader(s.getInputStream()));                   

                                        msg = br.readLine();          

                           }

                           catch(IOException ie) {

                                        ie.printStackTrace();

                           }

                           finally {

                                        try {       

                                                     if (br != null)                                                              

                                                                  br.close();

                                                     if (pw != null)

                                                                  pw.close();

                                                     if (s != null)

                                                                   s.close();

                                        }

                                        catch(Exception e) {

                                                     e.printStackTrace();

                                        }

                                        finally {

                                                     return msg;

                                        }

                           }

             }

 

             public static void main(String[] args) {

            

                           if (args.length != 1) {

                                        System.out.println("java -classpath CLASSPATH tcp.Fan name");

                                        System.exit(1);

                           }

                           String name = args[0];        // 인사말 듣고 싶은 스타의 이름

 

                           String msg = new Fan().greeting(name);                      

                           System.out.println(msg);

             }

}

예제 11 - 8 Fan.java

 

Fan 클래스에 메소드를 하나 정의해 놓았는데, 스타의 이름을 인자로 받으면,

스타 인사말 서비스를 제공하는 TCP 네트워크 서버로부터 스타가 말해놓은 인사말 멘트를

얻어서 리턴하는 메소드입니다.

이를 구현하기 위해서 TCP 네트워크 연결을 한후 Socket을 얻고, Socket으로부터

OutputStream을 얻은후 인사말 듣고 싶은 스타의 이름을 네트워크로 씁니다.

그리고 Socket 으로부터 얻은 InputStream으로부터 스타의 인사말 데이터를 읽은후

그 데이터를 리턴합니다.

 

클라이언트와 서버간에 데이터를 읽고,쓰는 관계는 항상 반대입니다.

,클라이언트가 쓰기를 하는 순간에 서버가 반드시 읽기를 해야하고, 서버가 쓰기를 하는

순간은 클라이언트가 읽기를 해야합니다.

TCP 네트워크를 통해서 읽고,쓰기를 하고자 한다면 이런 규칙을 잘 만들어야 합니다.

이것이 곧 프로토콜입니다.

 

Fan은 대표적인 TCP 네트워크 클라이언트의 구조를 가지고 있습니다.

서버로 TCP 연결을 해서 Socket객체를 얻고, Socket객체로부터 OutputStream

구하고 OutputStream을 이용해서 서버에게 서비스를 요청하고 (서버와 약속한 정보를

OutputStream으로 쓰고) Socket객체로부터 InputStream을 만들고 InputStream을 이용

해서 서버로부터 정보를 읽습니다. 그리고 원하는 바를 모두 마치면 네트워크 연결에

사용된 시스템 자원을 모두 풀어줍니다.

매우 간명한 구조이지만 대부분의 클라이언트는 이 범주에서 벗어나지 않습니다.

 

IterativeServer를 실행시킨 후 Fan 클라이언트를 실행시켜 보겠습니다.

 

 

사용자 삽입 이미지

그림 11 - 9

 

Fan 클라이언트는 TCP 네트워크를 이용해서 IterativeServer에게 스타 인사말 서비스를

받고 있습니다.

이와 함께 IterativeServer는 여러 번 Fan클라이언트에게 서비스를 제공했음에도 종료하지

않고 계속 서비스를 하고 있습니다. IterativeServer가 클라이언트에게 서비스를 제공한

후에도 종료하지 않은 것은 CopyServer NameServer와 달리 클라이언트에게 서비스를

제공한 후에도 종료하지 않도록 루프문 안에서 서비스 객체를 구성했기 때문입니다.

 

Fan클라이언트와 IterativeServer의 관계는 TCP 네트워크 기반의 클라이언트와 서버

프로그램의 대표적인 구조입니다.

Posted by
,