2010. 2. 17.

Clojure, Slime 연동시키기

Clojure와 Slime을 연동할 때에는 Swank 서버를 세팅해야한다.
일단 Swank 서버데이터를 가져온다.

git clone git://github.com/jochu/swank-clojure.git

그 다음에 필요한 것은 clojure를 기동시키는 것.. Clojure 기동 파일을 만든다.

#!/bin/bash

CLOJURE=/usr/local/clojure/clojure.jar
CONTRIB=/usr/local/clojure/clojure-contrib.jar
SRC=/home/siabard/project/clojure/src
CLASSES=/home/siabard/project/clojure/classes
SWANK=/home/siabard/src/swank-clojure/src
java -cp $CLOJURE:$CONTRIB:$SRC:$SWANK:$CLASSES clojure.main

이제 Clojure 를 기동시킨후 Swank를 실행시킨다.

(require 'swank.swank)
(swank.swank/start-server "/dev/null" :port 14005 :encoding "utf-8-unix")

포트는 적절히 정리한다.
이제 Emacs 에서 M-x slime-connect 해서 포트를 지정한다. Slime에서 Swank가 호환이 안될 수 있다고 하지만, 무시하고 그냥 진행시키면 된다. 이 메세지가 싫다면

(require 'swank.swank)
(swank.swank/ignore-protocol-version "2009-12-07")
(swank.swank/start-server "/dev/null" :port 14005 :encoding "utf-8-unix")

처럼 해서 프로토콜 버전 체크를 무시하면 된다.









2010. 2. 4.

Android, Maemo, Moblin 비교

한 웹기사에 따르면 Linux 커널 2.6.33 부터 Android 커널 코드가 떨어져나간다고 합니다. 넷상에서는 새로운 fork로 갈 것인가를 두고 이야기가 오고 가고 있습니다. Android 지원용 드라이버를 커널에 단순 삽입하는 것 이상으로, 새로운 lock 타입을 만들어야하는 등 시스템 보안모델에 관여하는 상황이 발생하게된다고 합니다. 이는 Android 가 다른 Linux기반 플랫폼과 다른 몇가지 특이점이 있기 때문입니다.

Android는 기본적으로 Java로 만든 코드를 Dalvik 바이트 코드로 변환해서 돌리고 있습니다. 말하자면 커널과 분리된 별도의 가상 기계가 돌아간다는 점인데, 이를 통해서 하나의 프로그램이 죽는다고 하더라도, 전체 시스템에 문제가 발생하지는 않습니다. 반대로 얘기하자면, 별도의 프로세스 관리를 필요로하는 부분이 커널에 필요할 수 있다는 점입니다.
이러한 코드가 커널 트리에 추가되어야하지만, Android용 드라이버는 Google의 자체적인 커널에 의존하기 때문에, 메인 커널에 추가가 어려웠습니다.

물론, 배포본마다 커널 소스를 일부 수정하는 방식은 기존에도 있어왔지만, Google이 자신만의 코드를 커널에 제때에 업데이트하지 않은점이 문제입니다. 결국, 이제 Android 코드 체계는 이후의 Linux 커널과는 동떨어진 자신만의 길을 걸어야할지도 모릅니다.

Maemo는 Debian 기반이며, 가장 C적인 API리스트를 보유하고 있습니다. QT를 개발한 Trolltech를 인수하면서, 기본 UI를 QT로 변경하는 등, 지속적인 Linux 기반작업을 진행하고 있습니다. Maemo 5를 기반으로하는 N900이 이미 출시된바 있고, 지금은 Maemo 6를 개발하고 있습니다.
Nokia 가 주도적으로 진행하고 있는 이 작업의 한가지 문제점은 기존의 Symbian 체계입니다. Symbian 과 Maemo의 2중 전략으로 이전하겠다고 공언한 만큼, 두 플랫폼의 통합을 이루어야하지만, 서로 체계가 상이해서 많은 어려움을 겪고 있습니다. 아마 이 점이 Maemo의 개발에 어려운 점으로 들 수 있을 것으로 봅니다.
하지만 Linux 개발에 익숙하다면 Maemo에서 상당히 편리한 개발을 할 수 있을 듯 합니다. 일단 C/Python으로 개발이 가능하고 PyGTK를 통해 쉽게 개발할 수 있다는 점이 개발자에게 매력적입니다. 다만, Linux에 완전히 붙어있는 구조라서, Linux에서 볼 수 있는 모든 오동작을 Maemo에서도 볼 수 있다는 점이 걱정됩니다. 불안정한 프로그램이 시스템 자체를 불안정하게 만들수있다는 점은 충분히 위협적입니다.

Moblin은 Intel에서 시작했지만, 지금은 Linux Foundation에서 관리를 맡고있어며, 다양한 벤더의 OS에서 사용이 가능합니다. 표준 Linux Desktop 라이브러리를 변경해서 만들어진 탓에, 기존의 Linux 어플리케이션은 새로운 Moblin에 맞추어서 변경을 해줘야하는 점이 있습니다. C/C++ 기반의 SDK를 공개하고 있으며, 넷북쪽에서는 ATOM프로세서와 더불어 상당한 영향력을 보일 것으로 생각합니다.

Linux에서 가장 가까운 플랫폼이라면 이제는 Maemo플랫폼을 들 수 있습니다. 따라서 임베딩 개발자라면 꽤 흥미로운 플랫폼이 될 것 같습니다. 반면에 Moblin은 넷북 제작 플랫폼으로써 상당히 뛰어나고, 지금은 스마트폰과 일반폰을 넘보고 있습니다. 또 다양한 배포본 사업자들이 뛰어들고 있는 점에서 강점을 가지고 있습니다.

이번 Android 파동이후, Linux 기반 플랫폼의 희비도 엇갈릴 것 같습니다. 개인적으로는 하드코어 Linux배포본인 Maemo가 좀 더 힘을 받았으면 하는 바램입니다만, Nokia 이외에는 별로 관심을 기울이지않는 것 같아서 많이 아쉽습니다. :-)

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차 테스트는 완료..