336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.


HTTP 프로토콜로 Get/Post 방식의 요청에대한 Request.Headers 헤더정보를 얻기위한 ASP.NET 소스코드이다.


int loop1, loop2;
NameValueCollection coll;
 
// Load Header collection into NameValueCollection object.
coll=Request.Headers;

// Put the names of all keys into a string array.
String[] arr1 = coll.AllKeys; 
for (loop1 = 0; loop1<arr1.Length; loop1++) 
{
   Response.Write("Key: " + arr1[loop1] + "<br>");
   // Get all values under this key.
   String[] arr2=coll.GetValues(arr1[loop1]);
   for (loop2 = 0; loop2<arr2.Length; loop2++) 
   {
      Response.Write("Value " + loop2 + ": " + Server.HtmlEncode(arr2[loop2]) + "<br>");
   }
}

 


자세한건 아래 MSDN 링크를 참고하자

MSDN Library 링크

블로그 이미지

By훈트

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

이번에 정리해 볼 이슈는 안드로이드 개발에 반드시 사용하게 되는 에뮬레이터의 관한 이슈이다.
에뮬레이터가 특별한 이슈가 될 것은 없지만 Network programming 을 할 때는 이슈가 발생한다.
특히, 안드로이드 애플리케이션이 서버로 동작할 때는 더욱더 그러하다.

들어가기에 앞서, 에뮬레이터를 클라이언트로 이용해서 사용하는 경우에는 별다른 이슈가 없다고 하였는데 이미 에뮬레이터에는 브라우저도 있고, WebView 클래스를 이용한 Webkit controller도 띄우는 예제가 있다. 다만, 외부로 접속하고자 할 경우(즉, 인터넷을 사용하는 경우에는) user-permission 셋팅을 해 주어야 하는 것을 잊지 말자.

AndroidManifest.xml 파일의 가장 상위 엘리먼트인 <manifes> 바로 다음 자식 엘리먼트로 다음을 넣어주면 된다.

<uses-permission android:name="android.permission.INTERNET"></uses-permission>

이 부분에 대한 간략한 설명은 이전 포스트에도 살펴볼 수 있다.

http://www.wiseant.net/blog/7 : android.webkit.WebView 를 이용한 데모

자! 그러면 여기서 살펴보고자 하는 것은 안드로이드 애플리케이션을 SocketServer를 이용한 서버로 띄웠을 때의 이슈이다.

그렇다면, 이에 해당하는 부분을 안드로이드 문서에서는 어떻게 정의해 두었을까? 다음의 링크를 통해서 확인할 수 있다.

# emulator reference

# emulator reference networking

일종의 제약 사항이라고 할 수 있는데 에뮬레이터의 네트워크 아키텍쳐를 virtual router/firewall 을 사용하여 내부적으로 에뮬레이터내에 IP를 할당하고 있는 것을 볼 수 있다.
이렇게되어 있다보니 로컬 시스템의 IP를 사용할 것으로 생각하면 오산인 것이다. 일단은 위의 문서를 토대로 대략적인 것을 살펴볼 수 있고, Network redirerction이란 것도 볼 수 있다.

안드로이드 에뮬레이터에서 Network redirection은 에뮬레이터가 가지는 VLAN과 로컬 시스템의 포트를 연결(파이프로 연결한다고 생각하면 된다)해 주는 개념이다.
즉, 로컬 시스템의 포트로 들어오는 패킷을 에뮬레이터의 포트로 넘겨주는 식이다(Forward 개념과 비슷하다).

좀 복잡한 듯 하니 그냥 간단한 테스트 소스와 설정하는 방법을 통해서 알아보자.
먼저, 안드로이드 애플리케이션에 올라가는 간단한 서버 소켓을 여는 프로그램을 만들어보자. 참고로 안드로이드 전체 소스는 생략한다. 많은 안드로이드 기초 강좌 등에서 찾아볼 수 있다.

    private String host = "localhost";
    private String port = 8002;
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        setContentView(R.layout.main);

        status = (TextView)findViewById(R.id.status);
        status.setText("do something");

        Button btnRunServer = (Button)findViewById(R.id.runserver);
        btnRunServer.setOnClickListener(new Button.OnClickListener() {
         public void onClick(View v) {
          // run server
          try {
           ServerSocket serverSocket = new ServerSocket(port);
           
           status.setText(status.getText() + " waiting");
           Log.d("Green", "Waiting...");
           
           Socket player1 = serverSocket.accept();

           status.setText(status.getText() + " Connected");
           Log.d("Green", "Connected");

           player1.close();
          }
          catch (Exception ex) {
           status.setText(status.getText() + ex.getMessage());
          }
         }
        });

        Button btnConnect = (Button)findViewById(R.id.connect);
        btnConnect.setOnClickListener(new Button.OnClickListener() {
         public void onClick(View v) {
          // connect to server
          try {
           Socket socket = new Socket(host, port);

           DataInputStream fromServer = new DataInputStream(socket.getInputStream());
           DataOutputStream toServer = new DataOutputStream(socket.getOutputStream());

           status.setText("connected");
           Log.d("Green", "Connected...");
           
           toServer.writeInt(1337);
          }
          catch (Exception ex) {
           status.setText(ex.getMessage());
          }
         }
        });
    }

위의 소스에서는 ServerSocket을 열어놓고(Listening), accept 시에 Connected 메시만을 뿌려주는 간단한 서버용 애플리케이션이다.
connect 버튼을 클릭 시에는 지정된 host로 접속해 보는 소스이다.

안드로이드 에뮬레이터에서 접속 문제를 다루고 있음으로 여기선 ServerSocket 등에 대해서는 자세하게 다루지 않는다.

일단 위의 소스를 기반으로 하여 안드로이드를 구동해 보자.

일단 위와 같이 애플리케이션이 구동되면 첫 번째 버튼을 클릭해서 ServerSocket을 띄워준다. 이제 안드로이드 서버 애플리케이션과 연동할 간단한 클라이언트 소스를 살펴보자.

/**
 * 
 */
package com.mediachorus.test.android.network;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.Socket;

/**
 * @author Sang-Hyup Lee
 * @version 1.0
 *
 */
public class ClientTest {

 /**
  * @param args
  */
 public static void main(String[] args) {
  String host = "localhost";
  int port = 8002;
  // TODO Auto-generated method stub
  try {
   Socket socket = new Socket(host, port);

   DataInputStream fromServer = new DataInputStream(socket.getInputStream());
   DataOutputStream toServer = new DataOutputStream(socket.getOutputStream());

   System.out.println("IP:" + socket.getInetAddress().getHostAddress()
       + ", port:" + socket.getPort());
   System.out.println("Connected...");
      
   toServer.writeInt(1337);
   // toServer.write("quit".getBytes());
   toServer.flush();
   
   System.out.println("Done...");
  }
  catch (Exception ex) {
   System.err.println(ex.getMessage());
  }
 }
}

클라이언트 소스 역시 설명할 것도 별로 없다. 자바 네트워크 프로그램으로 서버에 접속해서 패킷을 보내어 본다. 로그를 통해서 접속이 되었는지 확인만 해 보는 것이다.

클라이언트 소스를 실행해 보자. 다음과 같은 Exception을 발생시킨다.

Connection refused: connect

커넥션이 이루어지지 않았다는 말인데, 우리의 의도는 안드로이드 애플리케이션 TCP 서버 소켓에 접속하는 것이다. 동일한 PC 에서 이루어지는데 localhost 라고 하면 되는 줄 알았는데 아니다.

여기서 위의 구글 문서 링크에서 나온 것처럼 Network redirection이 필요하다.
다음과 같이 command 상에서 telnet 접속부터 시작해서 실행해보자. 참고로 에뮬레이터는 5554 포트를 기본으로 할당하여 telnet으로 접속이 가능하다.

telnet localhost 5554
redir add tcp:8002:8002 

사용자 삽입 이미지

위와 같이 로컬 시스템이 8002 포트를 사용하지 않으면 OK 메시지로 응답한다. 참고로 redir 사용법을 보려면 "redir help"라고 입력하면 된다.
위의 명령어 뜻은 다음과 같다.

TCP 형태로 redirection을 하는데, 로컬 시스템의 8002 포트를 VLAN(에뮬레이터에 올라간 안드로이드 애플리케이션) 8002 포트로 연결(redirection) 해 준다.

add <protocol>:<host-port>:<guest-port>

그렇다면 redirection을 해지하는 방법은 add를 del로 바꾸어주면 된다. 다음과 같은 형태이다.

redir del tcp:8002


del 옵션을 사용시에는 system host port만 입력해 주면 된다.

자, 이 상태에서(redirection을 걸어둔 상태에서) 클라이언트 프로그램을 실행해 보자. Connected 메시지를 볼 수 있을 것이다.

이렇게해서 기본적인 Network redirection을 해결할 수 있는데, 클라이언트 소스 코드를 '127.0.0.1'로 host 값을 바뀌어서 해 보자.
(결론은? 잘 된다. 어쩌면 당연한 것을?^^)

하지만, 로컬 시스템이 가지고 있는 IP를 host 값으로 수정하고 실행해 보자. 다시금 "Connection refused: connect" 에러가 발생한다.
그렇다면, 안드로이드 애플리케이션이 실행하고 있는 시스템이 아닌 다른 시스템에서의 접속은? 당연히 안 된다.

문제는 여기에 또 있는 것이다. Server/Client 프로그램은 당연히 서로 다른 시스템의 연결을 지양하고 있는 것인데 이런 문제가 발생한다.
이는 안드로이드 에뮬레이터가 동작시에 localhost에 해당하는 127.0.0.1을 개발 머신의 loopback interface로 alias를 해주기 때문이다.
결국은 로컬 시스템의 IP를 할당하여 수행하지 못한다는 것이다.

이를 해결하기 위해서 다음의 링크를 통해서 stcppipe.exe를 실행해 주어야 한다. 링크에는 sudppipe.exe도 포함되어 있다. 소스와 함께.

Simple UDP proxy/pipe 0.3a  : http://aluigi.org/mytoolz/sudppipe.zip
Simple TCP proxy/pipe 0.4.4a : http://aluigi.org/mytoolz/stcppipe.zip

사용법은 다음과 같다.

stcppipe.exe -b <local IP> <remote hosts> <local port> <remote port>
remote hosts : 127.0.0.1
remote port : emulator 의 host port

이를 응용해서 실행해 보면 다음과 같이 입력할 수 있다.

stcppipe -b 192.168.0.86 l27.0.0.1 8002 8002

이렇게 하고 다시금 로컬에서든 다른 시스템에서든지 Client 프로그램을 실행해보자.
stcppipe 프로그램을 통해서 IN/OUT 되는 포트도 확인해 볼 수 있다. UDP도 마찬가지이다. 각자 테스트해 보는 것도 좋을 거 같다.


이렇게해서 안드로이드 에뮬레이터의 Network redirection 이슈를 마친다. 끝으로 이 문제는 혼자서 고민하고 해결한 것이 아니라 kandroid.org 의 운영자이신 들풀님의 큰 도움이 있었다. 이에 감사의 뜻을 전하며 해당 부분을 Q&A를 통해서 해결하였는데, 링크를 걸어둔다.


# 처음 Emulator IP 관련된 질문후, redir (option)에 대해서 알게된 링크

# 로컬 시스템 내에서의 접속이 아닌 원격 시스템에서의 접속 처리를 해결하게 된 링크

두 번째 링크에도 포함된 링크인데 구글 그룹스에서도 똑같은 고민을 했었고 동일하게 해결한 것을 찾을 수 있다.



다시금 함께 고민해 주신 들풀님에게 감사의 뜻을 전한다.


[마치며]
지금까지의 Network redirection을 통해서 다양한 것을 개발해 보았다. 하지만 실제 개발에서 사용하는 것이 TCP, UDP를 함께 사용하면서 까다로운 네트워크 프로그램을 처리하는 것이었다. 특이한 점은 윈도우에서는 정상적으로 처리되지 않을 때가 많았다.
그래서 Ubuntu 에서 실행해 보니 접속에 대한 문제는 거의 발생하지 않았다. 이러한 원인에 대한 분석은 현재로서는 어려울 것 같아서 다루지 않겠다.
그냥 경험담이라고 생각해 주면 좋겠다.

모두들... 더 좋은 정보나 자료가 있으면 공유해 주세요~^^
블로그 이미지

By훈트

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.
안드로이드에서 HTTP를 통한 데이타를 가져오는 방식은,
URL클래스 또는 HttpClient를 사용하는 방법이 있다.

1. URL 클래스 사용

 GET 방식으로 간편히 사용할수 있지만, POST방식은 파라미터를 전달하는 방식이 다르므로
이 대로는 파라미터를 전달할수 없다.

2. HTTPClient 사용
잘못된 예1

HttpPost에 URL과 전달할 파라미터들을 헤더에 추가하여 결과를 얻는다.
HttPost 에 정보 담기
-> HttpClient에서 요청을 실행 HttpResponse로 결과 리턴
-> HttpResponse의 getEntity로 inputstream을 얻어서 결과 얻기

결과를 얻어왔지만,
ㄱ. 파라미터가 제대로 넘어가지 않는 문제와
ㄴ. 한글을 인코딩에서 문제 발생

잘못된 예2

HttpParams 를 사용하여 파라미터를 전달하려함 (후에 안것이지만 HttpParams는 이런 용도가 아니라고 함)

그래서 사용한 방법은
NameValuePair 목록을 가지는 List를 활용하는 방법로 파라미터명 - 값을 List<NameValuePair>로 모아
HttpPost요청에 setEntity(new UrlEncodedFormEntity()) 한다.

결과를 얻어왔지만,
ㄱ. 파라미터가 제대로 넘어가지 않는 문제와
ㄴ. 한글을 인코딩에서 문제 발생

3. 성공한 예

HttpURLConnection을 사용.
PrintWriter를 통해 데이타를 보내고, HttpURLConnection을 통해 InputStream을 얻어 결과를 만든다. -> 파라미터 전송 성공

한글문제는 보내거나 받을때, 모두 최초의 스트림을 만들때 인코딩 문자열을 넘길수 있는 생성자를 통해 한글 인코딩을 전달하여 생성하니 한글도 정상적으로 동작.
블로그 이미지

By훈트

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.
파일 전송을 위해 사용되는 multipart/form-data MIME을 사용하여 HTTP 기반의 파일 전송 방법에 대해서 살펴보자.

multipart/form-data를 지원하는 HttpRequestor의 구현

예전에 자바로 구현하는 Web-to-web 프로그래밍, Part 1에서 필자는 HTTP 프로토콜의 GET/POST 방식을 사용하여 웹 기반의 데이터 송수신 방법에 대해서 살펴보았다. 이때 작성했던 클래스인 HttpMessage는 application/x-www-form-urlencoded 인코딩을 사용하는 GET/POST 방식만을 지원했었다. 따라서 multipart/form-data 인코딩을 사용해야 하는 파일 전송의 경우는 HttpMessage로는 해결할 수 없었다.

이를 날카롭게 지적해주신 어떤 자바캔 회원님께서 필자에게 multipart/form-data 인코딩도 지원해주는 HttpMessage 클래스를 작성해줄 것을 요청했으며, 그에 따라 이번 기사에서는 multipart/form-data 인코딩까지 지원해주는 HttpRequestor 클래스에 대해서 살펴보도록 하겠다.

HttpRequestor의 구현

HTTP를 기반으로 하여 데이터를 송수신하기 위해서는 먼저 데이터를 전송할 대상 URL을 지정해주어야 한다. 이는 HttpRequestor의 생성자를 통해서 이루어지며, HttpRequestor의 생성자는 다음과 같이 정의되어 있다.

    public final class HttpRequestor {
        
        public static final String CRLF = "\r\n";
        
        /**
         * 연결할 URL
         */
        private URL targetURL;
        
        /**
         * 파라미터 목록을 저장하고 있다.
         * 파라미터 이름과 값이 차례대로 저장된다.
         */
        private ArrayList list;
        
        public HttpRequestor(URL target) {
            this(target, 20);
        }
        
        /**
         * HttpRequest를 생성한다.
         * 
         * @param target HTTP 메시지를 전송할 대상 URL
         */
        public HttpRequestor(URL target, int initialCapicity) {
            this.targetURL = target;
            this.list = new ArrayList(initialCapicity);
        }
        
        ...
    }

"\r\n"을 값으로 갖는 상수 CRLF를 정의하였다. 이는 줄 구분을 할 때 사용된다. (HTTP 프로토콜은 줄 구분을 "\r\n"으로 하도록 되어 있다.) 필드에는 targetURL과 list가 존재한다. targetURL은 연결할 URL을 나타낸다. 물론, HTTP 프로토콜을 위한 URL이 될 것이다. list는 파라미터 목록을 저장하기 위해서 사용된다. HttpRequestor는 HashMap이나 Properties와 같은 것을 사용하여 파라미터 이름과 값을 별도로 저장하지 않고 ArrayList에 모두 저장한다. 이때 list에는 파라미터이름1, 값1, 파라미터이름2, 값2, ...와 같이 파라미터 이름과 파라미터 값이 차례대로 저장된다.

전달될 파라미터를 지정은 두 메소드를 통해서 이루어진다. 첫번째 메소드는 텍스트 파라미터를 지정해주는 addParameter(String parameterName, String parameterValue) 메소드이고 다른 하나는 파일 파라미터를 지정해주는 public void addFile(String parameterName, File parameterValue) 메소드이다. 이 두 메소드는 다음과 같다.

    /**
     * 파라미터를 추가한다.
     * @param parameterName 파라미터 이름
     * @param parameterValue 파라미터 값
     * @exception IllegalArgumentException parameterValue가 null일 경우
     */
    public void addParameter(String parameterName, String parameterValue) {
        if (parameterValue == null) 
        throw new IllegalArgumentException("parameterValue can't be null!");
        
        list.add(parameterName);
        list.add(parameterValue);
    }
    
    /**
     * 파일 파라미터를 추가한다.
     * 만약 parameterValue가 null이면(즉, 전송할 파일을 지정하지 않는다면
     * 서버에 전송되는 filename 은 "" 이 된다.
     * 
     * @param parameterName 파라미터 이름
     * @param parameterValue 전송할 파일
     * @exception IllegalArgumentException parameterValue가 null일 경우
     */
    public void addFile(String parameterName, File parameterValue) {
        // paramterValue가 null일 경우 NullFile을 삽입한다.
        if (parameterValue == null) {
            list.add(parameterName);
            list.add(new NullFile());
        } else {
            list.add(parameterName);
            list.add(parameterValue);
        }
    }

위 코드에서 addFile() 메소드를 살펴보자. parameterValue가 null일 경우 값 부분에 NullFile을 전달해주는 것을 알 수 있다. 이는 전송할 파일을 지정하지 않은 것과 같은 효과를 주기 위한 것이다. 예를 들어, 게시판과 같은 곳에서 업로드할 파일을 지정하지 않은 것과 같이 전송할 파일을 지정하지 않을 때에는 parameterValue를 null로 지정해주면 된다. 참고적으로 NullFile 클래스는 HttpRequestor의 이너 클래스로 정의하였으며 다음과 같다.

    private class NullFile {
        NullFile() {
        }
        public String toString() {
            return "";
        }
    }

application/x-www-form-urlencoded 인코딩을 사용하는 GET/POST 방식은 파라미터 값을 인코딩해서 보내야 한다. 이는 자바로 구현하는 Web-to-web 프로그래밍, Part 1에서 사용한 방식을 그대로 사용하였다. HttpRequestor 클래스는 인코딩을 손쉽게 할 수 있는 메소드인 encodeString 메소드를 정의하고 있으며 encodeString() 메소드는 다음과 같다.

    private static String encodeString(ArrayList parameters) {
        StringBuffer sb = new StringBuffer(256);
        
        Object[] obj = new Object[parameters.size()];
        parameters.toArray(obj);
        
        for (int i = 0 ; i < obj.length ; i += 2) {
            if ( obj[i+1] instanceof File || obj[i+1] instanceof NullFile ) continue;            
            sb.append(URLEncoder.encode((String)obj[i]) );
            sb.append('=');
            sb.append(URLEncoder.encode((String)obj[i+1]) );            
            if (i + 2 < obj.length) sb.append('&');
        }
        
        return sb.toString();
    }

encodeString() 메소드를 살펴보면 파라미터의 값이 File이나 NullFile인 경우에는 처리하지 않는 것을 알 수 있다. 이는 application/x-www-form-urlencoded 인코딩에서의 파일 전송은 의미가 없기 때문이다.

전송을 처리해주는 메소드의 구현

이제 실제로 파라미터의 값을 지정한 URL로 전송해주는 메소드를 살펴보도록 하자. 파라미터를 전송해주는 메소드는 sendGet(), sendPost() 그리고 sendMultipartPost() 이렇게 3가지가 존재한다. sendGet()과 sendPost() 메소드는 자바로 구현하는 Web-to-web 프로그래밍, Part 1의 sendGet(), sendPost() 메소드와 거의 동일하므로 여기서는 간단하게 소스 코드만 보여주도록 하겠다. 이 두 메소드의 코드는 다음과 같다.

    /**
     * GET 방식으로 대상 URL에 파라미터를 전송한 후
     * 응답을 InputStream으로 리턴한다.
     * @return InputStream
     */
    public InputStream sendGet() throws IOException {
        String paramString = null;
        if (list.size() > 0)
            paramString = "?" + encodeString(list);
        else
            paramString = "";
        
        URL url = new URL(targetURL.toExternalForm() + paramString);
        
        URLConnection conn = url.openConnection();
        
        return conn.getInputStream();
    }
    
    /**
     * POST 방식으로 대상 URL에 파라미터를 전송한 후
     * 응답을 InputStream으로 리턴한다.
     * @return InputStream
     */
    public InputStream sendPost() throws IOException {
        String paramString = null;
        if (list.size() > 0)
            paramString = encodeString(list);
        else
            paramString = "";
        
        HttpURLConnection conn = (HttpURLConnection)targetURL.openConnection();
        conn.setRequestMethod("POST");
        conn.setRequestProperty("Content-Type",
                                "application/x-www-form-urlencoded");
        conn.setDoInput(true);
        conn.setDoOutput(true);
        conn.setUseCaches(false);
        
        DataOutputStream out = null;
        try {
            out = new DataOutputStream(conn.getOutputStream());
            out.writeBytes(paramString);
            out.flush();
        } finally {
            if (out != null) out.close();
        }
        return conn.getInputStream();
    }

크게 어려운 부분이 없으므로 설명은 생략하기로 하겠다.

이제 이 글의 핵심 부분인 sendMultipartPost() 메소드에서 대해서 살펴보자. 이 메소드를 살펴보기 위해서는 먼저 multipart/form-data 인코딩 방식의 데이터가 웹 서버에 어떤 형태로 전달되는 지 알아야만 한다. 이를 위해 80 포트로 들어오는 데이터를 그대로 출력해주는 TestServer.java를 작성해보았다. TestServer.java는 다음과 같다.

    import java.io.*;
    import java.net.*;
    
    public class TestServer {
        public static void main(String[] args) throws IOException {
            ServerSocket ss = new ServerSocket(80);
            
            Socket socket = ss.accept();
            InputStream is = socket.getInputStream();
            BufferedReader br = new BufferedReader(
                                    new InputStreamReader(is));
            String line = null;
            while ( (line = br.readLine()) != null) {
                System.out.println(line);
            }
            br.close();
            ss.close();
        }
    }

TestServer를 수행한 다음에 다음과 같은 HTML 폼을 사용하여 텍스트 파일을 전송해보았다.

    <html><body>
    <form action="http://localhost/test.jsp" method="post"
          enctype="multipart/form-data">
    이름: <input type="text" name="name"> <br>
    <input type="file" name="upload">
    <br>
    <input type="submit">
    </form>
    </body></html>

HTML 페이지에서 Submit 버튼을 누르면 TestServer는 다음과 비슷한 결과를 출력하게 된다.

    POST /test.jsp HTTP/1.1
    Accept: image/gif, image/jpeg, image/pjpeg, */*
    Accept-Language: ko
    Content-Type: multipart/form-data; boundary=---------------------------7d1539170136    Accept-Encoding: gzip, deflate
    User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)
    Host: localhost
    Content-Length: 558
    Connection: Keep-Alive
    Cache-Control: no-cache
    Cookie: JSESSIONID=aaa7qyaGZraPlm; id=admin; name=adminname
    
    -----------------------------7d1539170136    Content-Disposition: form-data; name="name"
    
    이름에 넣은 값
    -----------------------------7d1539170136    Content-Disposition: form-data; name="upload"; filename="C:\work\framework\test\    formtest.html"
    Content-Type: text/html
    
    <html><body>
    <form action="http://localhost/test.jsp" method="post"
          enctype="multipart/form-data">
    이름: <input type="text" name="name"> <br>
    <input type="file" name="upload">
    <br>
    <input type="submit">
    </form>
    </body></html>
    -----------------------------7d1539170136--

위에서 "Cookie:" 까지가 헤더에 해당하는 부분인데, 이 헤더 정보의 Content-Type을 살펴보면 컨텐츠 타입을 multipart/form-data로 지정한 것을 알 수 있으며, 또한 boundary를 지정한 것을 알 수 있다. 이 boundary는 각각의 파라미터를 구분할 때 사용되는 문자열로서 각 파라미터 사이의 경계라고 생각하면 된다.

실제로 경계에서 사용되는 boundary는 앞에 '--'가 추가된다. 그리고 가장 마지막 boundary에는 '--'가 뒤에 추가된다. 실제로 위 전송 데이터에서 각 boundary를 추출해서 비교하면 다음과 같다.

  • ---------------------------7d1539170136 : content-Type에서의 boundary의 값:
  • -----------------------------7d1539170136 : 각 파라미터 사이의 boundary
  • -----------------------------7d1539170136-- : 가장 마지막 boundary
위 전송 결과를 통해서 알 수 있는 또 하나의 사실은 multipart/form-data는 파라미터 값을 인코딩하지 않고 그대로 전송한다는 점이다. 따라서 application/x-www-form-urlencoded 인코딩을 위해서 사용한 encodeString() 메소드를 사용할 필요가 없다.

지금까지의 내용을 통해서 multipart/form-data MIME 타입을 사용할 경우 어떻게 데이터를 전송해야 하는지에 대해서 어느 정도 감을 잡았을 것이다. Socket을 사용하여 직접 위와 같은 형태로 데이터를 전송해도 되지만 Java에는 java.net.URLConnection이라는 클래스가 있으며, 필자도 URLConnection을 사용하여 구현해보았다.

sendMultipartPost() 메소드는 앞에서 살펴본 sendGet() 메소드와 sendPost() 메소드에 비해 코드가 길다. 하지만, 메소드를 부분부분 보여주는 것보다는 한번에 다 보여주는 것을 자바캔 회원들이 좋아할 것 같아서 먼저 sendMultipartPost() 메소드의 완전한 코드부터 시작하기로 하자.

    public InputStream sendMultipartPost() throws IOException {
        HttpURLConnection conn = (HttpURLConnection)targetURL.openConnection();
        
        // Delimeter 생성
        String delimeter = makeDelimeter();        
        byte[] newLineBytes = CRLF.getBytes();
        byte[] delimeterBytes = delimeter.getBytes();
        byte[] dispositionBytes = "Content-Disposition: form-data; name=".getBytes();
        byte[] quotationBytes = "\"".getBytes();
        byte[] contentTypeBytes = "Content-Type: application/octet-stream".getBytes();
        byte[] fileNameBytes = "; filename=".getBytes();
        byte[] twoDashBytes = "--".getBytes();
        
        conn.setRequestMethod("POST");
        conn.setRequestProperty("Content-Type",
                                "multipart/form-data; boundary="+delimeter);
        conn.setDoInput(true);
        conn.setDoOutput(true);
        conn.setUseCaches(false);
        
        BufferedOutputStream out = null;
        try {
            out = new BufferedOutputStream(conn.getOutputStream());
            
            Object[] obj = new Object[list.size()];
            list.toArray(obj);
            
            for (int i = 0 ; i < obj.length ; i += 2) {
                // Delimeter 전송
                out.write(twoDashBytes);
                out.write(delimeterBytes);
                out.write(newLineBytes);
                // 파라미터 이름 출력
                out.write(dispositionBytes);
                out.write(quotationBytes);
                out.write( ((String)obj[i]).getBytes() );
                out.write(quotationBytes);
                if ( obj[i+1] instanceof String) {
                    // String 이라면
                    out.write(newLineBytes);
                    out.write(newLineBytes);
                    // 값 출력
                    out.write( ((String)obj[i+1]).getBytes() );
                    out.write(newLineBytes);
                } else {
                    // 파라미터의 값이 File 이나 NullFile인 경우
                    if ( obj[i+1] instanceof File) {
                        File file = (File)obj[i+1];
                        // File이 존재하는 지 검사한다.
                        out.write(fileNameBytes);
                        out.write(quotationBytes);
                        out.write(file.getAbsolutePath().getBytes() );
                        out.write(quotationBytes);
                    } else {
                        // NullFile 인 경우
                        out.write(fileNameBytes);
                        out.write(quotationBytes);
                        out.write(quotationBytes);
                    }
                    out.write(newLineBytes);
                    out.write(contentTypeBytes);
                    out.write(newLineBytes);
                    out.write(newLineBytes);
                    // File 데이터를 전송한다.
                    if (obj[i+1] instanceof File) {
                        File file = (File)obj[i+1];
                        // file에 있는 내용을 전송한다.
                        BufferedInputStream is = null;
                        try {
                            is = new BufferedInputStream(
                                     new FileInputStream(file));
                            byte[] fileBuffer = new byte[1024 * 8]; // 8k
                            int len = -1;
                            while ( (len = is.read(fileBuffer)) != -1) {
                                out.write(fileBuffer, 0, len);
                            }
                        } finally {
                            if (is != null) try { is.close(); } catch(IOException ex) {}
                        }
                    }
                    out.write(newLineBytes);
                } // 파일 데이터의 전송 블럭 끝
                if ( i + 2 == obj.length ) {
                    // 마지막 Delimeter 전송
                    out.write(twoDashBytes);
                    out.write(delimeterBytes);
                    out.write(twoDashBytes);
                    out.write(newLineBytes);
                }
            } // for 루프의 끝
            
            out.flush();
        } finally {
            if (out != null) out.close();
        }
        return conn.getInputStream();
    }

먼저 sendMultipartPost() 메소드는 포스팅에서 사용되는 "Content-Disposition: form-data; name="과 같은 다양한 문자열의 byte[] 배열을 구한다. 이처럼 문자열을 byte로 변환하는 이유는 multipart/form-data가 텍스트 데이터만 사용되는 것이 아니라 바이트 기반의 데이터(예를 들어, 전송할 파일)도 전송하기 때문이다. 물론, sendMultipartPost() 메소드의 앞부분에서 정의한 byte[] 배열들은 필드로 정의하여도 무방하다. (실제로 필드로 정의하는 것이 더 좋을 것이다.)

데이터 전송에서 사용되는 문자열의 byte 배열을 구한 이후에는 HttpURLConnection에 필요한 헤더 정보를 지정한다. 특히 중요한 부분은 Content-Type 헤더 값을 "multipart/form-data"로 지정하는 것이다. 물론, 이때 boundary도 함께 지정해준다. 헤더 정보에 대한 기본 설정이 완료되면 HttpURLConnection으로부터 출력 스트림을 구한다.

출력 스트림을 구한 이후에는 파라미터값을 저장하고 있는 출력 스트림에 차례대로 출력하기 시작한다. 이때 String 파라미터냐 File 파라미터냐에 따라서 알맞게 데이터를 출력해준다. out.write() 메소드를 사용하여 출력하는 데이터의 순서를 잘 보면 앞에서 multipart/form-data로 데이터를 전송할 때의 규칙과 같다는 점을 알 수 있다. 파라미터 타입이 File일 경우에는 추가적으로 파일의 내용을 전송해준다.

File을 전송할 때 "Content-Type: application/octet-stream" 문장을 추가해주는 것을 알 수 있는데 여기서 application/octet-stream은 전송할 파일이 바이너리 파일이라는 것을 나타낸다. 여기서 바이너리 파일로 단정지은 것은 텍스트 파일의 경우 File이 아닌 텍스트로도 충분히 전송할 수 있기 때문이며, 또한 파일 전송은 대부분 바이너리 파일이 대상이기 때문이다.

boundary를 구할 때 makeDelimeter() 메소드를 사용하는데, 원칙적으로는 makeDelimeter() 메소드를 실행할 때 마다 다른 boundary 값을 리턴해야 하겠지만 필자는 매번 같은 boundary를 리턴하였다. 왜냐면 매번 같은 boundary를 리턴한다 해서 문제될 것이 없기 때문이다. 필자는 makeDelimeter() 메소드를 다음과 같이 구현하였다.

    private static String makeDelimeter() {
        return "---------------------------7d115d2a20060c";
    }

HttpRequestor 클래스의 사용

HttpRequestor 클래스의 사용방법은 매우 간단하다. 간단히 예를 들면 다음과 같다.

    HttpRequestor requestor = new HttpRequestor(someURL);
    requestor.addParameter("param1", "파라미터1");
    requestor.addParameter("param2", "parameter2");
    requestor.addFile("file1", null); // 전송할 파일을 지정하지 않는 경우
    requestor.addFile("file2", new File("c:\\autoexec.bat"));
    
    InputStream is = requestor.sendMultipartPost();
    BufferedReader br = new BufferedReader(new InputStreamReader(is));
    ...
    ...
    br.close();

결론

이번 글에서는 업로드한 한 파일을 처리해주는 유틸리티 클래스와 정반대의 기능을 수행해주는 HttpRequestor 클래스에 대해서 살펴보았다. 비록 여러분이 이 클래스를 직접적으로 사용하게 될 경우는 많지 않겠지만 웹 기반의 통신을 해야 하는 경우, 특히 이미지 파일이나 문서 파일등을 서로 주고 받아야 하는 경우 multipart/form-data의 지원은 필수적으로 할 수 있겠다. (필자도 multipart/form-data를 지원하지 않는 HttpMessage 때문에 손해아닌 손해를 본적이 있다.) 또한 굳이 multipart/form-data 방식의 데이터 교환을 사용할 필요가 없다고 해도 어떤식으로 multipart/form-data가 전송되는 지 아는 것도 나름대로 의미가 있을 것이라 생각한다.

관련링크:
블로그 이미지

By훈트

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

MemoryStream

Saving-Rebuilding InkCanvas Strokes

Load/Unload images into/from DB table

C# Image to Byte Array and Byte Array to Image Converter Class

 

Introduction

Recently I was looking for a class which could convert a System.Drawing.Image to byte[] array and vice versa. After a lot of searching on Google, I realised that it would be faster for me to write this class and also share it with the community.

The class which I wrote is called ImageConverter.cs. The class has two methods.

First method: Convert Image to byte[] array:

 Collapse Copy Code
public byte[] imageToByteArray(System.Drawing.Image imageIn)
{
 MemoryStream ms = new MemoryStream();
 imageIn.Save(ms,System.Drawing.Imaging.ImageFormat.Gif);
 return  ms.ToArray();
}

This method uses the System.Drawing.Image.Save method to save the image to a memorystream. The memorystream can then be used to return a byte array using the ToArray() method in the MemoryStream class.

Second method: Convert byte[] array to Image:

 Collapse Copy Code
public Image byteArrayToImage(byte[] byteArrayIn)
{
     MemoryStream ms = new MemoryStream(byteArrayIn);
     Image returnImage = Image.FromStream(ms);
     return returnImage;
}

This method uses the Image.FromStream method in the Image class to create a method from a memorystream which has been created using a byte array. The image thus created is returned in this method.

The way I happen to use this method was to transport an image to a web service, by converting it to a byte array and vice-versa.

Hope this class is useful to the community as well. The code of ImageConverter.cs can be downloaded from the link at the top of this article.

Rajan Tawate

Founder
Seegloo Web Meeting

 

 

 

 

 

 

 

--------------

 

Store Image into DB table

 Collapse Copy Code
...
byte[] content = ReadBitmap2ByteArray(fileName);
StoreBlob2DataBase(content);
...
protected static byte[] ReadBitmap2ByteArray(string fileName)
{
  using(Bitmap image = new Bitmap(fileName))
  {
    MemoryStream stream = new MemoryStream();
    image.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp);
    return stream.ToArray();
  }
}

protected static void StoreBlob2DataBase(byte[] content)
{
   SqlConnection con = Connection;
   con.Open();
   try
   {
     // insert new entry into table
     SqlCommand insert = new SqlCommand(
     "insert into Images ([stream]) values (@image)",con);
     SqlParameter imageParameter = 
     insert.Parameters.Add("@image", SqlDbType.Binary);
     imageParameter.Value = content;
     imageParameter.Size  = content.Length;
     insert.ExecuteNonQuery();
   }
   finally
   {
      con.Close();
   }
}

Store Images for OLEDB provider

Some of us use OLEDB provider to communicate with SQL Server. In this case you should use the code below to store images into your DB. Pay attention to using '?' instead of '@image' in the SQL query.

 Collapse Copy Code
protected static void StoreBlob2DataBaseOleDb(byte[] content)
{
   try
   {
      using(OleDbConnection con = Connection)
      {
         con.Open();

         // insert new entry into table
         using(OleDbCommand insert = new OleDbCommand(
             "insert into Images ([stream]) values (?)",con))
         {
            OleDbParameter imageParameter = 
            insert.Parameters.Add("@image", OleDbType.Binary);
            imageParameter.Value = content;
            imageParameter.Size  = content.Length;
            insert.ExecuteNonQuery();
         }
      }
   }
   catch(Exception ex)
   {
      // some exception processing
   }
}

Get Image from DB table and show it

 Collapse Copy Code
 // get image
 DataRowView drv = (DataRowView) _cm.Current;
 byte[] content = (byte[])drv["stream"];
 MemoryStream stream = new MemoryStream(content);
 Bitmap image = new Bitmap(stream);
 
 ShowImageForm f = new ShowImageForm();
 f._viewer.Image = image;
 f.ShowDialog(this);

Conclusion

You can use this technique to work with any type of binary data without using storage procedures. Good Luck.

<FORM style="PADDING-BOTTOM: 0px; MARGIN: 0px; PADDING-LEFT: 0px; PADDING-RIGHT: 0px; PADDING-TOP: 0px" id=aspnetForm method=post name=aspnetForm action=DisplayArticle.aspx>

<INPUT id="__VIEWSTATE" value="/wEPDwUKMTAyMTMzODg1Ng9kFgJmD2QWBAIJD2QWBAIFDw8WAh4HVmlzaWJsZWdkZAINDw8WAh8AZ2RkAgoPZBYKAgcPZBYOAgEPZBYCZg8WAh4LXyFJdGVtQ291bnRmZAIDD2QWCGYPDxYCHgtOYXZpZ2F0ZVVybAUpL0tCL2RhdGFiYXNlL2ltYWdlczJkYi5hc3B4P2Rpc3BsYXk9UHJpbnRkZAICDw8WAh8AaGRkAgMPZBYGAgIPDxYCHghJbWFnZVVybAUhL3NjcmlwdC9Cb29rbWFya3MvSW1hZ2VzL2xpbmsucG5nZGQCBA8PFgIfAgU/L3NjcmlwdC9Cb29rbWFya3MvQWRkLmFzcHg/b2JpZD05MzIzJm9idGlkPTImYWN0aW9uPUFkZEJvb2ttYXJrFgYeBG5hbWUFCWJtXzkzMjNfMh4FY2xhc3NlHgdvbmNsaWNrBRtyZXR1cm4gYm9va21hcmtNZSg5MzIzLCAyKTtkAgYPFgIfBAUJYm1fOTMyM18yZAIEDw8WAh8CBSUvc2NyaXB0L0FydGljbGVzL1JlcG9ydC5hc3B4P2FpZD05MzIzZGQCBQ8WAh8EBQtDdXJSYXRfOTMyMxYCZg9kFgJmD2QWAmYPZBYEZg9kFgICAQ8PFgIeBFRleHQFGjIxIHZvdGVzIGZvciB0aGlzIGFydGljbGUuZGQCAw9kFgJmD2QWBAIBDw8WBB8HBRBQb3B1bGFyaXR5OiA0LjgxHwIFKS9zY3JpcHQvQXJ0aWNsZXMvVG9wQXJ0aWNsZXMuYXNweD90YV9zbz0xZGQCBQ8WAh8HBRxSYXRpbmc6IDxiPjMuNjQ8L2I+IG91dCBvZiA1ZAIVDxYCHwBnFgQCAQ8PFgIfAgVGL3NjcmlwdC9NZW1iZXJzaGlwL1N1YnNjcmliZS5hc3B4P3JwPSUyZktCJTJmZGF0YWJhc2UlMmZpbWFnZXMyZGIuYXNweGRkAgMPDxYCHwIFTy9zY3JpcHQvTWVtYmVyc2hpcC9TZW5kQ29uZmlybVJlcXVlc3QuYXNweD9ycD0lMmZLQiUyZmRhdGFiYXNlJTJmaW1hZ2VzMmRiLmFzcHhkZAIZD2QWCgIBD2QWBAIBDxYCHglpbm5lcmh0bWwFsQI8cD5UaGlzIGFydGljbGUgaGFzIG5vIGV4cGxpY2l0IGxpY2Vuc2UgYXR0YWNoZWQgdG8gaXQgYnV0IG1heSBjb250YWluIHVzYWdlIHRlcm1zIGluIHRoZSBhcnRpY2xlIHRleHQgb3IgdGhlIGRvd25sb2FkIGZpbGVzIHRoZW1zZWx2ZXMuIElmIGluIGRvdWJ0IHBsZWFzZSBjb250YWN0IHRoZSBhdXRob3IgdmlhIHRoZSBkaXNjdXNzaW9uIGJvYXJkIGJlbG93LjwvcD48cD5BIGxpc3Qgb2YgbGljZW5zZXMgYXV0aG9ycyBtaWdodCB1c2UgY2FuIGJlIGZvdW5kIDxhIGhyZWY9Ii9pbmZvL0xpY2Vuc2VzLmFzcHgiPmhlcmU8L2E+PC9wPmQCAg9kFgICAQ8QZGQWAGQCBQ8WAh8BAgFkAgcPFgIfBwW1CTxoMj5PdGhlciBwb3B1bGFyIERhdGFiYXNlIGFydGljbGVzOjwvaDI+PHVsPjxsaT48YSBocmVmPSIvS0IvZGF0YWJhc2UvY2FhYWRvY2xhc3MxLmFzcHgiPkEgc2V0IG9mIEFETyBjbGFzc2VzIC0gdmVyc2lvbiAyLjIwPC9hPjxkaXYgY2xhc3M9InNtYWxsLXRleHQiPlNpbXBsZSBkYXRhYmFzZSBhY2Nlc3MgdXNpbmcgYW4gQURPIGNsYXNzLjwvZGl2PjwvbGk+PGxpPjxhIGhyZWY9Ii9LQi9kYXRhYmFzZS9maWxlaGVscGVycy5hc3B4Ij5GaWxlSGVscGVycyB2Mi4wIC0gRGVsaW1pdGVkIChDU1YpIG9yIEZpeGVkIERhdGEgSW1wb3J0L0V4cG9ydCBGcmFtZXdvcms8L2E+PGRpdiBjbGFzcz0ic21hbGwtdGV4dCI+QW4gZWFzeSB0byB1c2UgLk5FVCBsaWJyYXJ5IHRvIHJlYWQvd3JpdGUgc3Ryb25nIHR5cGVkIGRhdGEgZnJvbSBmaWxlcyB3aXRoIGZpeGVkIGxlbmd0aCBvciBkZWxpbWl0ZWQgcmVjb3JkcyAoQ1NWKS4gQWxzbyBoYXMgc3VwcG9ydCB0byBpbXBvcnQvZXhwb3J0IGRhdGEgZnJvbSBkaWZmZXJlbnQgZGF0YSBzdG9yYWdlcyAoRXhjZWwsIEFjY2VzLCBTcWxTZXJ2ZXIsIE15U3FsKTwvZGl2PjwvbGk+PGxpPjxhIGhyZWY9Ii9LQi9kYXRhYmFzZS9Dc3ZSZWFkZXIuYXNweCI+QSBGYXN0IENTViBSZWFkZXI8L2E+PGRpdiBjbGFzcz0ic21hbGwtdGV4dCI+QSByZWFkZXIgdGhhdCBwcm92aWRlcyBmYXN0LCBub24tY2FjaGVkLCBmb3J3YXJkLW9ubHkgYWNjZXNzIHRvIENTViBkYXRhPC9kaXY+PC9saT48bGk+PGEgaHJlZj0iL0tCL2RhdGFiYXNlL3NxbGRvZG9udC5hc3B4Ij5TUUwgU2VydmVyIERPJ3MgYW5kIERPTlQnczwvYT48ZGl2IGNsYXNzPSJzbWFsbC10ZXh0Ij5TUUwgU2VydmVyIGRhdGFiYXNlIGJlc3QgcHJhY3RpY2VzPC9kaXY+PC9saT48bGk+PGEgaHJlZj0iL0tCL2RhdGFiYXNlL0R5bmFtaWNNZXRob2RfSUxHZW5lcmF0b3IuYXNweCI+RHluYW1pYy4uLiBCdXQgRmFzdDogVGhlIFRhbGUgb2YgVGhyZWUgTW9ua2V5cywgQSBXb2xmIGFuZCB0aGUgRHluYW1pY01ldGhvZCBhbmQgSUxHZW5lcmF0b3IgQ2xhc3NlczwvYT48ZGl2IGNsYXNzPSJzbWFsbC10ZXh0Ij5Ib3cgdG8gdXNlIHRoZSBEeW5hbWljTWV0aG9kIGFuZCBJTEdlbmVyYXRvciBjbGFzc2VzIHRvIGNyZWF0ZSBkeW5hbWljIGNvZGUgYXQgcnVudGltZSB0aGF0IG91dHBlcmZvcm1zIHJlZmxlY3Rpb248L2Rpdj48L2xpPjwvdWw+ZAIJDw8WAh8AZ2RkAgsPZBYCZg9kFgICAQ9kFgJmD2QWAmYPFgQfBAUNUmF0ZUl0ZW1fOTMyMx8FZRYCAgYPZBYCAgEPD2QWAh8GBSJyZXR1cm4gcmF0ZUl0ZW0oOTMyMywyLHRydWUsdHJ1ZSk7ZAIbDw8WAh8AZ2RkAiEPFgIfAGhkAgsPDxYCHwIFJi9zY3JpcHQvQXJ0aWNsZXMvQXJ0aWNsZS5hc3B4P2FpZD05MzIzZGQCEQ8WAh8HBQsyNCBBdWcgMjAwNWQCEw8PFgQfBwUOU21pdGhhIFZpamF5YW4fAgUmL3NjcmlwdC9NZW1iZXJzaGlwL1ZpZXcuYXNweD9taWQ9Mjg5NzBkZAIVDxYCHwcFIkNvcHlyaWdodCAyMDA1IGJ5IE1heGltIEFsZWtzZXlraW5kZHoy3t5b1AFEWlnw6kJkhmg0NxIO" type="hidden" name="__VIEWSTATE">
<INPUT id="__EVENTVALIDATION" value="/wEWCALryJLaBgLAlMXDBwLBlMXDBwLClMXDBwLDlMXDBwLElMXDBwLP+++tCwK5upDkC+Baommd5pH6A8nKaeXZ0JWo3SoP" type="hidden" name="__EVENTVALIDATION">

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

 

</FORM> 

 

 

 

 

--------------------------

 

 

byte[] content = ReadBitmap2ByteArray(fileName);
StoreBlob2DataBase(content);
...
protected static byte[] ReadBitmap2ByteArray(string fileName)
{
  using(Bitmap image = new Bitmap(fileName))
  {
    MemoryStream stream = new MemoryStream();
    image.Save(stream, System.Drawing.Imaging.ImageFormat.Bmp);
    return stream.ToArray();
  }
}

protected static void StoreBlob2DataBase(byte[] content)
{
   SqlConnection con = Connection;
   con.Open();
   try
   {
     // insert new entry into table
     SqlCommand insert = new SqlCommand(
     "insert into Images ([stream]) values (@image)",con);
     SqlParameter imageParameter = 
     insert.Parameters.Add("@image", SqlDbType.Binary);
     imageParameter.Value = content;
     imageParameter.Size  = content.Length;
     insert.ExecuteNonQuery();
   }
   finally
   {
      con.Close();
   }
}

블로그 이미지

By훈트

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

위치기반 서비스를 구현하기 위해서는 스마트폰 기기내의 GPS, 가속도 센서 등을 이용하여 현재위치정보를 얻습니다.

안드로이드에서는 Google Maps API (구글맵스API)와 안드로이드 위치 기반 관련 라이브러리(android.location.Library)를 이용합니다!

 

 

 

 
  Google Maps Service에 접근하는 인터페이스를 제공하는 패키지로써 주요 클래스는 맵을 표시하는  MapView 클래스와 MapView를 Activity를 관리하는 MapActivity 클래스 등으로 구성되어 있습니다.
 
 
  android.location Package
 
  GPS나 무선랜 등의 정보를 이용하여 휴대전화의 현재 위치 정보(위도,경도)를 얻기 위한 기능을 제공하는 패키지로써 시스템의 위치 서비스(Location Service)의 접근을 제공하는 LocationManager 클래스, 위치정보와 주소정보를 변환하는 Geocoder 클래스, GPS엔진 상태를 표현하는 GpsStatus 클래스 등으로 구성되어 있습니다.
 

 

 

Google Maps (구글맵스)  API Key 발급 받기 :-)

 

Google Maps API 데이터를 받으려면  API Key를 발급받아야합니다. (무료)

 

1) SDK 디버그 서명증명서의 MD5 핑거프린터 확인하기

 

먼저 미리 설치해두신 JDK가 설치된 폴더의 bin폴더에 있는 Keytool를 이용해야합니다.

확인해주세용~

 

 

Keytool를 손쉽게 이용하기 위해서는 path가 등록되어야합니다.

내컴퓨터-속성-고급탭-환경변수-시스템변수의 path 항목에 JDK의 bin폴더가 지정되어있지 않으면, 지정해주세요!

 

지정이 되었으면 이제 keytool을 이용하여 MD5 핑거프린터를 확인해야합니다.

CMD창에서 다음과 같이 입력해주세요'-'

 keytool -list -alias androiddebugkey -keystore debug.keystore -storepass android -keypass android

MD5의 핑거프린터를 확인하셨으면, 복사해서 보관해주세요~

 

 

 

2) Google에서 API Key발급받기

 

 http://code.google.com/android/maps-api-signup.html 로 가셔서

 MD5 핑거프린트를 [My certificate's MD5 fingerprint] 란에입력하시고 구글계정으로 들어가면 API Key를 발급받을수 있습니다.

 

 

<결과 >

 

 

제꺼는 왜 깨져서 나왔는지 -_ㅜ

암튼, 처음에 있는게 사용자 키 API Key입니다!

 

 

3) 안드로이드에서 Google Maps API를 사용하기

 

AndroidManifest.xml

 

 

 

Application에서 com.google.android.maps라이브러리 추가!

 

 

Permission에서 인터넷 추가!!

 

main.xml

 

 

 
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.maps.MapView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/mapview"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:clickable="true" //클릭(터치)으로 맵 이동 가능
    android:apiKey="자신이 발급받은 apikey 입력"
/>

 

HelloMaps.java (메인 엑티비티 )

 

 

package babyjaycee.blog.me;


import com.google.android.maps.MapActivity;
import com.google.android.maps.MapView;
import android.os.Bundle;


public class HelloMaps. extends MapActivity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);


        MapView mapView = (MapView) findViewById(R.id.mapview);
        mapView.setBuiltInZoomControls(true);  //줌컨트롤을 활성
    }


   @Override
    protected boolean isRouteDisplayed() { //MapAcitivity의 추상메소드 .상속하면 꼭 써줘야함! 라우트정보에 관한것
return false;
    }
}

 

 <결과>

 

 

 

 

블로그 이미지

By훈트

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

이미지에 관련된 전반적인 사항이니 참고하세요.

 

1. 기본적으로 resource에 저장되어 있는 이미지의 경우 Drawable이라는 오브젝트를 구해와서 화면에 그릴 수가 있습니다.

 

Drawable drawable = getResources().getDrawable(id);

drawable.setBounds(0,0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());  // drawable을 어느 영역에 그릴 것인가?

 

onDraw(canvas canvas) {

           drawable.draw(canvas);

}

 

setBounds에 설정한 값에 따라서 자동으로 이미지가 scaling이 됩니다.

 

원본 이미지 사이즈가 100*50인데 bounds를  (0,0, 200, 100)이라고 설정하면 가로 세로가 2배로 확대되어서 그려지겠죠.

 

2. 임의의 bitmap을 생성하고 bitmap에 원하는 내용그리기

 

다음과 같이 임의의 bitmap을 생성합니다.

Bitmap bitmap = Bitmap.createBitmap(width, height, Config.ARGB_8888);

 

Config.ARGB_8888말고 Config.RGB_565 있고 몇가지 있습니다.

 

원하는 걸로 생성하면 되는데 ARGB8888 생성할 경우 투명값을 지정할 수가 있는 반면 RGB_565 생성하시면 불투명한 이미지만 가능합니다.

 

이렇게 만들어진 bitmap에 직접 그림을 그리거나 다른 이미지를 그릴려고 하면 아래와 같이 새로운 canvas를 만들어야 합니다.

 

Canvas canvas = new Canvas();

canvas.setBitmap(bitmap);

 

그러면 향후에 canvas에 그리는 모든 작업은 bitmap에 반영이 됩니다.

 

3. Bitmap Drawable간의 변환

 

안드로이드에서는 bitmap을 직접 다루기보단 대부분 Drawable이라는 wrapping된 형태로 이미지를 처리하기 때문에

 

Bitmap의 경우 종종 Drawable로 변환해야 하는 경우가 있습니다.

 

이를 위해서 BitmapDrawable이라는 클래스가 존재하고 아래와 같은 식으로 사용이 가능합니다.

 

Drawable drawable = (Drawable)(new BitmapDrawable(bitmap));

 

BitmapDrawable은 Drawable로 캐스팅이 가능하죠.

 

4. canvas 처리

 

w*h크기의 drawable 오브젝트가 있을 때 setBounds를 이용하여 임의의 좌표(x,y)에 원형크기대로 출력할려면 아래와 같습니다.

 

obj.setBounds(x,y,x+w,y+h);

obj.draw(canvas);

 

이 방식의 귀찮은 점은 항상 w,h를 지정을 해줘야 하기 때문에 코드도 상당히 길어지고 지저분해보이는 경우가 많습니다.

(getIntrinsicWidth()/Height()로 항상 구하던지 별도의 변수에 값을 유지해야하죠)

 

그래서 위와 같은 방법보다는 아래와 같이 canvas의 좌표이동 변환식을 이용하는게 깔끔합니다.

 

obj.setBounds(0,0,w,h); // 얘는 drawable을 최초로 생성했을 때 한번만 지정하면 됨

 

canvas.save(); // 현재 변환식을 저장

canvas.translate(x,y) // 좌표이동과 관련된 변환식 적용

obj.draw(canvas); // drawable을 그린다.

canvas.restore(); // 원래 변환식으로 복구

 

canvas.translate(x,y) 를 지정할 경우 출력할 이미지를 (x,y)만큼 이동시켜서 그려줍니다. (좌표이동 행렬식이라고 생각하면 됨)

 

블로그 이미지

By훈트

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.
Object 에서 byte[]
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutput out = new ObjectOutputStream(bos);   
out.writeObject(yourObject);
byte[] yourBytes = bos.toByteArray(); 

byte[]에서 Object
ByteArrayIntputSream bis = new ByteArrayInputStream(yourBytes);
ObjectInput in = new ObjectInputStream(bis);
Object o = in.readObject();
블로그 이미지

By훈트

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

xml코드가 아닌 java 코드단에서 xml의 EditText의 속성중 layout_widthlayout_height값을 변경해보자.




<layout xml 코드>
EditText의 android:layout_width android:layout_height 를 주목하자.
각각의 속성 값을 wrap_content 로 하였기 때문에 EditText의 기본 height 값과 android:text 의 값의 길이만큼의 크기로
실행 화면에 나와야 한다.

<Java Souce 코드>
소스코드에서 setLayoutParams() 메서드를 이용하여 EditText layout_width layout_height 값을 변경해 보자.


<실행 화면>
실행 화면을 보게되면 EditText의 가로 세로 길이가 300,300 으로 설정된 것을 확인할 수 있다.
블로그 이미지

By훈트

,
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

상용
aiCharts
http://www.artfulbits.com/Android/aiCharts.aspx
상용 차트입니다.
갤러리 - http://www.artfulbits.com/Android/gallery/galleryCharts.aspx
우크라이나 회사 같습니다. 미국에서도 영업합니다.
온라인 결재 299달러 시작

오픈소스
achartengine
http://code.google.com/p/achartengine/
현재도 계속 개발중입니다.
종류
line chart
area chart
scatter chart
time chart
bar chart
pie chart
bubble chart
doughnut chart

chartdroid
http://code.google.com/p/chartdroid/
현재도 계속 개발중입니다.

androidchart
http://code.google.com/p/androidchart/
주식형 차트인데 2008년 이후로 업데이트 되지 않습니다.

http://shaffah.com/droid-analytic-google-analytics-for-android
이런 비슷한 오픈 소스 프로그램이 있나 해서 찾다가 본건데
개발자가 아니어서 사용성까지는 잘 모르겠습니다.


블로그 이미지

By훈트

,