2010. 1. 28.

TCP 통신하기.. Thread버전..

원래애도 쓰레드로 움직이기는 한데..
여기서는 입출력부분을 쓰레드 영역으로 분리시켜보았다.

(defn echo-thread [socket]
  (let [reader (new java.io.BufferedReader (new java.io.InputStreamReader (. socket getInputStream)))
        out (. socket getOutputStream)
        pout (new java.io.PrintStream out)
        msg (. reader readLine)]
    (. socket shutdownInput)
    (. pout print msg)
    (. out flush)
    (. out close)
    (. socket close)))


(defn test-srv3 []
  (let [server (new java.net.ServerSocket 10013)]
    (loop [nextClient (. server accept)]
      (.start (new Thread (fn [] (echo-thread nextClient))))
      (recur (. server accept)))
    (. server close)))


그다지 깨끗한 코드는 아니지만.. 어쨌든 이제 소켓 생성하고 데이터를 내보내는 것은 무난히 이루어짐..


TCP 소켓 통신하기 php <-> clojure

일단 소스 코드부터..
아래는 Clojure 테스트 소스이다.

(defn test-srv2 []
  (let [server (new java.net.ServerSocket 10013)]
    (loop [nextClient (. server accept)]
      (let [reader (new java.io.BufferedReader (new java.io.InputStreamReader (. nextClient getInputStream)))
            out (. nextClient getOutputStream)
            pout (new java.io.PrintStream out)
            msg (. reader readLine)]
        (. nextClient shutdownInput)
        (if (not (= msg "exit"))
          (do
            (. pout print msg)
            (. out flush)
            (. out close)
            (. nextClient close)
            (recur (. server accept)))
          (do
            (. out close)
            (. nextClient close)))))
    (. server close)))
다음은 PHP 클라이언트 소스 코드이다.


$port = 10013;
$address = "localhost";

$socket = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
if($socket == false) {
  die("socket create_failed");
}

$result = socket_connect($socket, $address, $port);
if($result == false) {
  die("socket connection failed");
}

$msg = $_GET['msg'];
if( strlen($msg) == 0 ) {
  $msg = "default message";
}

$msg .= "\n";
socket_write($socket, $msg,  strlen($msg));

//header("Content-type: text/xml");
while($out = socket_read($socket, 2048)) {
  echo $out;
}
socket_close($socket);
  

이 소스는 1:1 연결이 가능하다. msg 라는 인자로 해당 php를 호출하면, 그에 따른 문장을 돌려준다. (일종의 echo서버..)
그런데 여기서 문제가 있다. 서버에서 read/write 하는 부분은 한 쓰레드 안에 들어있기때문에(서버 생성과 동일한 스레드) 한번에 하나씩밖에 연결을 처리한다.
다음으로 해야할 일은 서버를 만드는 것은 한번만 하고, 새로운 접속이 일어나면 해당하는 소켓을 인자로 넘겨받아 처리하는 스레드 루틴이 필요하다.

2010. 1. 23.

JNpDp 6장 Part 1 TCP 보내기 / 받기

소켓과 패킷을 따로 만들어 관리하던 UDP와는 달리 Java에서 TCP는 직접적으로 소켓을 열어 관리한다.
이는 패킷을 보내기만하고 따로 접속은 관리하지않는 UDP와는 근본적으로 다른 TCP의 방식때문이다.
TCP는 접속을 하는 순간부터 끊을때까지 클라이언트와 서버의 연결이 지속되는 구조이다. 따라서, 신뢰성높고, 에러를 검출할 수 있는 방식으로 동작한다.

TCP를 열기 위해서는 서버쪽에서는 ServerSocket을, 클라이언트에서는 Socket을 생성해야한다.

(let [server (new java.net.ServerSocket port)] ... )
(let [daytime (new java.net.Socket host port)] ... )

여기서 host는 문자열이 들어갈 수 있다. DatagramPacket과는 또 다르다.(왜 여기에는 InetAdress를 넣고.. 저기서는 문자열을 넣는지.. )

생성된 소켓에 데이터를 읽고, 쓰는 방식을 위해 Socket은 getInputStream과 getOutputStream을 통해서 스트림 객체를 반환한다. 그러므로, 다음과 같은 방법으로 읽고 쓸 수 있다.
      (let [nextClient (. server accept)
            out (. nextClient getOutputStream)
            pout (new java.io.PrintStream out)]
        (. pout print (new java.util.Date))
        (. out flush)
        (. out close)
        (. nextClient close))

      (let [reader (new java.io.BufferedReader (new java.io.InputStreamReader (. daytime getInputStream)))]
            (println (. reader readLine)))


서버쪽에서 accept는 DatagramSocket에서 receive와 마찬가지로, 수행을 정지하고 입력을 기다린다. 이에 따른 반환 값은 java.net.Socket으로 이를 통해 데이터를 주고 받는다.
위에서는 서버쪽에서 PrintStream 객체로 포장해 java.io.PrintStream.print 를 통해서 해당 데이터를 출력한다.
반면에 클라이언트 쪽에서는 BufferedReader로 포장해서 java.io.BufferedReader.readLine 을 통해 해당 데이터를 읽었다.

여기서 교훈은, 많이 써야할 함수를 체크해서 결과에 잘 부응할 만한 클래스로 스트림을 묶어야한다는 점이다.

TCP 데이터를 주고 받는 소스는 다음과 같다.

(ns time_server
  (:import [java.net ServerSocket Socket]))

(defn daytime []
  (let [server (new java.net.ServerSocket 10013)]
    (do
      (let [nextClient (. server accept)
            out (. nextClient getOutputStream)
            pout (new java.io.PrintStream out)]
        (. pout print (new java.util.Date))
        (. out flush)
        (. out close)
        (. nextClient close))
      (. server close))))

(defn getday [host port]
  (let [daytime (new java.net.Socket host port)]
    (do
      (. daytime setSoTimeout 2000)
      (let [reader (new java.io.BufferedReader (new java.io.InputStreamReader (. daytime getInputStream)))]
            (println (. reader readLine)))
      (. daytime close))))
     







2010. 1. 22.

JNpDp 5장 Part2 UDP 보내기..

UDP를 보내기 위한 방법이다. UDP를 보내기 위해서도 DatagramSocket과 DatagramPacket이 모두 필요하다.
단 이때 DatagramPacket에 받을 곳의 주소와 포트를 등록해야한다.

이 때 주소는 java.net.InetAddress 형식이다.
따라서 packet을 만드는 방법은 다음과 같다.

(defn test_udp_send []
  (let [socket (new java.net.DatagramSocket)
        remote (. java.net.InetAddress getByName "127.0.0.1")
        bout (new java.io.ByteArrayOutputStream)
        pout (new java.io.PrintStream bout)]
    (do
      (. pout print "Greetings!")
      (let [barray (. bout toByteArray)
            packet (new java.net.DatagramPacket barray (. bout size))]
        (do
          (. packet setAddress remote)
          (. packet setPort 2002)
          (. socket send packet)))
      (. socket close))))

위에서 remote를 만드는 방법을 보면
remote (. java.net.InetAddress getByName "127.0.0.1")
처럼 만들었다. 즉 해당하는 주소는 InetAddress형으로 만들어야 한다는 것..
DatagramPacket을 생성할 때에는 해당하는 Byte Array와 그 길이를 돌려주어야한다. 문제는 byte[] 의 길이를 가져올 방법이 없었다. 그래서 할 수 없이 원 소스인 bout을 이용해서 해당하는 길이를 구했다. 메서드를 실행하는 방법은 쉬운데.. 멤버 변수를 가져올 방법이 없다. 구글링을 해봤는데 찾을 수가 없었음.. =.=

기타 나머지 부분은 UDP 받기에서 했었던 내용이므로 여기서는 패스~

마지막으로 UDP 보내기 부분을 조금 수정했다. 아래가 수정한 내용..

(defn read_from_stream [stream]
  (loop [s (. stream read)]
    (if (not (== s 0))
      (do
        (print (char s))
        (recur (. stream read))))))

(defn test_udp_receive []
  (let [socket (java.net.DatagramSocket. 2002)
        packet (java.net.DatagramPacket. (make-array (Byte/TYPE) 256) 256)
        end_of_work false]
    (do
      (. socket receive packet)
      (println (str "Send By : " (.. packet getAddress getHostAddress)))
      (println (str "Sent from : " (. packet getPort)))
      (let [bin (new java.io.ByteArrayInputStream (. packet getData))
            blength (. packet getLength)]
        (read_from_stream bin))
      (. socket close))))

조금 허접하지만.. 그래도 어느 정도 완성된 것이라고 자위..



2010. 1. 20.

JNpDp 5장.. Part 1 UDP 받기

UDP에 관련된 부분..
UDP는 일단 패킷의 도착을 담보하지도 않고, 도착했다고 하더라도 그 순서를 맞춰주지도 않는다. 말하자면 상당히 간편한 프로토콜로, 작은 데이터를 많이 보낼 때, 해당 데이터를 일일이 검수하지않아도 된다면 UDP도 괜찮은 요소..

UDP에서 핵심되는 부분은 다음의 두 클래스임..
  • java.net.DatagramPacket
  • java.net.DatagramSocket
DatagramPacket은 소켓으로 들어온 데이터를 받는 곳으로, 생성자는 각기 데이터를 넣을 배열과 그 크기를 주어서 보낸다.

DatagramPacket dp = new DatagramPacket(new byte[1024], 1024);

처럼 되어 있는 부분을 Clojure에서는 이렇게 한다.

(java.net.DatagramPacket. (make-array (Byte/TYPE) 4096) 4096)

사실 DatagramPacket을 만들때마다 새로 버퍼를 구성해야할 필요는 없으니까..

(let [buffer (make-array (Byte/TYPE) 4096)
     datagram (java.net.DatagramPacket. buffer 4096)]
...)


정도로 구성하면 된다.

DatagramSocket의 경우 생성자에서 port랑 address를 필요로 한다. address가 생략되면 localhost를 쓴다.

(let [socket (java.net.DatagramSocket. 9999 "127.0.0.1")]
...)


식으로 구성해서 해당 소켓을 이용하면 OK..

UDP 패킷을 읽어오는 Java 프로그램은 이렇다.

DatagramPacket packet = new DatagramPacket(new byte[256], 256);
DatagramPacket socket = new DatagramSocket(2000); // localhost 포트 2000 을 이용함

while(! end_of_work) {
  socket.receive( packet );
  // do other job
}
socket.close();

이것을 Clojure로 변형하면..

  (let [socket (java.net.DatagramSocket. 2000)
        packet (java.net.DatagramPacket. (make-array (Byte/TYPE) 256) 256)
        end_of_work false]
    (do (while (not end_of_work)
          (. socket receive packet)
          ; do other thing
          )
        (. socket close)))


쯤 될 것이다.

테스트용 스크립트는 다음과 같다


(defn test_udp_server []
  (let [socket (java.net.DatagramSocket. 2002)
        packet (java.net.DatagramPacket. (make-array (Byte/TYPE) 256) 256)
        end_of_work false]
    (do
      (. socket receive packet)
      (println (str "Send By : " (.. packet getAddress getHostAddress)))
      (println (str "Sent from : " (. packet getPort)))
      (let [bin (new java.io.ByteArrayInputStream (. packet getData))
            blength (. packet getLength)]
        (println (. bin read)))
      (. socket close))))
해당 스크립트를 실행시키고 hping으로 UDP 데이터를 날려본다.

 sudo hping3 localhost --udp -V -p 2002
정상적으로 실행되면 UDP 1차 테스트는 완료..



JNpDp 4장

기본적인 파일 입출력을 하는 부분이다.

필요할 때가 있을듯하여 unless 매트로를 구성했다.

;; File Read/Write

(defmacro unless [expr form] (list 'if expr nil form))
파일을 열고 닫을 때를 대비해서 파일 기술자(file description)을 얻어본다. 원래는 read, write에 대해서 서로 다른 인스탄스를 리턴해야하지만 일단은 귀차니즘으로 한번에..

(defn file_open [s]
  (java.io.FileInputStream. s))

(defn file_wopen [s]
  (java.io.FileOutputStream. s))

(defn file_close [fd]
  (.close fd))
꽤나 사람 열받게 했던 부분.. reader를 구성해야하는데 일단 reader는 lambda 함수로 구성했다.
해당하는 fd에서 바이트를 읽어오는 형식으로 tail - recursion으로 구현

(defn file_input [fd]
  (let [reader (fn []
                 (try (.read fd)
                      (catch Exception e -1)))]
    (loop [data (reader)]
      (unless (== data -1)
              (do
                (print (str data))
                (recur (reader)))))))
읽은 놈을 다른 파일로 쓰기위한 부분.. 일단 reader를 만들고 이후에는 해당 기술자로 뿌렸다.
원칙대로라면 fd1, fd2도 인자로 계속 돌려줘야하는데.. 귀차니즘으로 포기..
(defn file_output [fd1 fd2]
  (let [reader (fn []
                 (try (.read fd1)
                      (catch Exception e -1)))]
    (loop [src (reader)]
      (unless (== src -1)
              (do
                (.write fd2 src)
                (recur (reader)))))))
이건 실행용 구문.. 별 의미는 없다. do를 써서 복수문장을 돌린 것이 좀 다른 거..
    
(defn file_run [s]
  (let [fd (file_open s)]
    (do
      (file_input fd)
      (file_close fd))))

(defn file_run2 [s1 s2]
  (let [fd1 (file_open s1)
        fd2 (file_wopen s2)]
    (do
      (file_output fd1  fd2)
      (file_close fd2)
      (file_close fd1))))
리더 테스트.. java에서도 스트림을 reader로 묶는데 꽤 애를 먹었는데 여기서도 역시나.. 일단 스트림을 스트림 리더로 묶고.. 다음에 특화된 리더로 다시 묶었다.

;; Reader Test
(defn reader_test []
  (let [bufReader (java.io.BufferedReader.
                   (java.io.InputStreamReader.
                    System/in))]
    (do   (do
      (print "Please Enter Your name :")
      (println "Please to meet you" (.readLine bufReader)))))

      (print "Please Enter Your name :")
      (println "Please to meet you" (.readLine bufReader)))))
swank버그로 인해서 slime내에서는 정상적인 동작이 불가능.. 외부에서 그냥 실행시키면 잘 돈다.









2010. 1. 15.

JNpDp 3장

Java Network Programming and Distributed Computing 3장 예제소스를 Clojure로 바꿔보고 있다.
로컬 호스트를 찾는 부분부터 걸리는데..

// Get the local host
InetAddress localAddress =
InetAddress.getLocalHost();
System.out.println ("IP address : " +
                localAddress.getHostAddress() );


이 부분이다.
Clojure에서는 다음과 같이 일단 해봤다.
(ns inetaddress
  (:import [java.net InetAddress]))

(defn localhost []
  (let [localAddress (java.net.InetAddress/getLocalHost)]
    (print (.getHostAddress localAddress))))
일단 import를 하고, java 메서드를 호출해서 localAddress라는 임시인스탄스를 만든다.
마지막으로 해당 인스탄스로부터 getHostAddress를 호출하게 했다.

그 다음 network resolver 를 쉬운 편..
(defn network_resolver [s]
  (let [addr (java.net.InetAddress/getByName s)]
    (print (str "IP Address : " (.getHostAddress addr)))
    (print (str "Hostname : " (.getHostName addr)))))
라고 해주면 된다. 일단 슬슬 clojure에 익숙해져간다.

2010. 1. 12.

노키아, 심비안, 마에모...

심비안 폐지얘기가 나와서 끄적댑니다.

2009년 한해동안 노키아는 꽤나 애를 먹었습니다.
주력사업인 핸드폰 단말기 시장에서는 후발주자와 힘겨운 싸움을 하고 있었고, 스마트폰 시장, 특히 북미에서는 Apple, RIM에 밀려 시장 점유율, 이익율 두 가지에서 무지 고전을 했더랬죠. 거기다 안드로이드 쇼크까지 터지는 바람에 개발자들의 눈에서 심비안이 밀려나는 상황까지 됐습니다.

이미 이때 심비안 2, 3, 4에 대한 이정표는 나와있었습니다. 2009년 말에 2,  2010년 2분기즈음에 3, 그리고 3와 6개월 텀을 두고 4가 나오는 식이었지요. 근데 스마트폰 시장에서 심비안에 대한 인식은 아주 안좋았습니다. 일부 매체에서는 심비안 60 5ed 를 채용한 N97에 실망했다는 내용도 나왔습니다.(전체적인 기기완성도는 좋다라고 했지만요..) 업친데 덮친격으로 하반기에는 노키아에서 안드로이드를 채용한 핸드셋을 만든다는 루머까지 돌았지요(노키아는 부정했습니다).
결국 이 와중에서 내노라하는 기업분석가/기관에서는 노키아가 심비안을 포기하고, 마에모에 집중할 가능성이 있다는(그들에게는 일종의 희망사항) 보고서를 내놨습니다. 이게 2009년 상황입니다.

하반기에 들어서면서 노키아는 마에모 5를 채용한 N900을 공개했고, 마에모 6에 대한 로드맵을 내놓게됩니다. 그러면서 OS는 2원전략으로 갈 것이라고 공표하죠.
일반 핸드셋 및 저가 스마트폰 부문에서는 심비안을, 플래그쉽 스마트폰에서는 마에모 플랫폼으로 가겠다고 한 것입니다. 문제는 아직까지도 심비안은 제대로 터치스크린(특히나 정전식)에 적응을 못하는 상태라는 점입니다. 멀티터치를 비롯한 각종 UI에 관련해서 상당한 불협화음을 내고 있는데, 이것을 올중반에 나올 새로운 심비안 OS로 대체하겠다는 것이죠.

올 상반기 노키아 스마트폰은 상당히 힘겨울 것으로 생각합니다. 불붙기 시작한 안드로이드 플랫폼이 수십종씩 쏟아지고 있고, 새로운 심비안을 공개하겠다는 중반기에는 Apple의 4G 아이폰이 기다리고 있지요. 결국 이 동안의 스마트폰 싸움을 마에모가 받아줘야하는 상황입니다.

노키아입장에서는 새로운 심비안이 나오기 전까지는 스마트 폰 시장의 시장 점유율이 현 상황을 유지했으면 할 겁니다. 그렇게만 된다면, 새로운 심비안으로 가격/성능비가 뛰어난 시장을, 마에모로는 고사양 스마트폰 시장을 동시에 공략할 수 있으니까요. Qt를 내세우면서 심비안/마에모 양쪽에 사용할 수 있을 것이라고 개발자들에게 지속적으로 홍보하고 있고(물론 그전에 양 플랫폼에 걸친 클래스 네이밍부터 정리해야하는 등 문제가 좀 많지만), 인도, 인도네시아, 러시아, 브라질, 유럽등에서는 아직도 확실한 시장을 점유하고 있습니다.

실제로 핸드셋 자체보다는 핸드셋과 연계된 각종 은행/교통/생활편의 시설서비스가 잘 되어있다보니, 다른 핸드셋이 끼어들기가 어렵웠지만 앞으로도 이 상황이 지속될지는 않을 겁니다. 소프트웨어로 해결 가능한 부분은 다른 플랫폼에서 금새 끼어들 것이고, 노키아가 자체적으로 육성중인 SNS, 지도, 정보, 앱스토어는 통신사/금융사 역시 뛰어드는 곳이니까요.

제 맘같아서는 노키아가 더 분발해서, Apple, 구글, 노키아 3파전이 제대로 불붙었으면 좋겠습니다. 그렇게만 된다면야 소비자는 저렴한 가격에 좋은 스마트폰을 사용할 수 있을 것이고, 이들이 모두 서비스 플랫폼 사업자이거나, 변신하려는 태세이기때문에 통신시장에도 신선한 충격을 줄 수 있을 것 같아서 말이지요. :)

--
새로움을 느끼기에 삶은 즐겁다..
모험가 아돌 크리스틴을 꿈꾸며..
Sia..