post list

2013년 1월 28일

[Java, C, Android] Chatting Program의 공통점 및 핵심 (Example)


 이번 포스트는 채팅 프로그램의 핵심을 알기 위해 Java, Android, C 의 채팅 프로그램에 대해서 조사한 바를 정리하는 겸 해서 작성했다.
 본래의 목표는 Java, Android, C 모두를 실제로 구현해보고 눈으로 확인한 다음 코드들의 핵심을 파악하는 것이 목표였으나 Android와 C는 코드만 구하고 실제로 돌려보지는 않았다. 다만 코드를 확인하고 읽는데는 문제가 없었으므로 핵심을 파악하는데는 틀리지 않았다고 생각한다.

 여기서 다룰 주제는 다음과 같다.

1. Chatting Program in Java
2. Chatting Program in Android
3. Chatting Program in C
4. 채팅 프로그램들의 공통점



1. Chatting Program in Java

 Java 채팅 프로그램의 Source Code는 ‘자바의 정석’  TCP 프로그래밍의 한 예제에서 가져왔다. 예제 프로그램은 크게 두 서버측 프로그램과 클라이언트측 프로그램으로 나누어져 있다.

 먼저 서버측 프로그램을 살펴보자.

 서버측 프로그램에서는 ServerSocket 를 이용하는데 이 녀석은 교환기 같은 역할을 한다. 우선 자신이 차지할 Port를 설정해서 만들게 되면 클라이언트가 접속해 주기를 기다린다. 

ServerSocket serverSocket = new ServerSocket(Port);

그러다가 클라이언트의 ‘요청’이 생기게 되면 socketAccept() 메서드를 이용해 서버 Socket과 클라이언트 Socket을 연결시킨다. 

Socket socket = serverSocket.accept();

 그래서 ServerSocket이 마치 교환기 같은 역할을 한다는 것이다. 왜냐하면 자신의 port에 누군가(Client측 Socket)로부터 전화가 오면 그 전화를 받을 사람(Server측 Socket)과 연결시키는 역할을 하기 때문이다.

 본 예제는 다중 채팅을 지원하기 위해서 Thread를 이용했다. Thread를 이용하는 방법에 대해서는 여기서 다룰 만한 주제가 아니니 생략한다. 다만 Thread가 무슨 일을 하는지 파악하는 것이 중요하다.

 Thread는 Client가 접속을 할 때마다 생성된다. 그리고 쓰레드 내부에 전화기 역할을 하는 DataInputStream과 DataOutputStream을 만들어서 클라이언트와 통신을 하게 된다. 

DataInputStream  in = new DataInputStream(socket.getInputStream());
DataInputStream  out = new DataOutputStream(socket.getOutputStream());

여기서 inputStream은 전화기에서 상대편 음성이 들려오는 수신부 역할을 하게 되고, outputStream은 내가 말하는 발신부 역할을 하게 된다.






2. Chatting Program in Android

 Android 상에서 채팅 프로그램을 구현하기 위해 다음의 사이트에서 소스를 가져왔다.

 여기서는 서버와 클라이언트를 각각 컴퓨터와 핸드폰으로 두었다. 컴퓨터를 서버로 돌리고 핸드폰을 클라이언트로 둬서 서로 통신을 할 수 있도록 하는 프로그램이다.

 하지만 코드를 빌드하는데 문제가 생겨 아직까지 실제로 프로그램을 실행해보지는 못했다. 그래서 눈으로 코드를 보고 분석하는데 그쳤다. 분석한 결과는 다음과 같다.

 Android도 결국 Java 랭기지를 사용하기 때문인지 자바의 그것과 아주 흡사했다. 또한 TCP Socket 채팅 프로그램의 틀에서 그렇게 많이 벗어나지 않는다는 것을 발견했다.

 서버측부터 살펴보면 서버측은 Java의 그것과 거의 똑같은 수준이다. 어차피 서버는 컴퓨터에 존재하는데다 서버가 하는 일은 동일하기 때문이다. Java에서와 마찬가지로 ServerSocket은 PORT 하나를 독점하고 클라이언트의 요청을 기다리다가 요청이 들어오면 Accept를 해서 서버와 클라이언트간의 통신을 연결시켜준다.  

 클라이언트인 Application 내에서는 주소값과 포트를 이용해서 서버로의 접속을 시도한다. 연결이 완료되면 ObjectInputStream, ObjectOutputStream을 사용해서 서버의 소켓와 데이터를 주고 받는다. ObjectStream을 사용한다는게 조금 특이하기는 하지만 중요한 것은 결국 통신을 하는데는 InputStream, OutputStream을 사용하고 있다는 점이었다.






3. Chatting Program in C on Linux

 C언어는 OS에 따라 가져오는 API가 다르기 때문에 Mac을 이용해서 Socket 프로그래밍을 하기 위해서 다음의 사이트를 참고했다.

 연결을 기다릴때는 listen()함수를 사용하고 요청을 받아들일 때는 accept()를 사용한다. 또한 데이터를 주고 받는데는 read(), write()함수를 사용한다. 

 위의 함수사용법만 제외하면 다시 Socket 프로그래밍에 대해서 이야기하기가 민망할 정도로 Java 에서의 소켓 프로그래밍와 유사했다. 그러므로 이 부분에 대한 자세한 이야기는 생략하고 핵심적인 이야기를 하기 위해 다음 목차로 넘어가도록 한다.






4. 채팅 프로그램들의 공통점

 Java, Android, C 모두 Socket을 이용해서 채팅 프로그램을 만들고 있음을 알 수 있었다. 여기서 모든 공통된 흐름이 있었는데, 정리하자면 다음과 같다.

-서버측
  1. 자신의 IP주소값과 임의의 PORT 값을 이용해서 서버측 소켓을 만든다.
  2. 클라이언트로부터 요청이 들어오기를 기다린다.
  3. 요청이 들어오면 Accept 한다.
  4. 클라이언트와 데이터를 주고 받는다. 

-클라이언트측
  1. 접속하고자 하는 서버의 IP주소와 Port를 이용해 소켓을 생성하고 접속한다.
  2. 서버와 데이터를 주고 받는다.



 여기서는 Java 소스 코드만을 압축하여 올려둡니다. Android나 C 코드가 궁금하신 분은 제가 올려둔 링크를 참조하시거나 구글에서 검색을 하시면 금방 찾으실 수 있을 겁니다.

 Java 소스코드가 필요하신 분은 아래의 링크를 클릭하여 다운 받으세요.







[Android] SurfaceView 와 Path를 이용한 Circle, Rectangle, Triangle 그리기 Example



 이전 포스트에서 다루었던 [View에서 Canvas에 도형 그리기]를 이번에는 SurfaceView에서 구현해 보았다. 이번 포스트의 구성은 다음의 4가지로 요약할 수 있다.

  1. 정리 및 요약
  2. 화면구성
  3. SurfaceView
  4. Thread on SurfaceView

1. 정리 및 요약

 본 보고서는 SurfaceView가 무엇이며 어떻게 구성되는지를 이해하는데 목적을 둔다.

  • SurfaceView : 왜 SurfaceView를 사용하는가, SurfaceView는 어떻게 구성되는가.
  • Thread on Surface : SurfaceView와 Thread는 어떤 관계가 있고 어떤 의미를 가지는가.


2. 화면구성



 이전 포스트에서 다뤘던 것과 마찬가지로 새로운 클래스를 생성하여 SurfaceView를 상속받아 맏들어지며 조금 다른 점은 Thread를 사용한다는데 있다.



3. SurfaceView

 SurfaceView는 다른 View 들과 달리 직접 Contents를 다루지 않는다. 왜냐하면 View의 경우에는 화면을 구성하는 연산, 사용자와의 상호작용 처리, 나머지 연산 등을 모두 하나의 쓰레드에서 처리하지만 SurfaceView의 경우에는 화면 업데이트를 백그라운드 쓰레드로 수행하여 처리하기 때문이다. 즉, View에서는 성능의 문제가 생길 수 있으니 SurfaceView를 사용하는 것이다.



 (참고 : SurfaceView의 이해)

 위의 그림를 참고해서 이야기하자면 SurfaceView 내의 Surface를 관리하는 것은 SurfaceHolder이다. SurfaceView를 상속받아서 SurfaceHolder를 얻고 CallBack을 연결시키게 되면 Surface를 다룰 수 있는 준비가 끝나는 것이다.

 또한 Surface View를 상속받게 되면 다음의 3가지 메서드를 생성해 줘야 한다. 
 Code에 대한 이야기는 최대한 생략하고자 하기 때문에 간단히 설명하고 넘어가겠다.

public void surfaceCreated(SurfaceHolder holder);
public void surfaceDestroyed(SurfaceHolder holder);
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h); 

 위에서 부터 차례대로 SurfaceView가 처음 생성될때, 소멸될때, SurfaceView의 format이나 size가 변경될 때 호출되는 메서드다.




4. Thread on SurfaceView

 위에서 언급한 바와 같이 SurfaceView는 백그라운드에서 쓰레드를 사용하게 된다. 보통 이러한 쓰레드를 따로 클래스로 빼서 생성하고, SurfaceView를 상속받는 클래스에서 그 쓰레드를 돌리는 구조로 만들어진다.

 다음의 그림을 참고해보자.





 즉, thread에서 만들어서 화면으로 올린다는 의미다. 이러한 과정이 쓰레드에서 이루어지는데 Holder로부터 Canvas를 가져와서 Lock을 시킨다음 그린 다음에 UnLock을 하면 비로소 화면으로 올라간다.



 코드에 대한 설명을 최대한 배제하고자 하였기 때문에 이 포스트를 통해 코드를 작성하는 법을 배우기에는 조금 힘들 것이다. 하지만 이 포스트를 이해했다면 코드를 이해하기가 쉬워질 것이다. 코드가 궁금하신 분들을 위하여 아래에 링크로 프로젝트 자체를 포멧하여 올려둔다.

 참고하실분 다운 받으세요^^






2013년 1월 24일

[Android] Canvas에 도형 그리기 Example


Android Circle, Rectangle, Triangle 그리기



  1. 화면 구성 (View를 상속받은 Class를 이용)


 위의 화면은 크게 2가지로 나눠진다. 첫번째는 하단에 존재하는 버튼 4개를 가지고 있는 레이아웃, 2번째는 View를 상속받은 클래스를 레아아웃 XML상에서 구현하여 Canvas로 활용되는 레이아웃이다.
 Canvas로 활용되는 레이아웃은 View를 상속해서 Paint, Path, Canvas 객체를 활용해서 Canvas View를 구현할 수 있었다


2. View를 상속받은 클래스를 XML 상에서 구현하는 과정

 예를 들어 Java 파일에서 Custom View를 위한 클래스를 다음과 같이 만들었다고 하자. 

class CanvasView extends View


 이렇게 View를 상속받게 되면 생성자를 생성하게 되는데 View에는 필요에 따른 3종류의 생성자가 View에 구현되어 있다.
 (참고 사이트 : Custom Android View : Definition)

 첫번째 생성자는 다음과 같다.

public CanvasView(Context context) {
super(context);
}

 이 생성자는 코드상에서 직접 CanvasView를 구현할 때 사용되는 생성자로 Parameter로 오직 Context만은 요구한다. 하지만 XML 상에서 사용하기 위해서는 이 생성자만 구현해서는 안된다.

 두번째 생성자는 다음과 같다.
public CanvasView(Context context, AttributeSet attribs) {
super(context, attribs);
}

 이 생성자는 XML상에서 CanvasView를 구현하기 위해서 사용된다. 즉, XML상에서 Custom View를 사용하기 위해서는 반드시 구현해줘야 한다. 이 생성자는 Parameter로 Context와 더불어 AttributeSet를 요구한다.
 여기서 AttributeSet 이란 Attribute들의 집합체로 Resource File (XML파일)에서 사용되는 속성들을 의미한다.
 (참고 사이트 :  Google Android Developer : AttributeSet)

 세번째 생성자는 다음과 같다.
public CanvasView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
} 

두번째 생성자와 마찬가지로 Resource File에서 사용되는 생성자다. 두번째와 다른 점은 Parameter로 int defStyle을 요구한다는 점인데 만약 defStyle이 0일 경우에는 현재 어떤 Theme도 적용시키지 않게 된다. 즉, defStyle에 적용되는 Value에 따라 theme를 변경시킬 수 있게 된다.

 기본적으로 이러한 생성자들을 구현하고 나면 XML 상에서 Custom View를 적용시킬 수 있게 된다. 적용 방법은 패키지 패쓰와 클래스 이름을 함께 XML상에서 적어주면 된다. 

 <com.example.androidtest.CanvasView
        android:id="@+id/canvasView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/> 

여기서 com.example.androidtest는 패키지 Path를 의미하고 CanvasView는 View를 상속받은 클래스의 이름이다.


3. 필요한 메서드를 Override 하기

 상속받은 View에는 중요한 여러가지 메서드가 많이 만들어져 있다. 우리는 이 메서드를 Custom View 클래스에서 오버라이드하여 사용이 가능하다. View에 구현되어 있는 메서드의 종류중 대표적인 메서드는 다음과 같다.

public boolean onKeyDown(int keyCode, KeyEvent keyEvent);
public boolean onKeyUp(int keyCode, KeyEvent keyEvent);
public boolean onTrackballEvent(MotionEvent event);
public boolean onTouchEvent(MotionEvent event);
protected void onMeasure(int wMeasureSpec, int hMeasureSpec);
protected void onDraw(Canvas canvas)


메서드 이름만 봐도 직관적으로 어떤 기능을 하는 메서드인지 알 수 있기 때문에 몇개의 메서드만 짚고 넘아가자.

public boolean onTouchEvent(MotionEvent event);

 위의 메서드는 화면에 터치를 했을때마다 호출되는 메서드다. 주어지는 인수로 MotionEvent가 들어온다. 


protected void onMeasure(int wMeasureSpec, int hMeasureSpec);

 위의 메서드는 Parameter로 부모View로 부터 결정된 화면 치수를 인수로 넘겨받는다. 이 메서드는 본 프로그램에서 사용하지 않았다. 자세한 것은 아래의 사이트에서 확인할 수 있다.


protected void onDraw(Canvas canvas);

 위의 메서드는 실제로 View에 그림을 그릴 수 있도록 해주는 메서드다. 인수로 Canvas가 들어오는데 이 Canvas에 Paint와 Path를 이용해서 화면을 구성할 수 있도록 도와준다.

4. Paint

 Paint는 Canvas에 무언가를 그리려고 할 때 사용되는 객체다. 이 녀석은 그림을 그릴 때 사용되는 붓의 종류와 같은 역할을 한다. 예를 들어 다음과 같다.

Paint pnt = new Paint();
pnt.setAntiAlias(true);
pnt.setColor(color);
pnt.setStrokeWidth(width);
pnt.setStyle(Paint.Style.STROKE);

 setAntiAlias(boolean)는 그림이 화면에 표현될 때 더욱 자연스러워 보이게 한다. 왜냐하면 그려지는 그림 주변에 옅은 색상을 더 뿌려서 화면에 표현되게 하기 때문이다.
 setStyle(Paint.Style)은 인수에 따라 그려지는 그림이 달라진다. 무슨 말인가 하면 FILL값으로 Parameter를 넘겨주게 되면 동그라미를 그렸을 때 그 안이 꽉 차게 된다. 그러나 STROKE를 넘겨주게 되면 동그라미 안은 비어있게된다.
 나머지 메서드는 직관적인 이름이기 때문에 생략한다. (Paint에 대한 더욱 자세한 정보는 검색을 통해서 알 수 있을 것이다)


5. Path

 Path는 도형과 관련된 객체로 실제로 그림을 그리는데 중요한 역할을 한다. 원, 사각형, 호, 타원등을 그리는 메서드를 기본적으로 제공해준다. 
 moveTo(int x, int y) 메서드를 사용해서 화면상에 특정 좌표로 이동하는 것도 가능하며 lineTo(int x, int y) 메서드를 이용해 직선을 긋는것도 가능하다. 특히 rLineTo(int x, int y)의 경우에는 상대좌표를 적용하여 그릴 수 있기 때문에 특히 편리하다.
 혹은 Path의 reset()메서드를 사용한 다음 캔버스에 그려주게 되면 캔버스를 깨끗하게 지울 수 있기도 하다.


6. Canvas

 View의 onDraw(Canvas)는 인수로 Canvas를 넘겨준다. 이 Canvas가 화면 상에 보이는 실제 화면이다. Canvas.drawColor()를 이용하면 기본 화면 색상을 정할 수도 있으면 Canvas.drawPath를 이용해 미리 Path를 만들어저 Canvas에 적용할 수도 있다.



7. 동그라미, 세모, 네모 구현

 먼저 Paint를 생성하고 설정을 해준다. 왜냐하면 본 프로그램에서는 색상나 굵기 등을 변경하지 않기 때문이다. 만약 이러한 기능을 설정하고 싶다면 따로 기능을 추가하여 Paint 값을 변경시켜주면 된다. 여기서는 Paint의 색상을 Black, 두께를 10으로 맞춰줬다.

 동그라미, 세모, 네모 버튼을 눌리게 되면 CanvasView에게 Int값을 넘겨주도록 한다. 이 Int 값은 CanvasView에게 사용자가 어떤 것을 그리고 싶은지에 대한 정보를 넘겨주는 것과 같다.

 버튼을 선택한 후에 화면을 터치하게 되면 CanvasView의 onTouchEvent(MotionEvent) 메서드가 호출된다. onTouchEvent 메서드에서 MotionEvent.getAction()과 Switch문을 이용해서 사용자가 손을 뗏을 경우에 그림이 그려지도록 했다. 또한 MotionEvent는 사용자가 터치한 좌표값을 넘겨주기 때문에 이 값은 CanvasView 자체 멤버로 저장되도록 했다. 

 그려지는 그림은 사용자가 미리 선택한 버튼에 따라 달라진다. 동그라미의 경우 Path의 addCircle메서드를 호출하고 네모의 경우 Path의  addRect메서드를 호출한다. 그러나 세모의 경우에는 Path에서 기본적으로 지원이 되지 않는다. 그래서 정삼각형을 기본으로 해서 Math의 Sin, Cos 함수를 이용해 그린다. 특히 여기서 상대좌표를 적용할 수 있는 Path의 rLineTo메서드가 유용하게 사용된다. 이 메서드를 이용해 삼각형을 좌표를 계산하여 그려준다. 
 그런데 문제는 딱 삼각형을 그리게 되면 마지막 끝점이 다음과 같이 어긋나게 된다.



그래서 해결한 방법은 한번더 rLineTo를 이용해 선을 그어주는 것이다. 그렇게 하면 다음과 같이 깔끔한 삼각형이 만들어진다.




소스 코드는 프로젝트 자체를 압축해서 아래의 링크에 걸어놨으니 참고하세요.

Project Source Download