[Java] Socket JAVA 2012. 2. 23. 15:02

C#과 그냥 동일한 형태이다. 단 다음 예제는 객체를 던진다.

즉, Sirializable 된 데이터를 넘겨준다. 물론 C#에도 있다. 메모리 스트림을 이용하여 넘겼었던가 그럴 것이다.

서버 측 코드는 다음과 같다.

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Server_Object
{
  public static void main(String[] args) throws IOException
  {
  
     ServerSocket ServerSocket = null;  
     Socket AcceptSocket = null;      
 
         try
         {
             ServerSocket = new ServerSocket( 8520, 2000 );                                      
   
             while(true)
             {
                 AcceptSocket = ServerSocket.accept();
                 ThreadClass service = new ThreadClass(AcceptSocket);
                 service.run();
             }
         }
         catch(Exception ex)
         {
          System.out.println("error" + ex.getMessage());
         }
         finally
         {
             ServerSocket.close();           
         }
  }
}

class ThreadClass extends Thread
{
    private Socket s_socket;
   
    public ThreadClass(Socket Sock)
    {       
        s_socket = Sock;
    }

    public void run()
    {
        try
        {
   ObjectInputStream in = new ObjectInputStream(s_socket.getInputStream());
   
   Student data = (Student)in.readObject();
   
   out.writeObject(data);
        }
        catch(Exception e)
        {
         System.out.println("error" + e.getMessage());
        }
        finally
        {                  
         try
         {
          s_socket.close();
         }
         catch(IOException ioex)
         {          
         }
        }
    }
}

크게 다른 것은 없다

소켓을 생성하여 거기에 대한 접근 소켓을 따로 처리 해 주면서 Stream을 통하여 처리를 한다. 문자열을 보내고 싶다면

BufferedWriter와 BufferedReader, StreamReader 쪽을 찾아서 처리를 하면 된다.

스트림을 받을 땐 명시적으로 반드시 해당하는 형에 따른 변환을 하여서 처리를 해야한다.

그리고 서버 소켓 쪽에 살펴보면 2000 이라고 명시 된 부분이 있는데 이는 C#에서 Listen 과 같은 역할을 한다.

클라이언트 쪽은 다음과 같다.

import java.net.*;
import java.io.*;

public class Client_Object {
 
 public static void main(String[] Args) throws IOException
 {
  Socket c_socket = null;
    
  try
  {   
   c_socket = new Socket("127.0.0.1",5425);
   
   ObjectOutputStream out = new ObjectOutputStream(c_socket.getOutputStream()
         Student data = new Student();
         data.setName("홍길동");
         data.setAdd("경북");
         
            out.writeObject(data);
  }
  catch(Exception ex)
  {
   System.out.println("error" + ex.getMessage());
  }
  finally
  {
   c_socket.close();
  }
 }
}
역시나 마찮가지로 소켓을 하나 생성한 후 ObjectOutputStream을 이용하여 날려 보내면 된다.

바이트로 데잍를 전송 시킬 경우는 다음과 같다.

일단 먼저 서버이다.

try
                {
                   byte[] buff = new byte[1024];                  
                   in.read(buff);                       
                   String sData= new String(buff);
                  
                   System.out.println(sData);
                  
                   out.write("S->C".getBytes());   
                }
                catch(Exception e)
                {
                 System.out.println("error" + e.getMessage());
                 s_socket.close();
                    break;
                }
                finally
                {                  
                }

InputStream을 이용하여 buffer에 쌓인 데이터를 받아  원하는 형태로 바꿔주고

전송 할 경우 getBytes()를 이용한다. 이는 C#에 Encoding.Defalt.GetBytes() 와 동일한 역할을 한다.

클라이언트 측도 위 내용과 동일한 형태이다. InputStream과 OutputStream을 이용하면 손쉽게 작성 가능하다.

말이 C#과 연동이지..

아이피랑 포트랑만 연결되면.. 다들 된다 ㅠㅠ

그러니 다들 돌 좀 던지지 마세요 ㅠㅠ


import java.util.Calendar;
import java.util.Timer;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class pay_Main {

 /**
  * @param args
  */
 public static void main(String[] args) {
  // TODO Auto-generated method stub
  //Timer timer = new Timer();
  //Calendar date = Calendar.getInstance();
  //timer.schedule(new TimeRun(), date.getTime(), 1 * 1000);
  ServerSocket ServerSocket = null;
        Socket AcceptSocket = null;      
       
        int port = 8520;

        try{
            ServerSocket = new ServerSocket( 8520, 2000 );                                      /* Create Server Socket. 2000 개 fork 가능 */
            System.out.println("Client Connect "+ ServerSocket.getInetAddress() + " 포트: " + port + "에 바인드 되었습니다.");
 
  
            while(true) {
                System.out.println("Client Connect Wait");
                AcceptSocket = ServerSocket.accept();
               
                String ip =  AcceptSocket.getInetAddress().toString();
                  
                System.out.println("Connection");

                NXService service = new NXService(AcceptSocket);
                service.run();
            }
        }catch(IOException ex) {
        }finally {
            try {
              if (ServerSocket != null) ServerSocket.close();
             }catch(Exception ex2) {
             }
        }
 } 
}
class NXService extends Thread
{
    Socket xSocket;

    final int miCutNum  = 1024;                        
    final int miCut     = 960;                         
    byte[] buff         = null;                        

    DataInputStream din     = null;                    
    DataOutputStream dout   = null;                    
    int protocol            = 0;                       

    BufferedReader br;
    BufferedWriter bw;

    String[] sCuttingString;                           
    String sReturnValue = "";


    public NXService(Socket Sock) {
        super();
        xSocket = Sock;
    }

    public void run() {
        String sAddString = "";    
        try {

            this.din  = new DataInputStream(new BufferedInputStream(xSocket.getInputStream()));        
            this.dout = new DataOutputStream(new BufferedOutputStream(xSocket.getOutputStream()));     

            while(true){
                try {

                    buff = new byte[miCutNum]; 
                   protocol = (byte)this.din.read(buff);                                
                   
                        String sData= new String(buff);

                        System.out.println(sData);
                        dout.writeBytes("ServerReceive");
                        dout.flush();
                }
                catch(Exception e){  
                    break;
                }finally{}

            } // end whlie


                }catch(Exception e){
                    System.out.println("Error : Server Send & Receive to Client");    
                }
           
    }

}

 

싱글턴 패턴을 보다가 신기한 것을 알았다 -_-

Syncronized를 할 경우 멀티 스레딩의 경우 효율적이지 못한 결과가 일어난다.

성능 저하를 말하는 것이다.

하지만 -_- DCL..

요놈을 이용하면 된답 -_-

이것은 인스턴스가 생성 되었는지 확인하고 난 다음 생성되지 않을때만 동기화를 한다

하지만 요놈은.. 1.4버전 이전은.. 되지 않는답 ㅠㅡㅠ

다음은 그 간단한 코드 예이다

public class Test{

         private volatile static Test t;

         private Test(){}

         public static Test getInstance(){
                if(t==null){
                     syncronized(Test.class){
                             if(t==null){
                                     t = new Test();
                             }
                     }
                 }
         }
}

자바 네이티브 인터페이스(JNI)는 기본 언어 프로그램과 자바 가상 시스템(JVM) 사이의 통신 설정에 사용할 수 있는 메커니즘입니다. JVM과 C/C++ 코드 간의 상호 작용에 대해서는 JNI 관련 설명서나 JNI에 관한 기술 문서에서 자세히 다룹니다. 또 자바 SDK는 자바 코드를 사용한 C/C++ 프로그램 호출을 용이하게 하는 헤더 파일 생성 유틸리티도 제공합니다.

그러나 자바와 어셈블리 언어 코드의 연동에 대해 다룬 문서는 찾아보기가 어렵습니다. 저는 이전 기사를 통해 자바 애플리케이션에서 어셈블리 언어 프로그램을 호출하는 방법을 설명한 바 있습니다. 이번에는 어셈블리 언어 코드로 자바 메소드를 호출하는 데모 애플리케이션을 통해 ASM 프로세스에서 자바 프로그램을 호출하는 방법에 대해 소개하겠습니다. 이 자바 메소드는 Swing JDialog를 띄워서 실제로 실행되었음을 보여 줍니다.


ASM 기능의 자바를 선택하는 이유

자바를 구현하려면 JNI가 꼭 필요합니다. JVM의 일부 기능은 기본 플랫폼과의 상호 작용을 통해서만 구현되기 때문입니다. 그러나 이것 말고도 자바 클래스는 흔히 다른 언어로 작성된 애플리케이션을 보완하는 데 유용합니다. 자바는 고급 기능을 아주 간단하게 구현할 수 있도록 하는 광범위한 API를 제공하기 때문입니다.

얼마 전에 저는 몇 가지 소스에서 실시간 데이터를 수집하고 수집된 데이터를 순환 버퍼에 저장하여 버퍼가 가득 차면 새 데이터로 예전 데이터를 덮어쓰게 하는 애플리케이션 작업에 참여한 적이 있습니다. 지정된 트리거 이벤트가 디지털 입력에서 감지되면 정해진 수의 데이터 샘플을 버퍼에 저장하여 트리거 이전과 이후의 데이터 스냅샷을 볼 수 있도록 했습니다. 이때 원본 애플리케이션은 어셈블리 언어로 작성했습니다.

이 애플리케이션을 몇 달 사용해 본 결과, 트리거 이벤트가 발생할 때마다 애플리케이션이 권한 있는 감독자에게 스냅샷을 메일로 보내도록 하면 정말 편리하겠다는 생각이 들었습니다. 물론 그런 확장 기능을 어셈블리로 작성할 수도 있었지만, 팀원들은 이런 경우에는 자바로 확장 기능을 만든 다음, ASM 프로그램으로 기능을 연결하는 것이 더 쉽다는 의견이었습니다. 저는 ASM 지향의 JNI로 작업해 본 경험이 있었기 때문에 그것이 가능하다는 것을 알았고, 실제로 그 프로젝트는 금새 구현되어 성공을 거두었습니다.

이러한 애드온을 활용할 수 있는 어셈블리로 작성된 레거시 애플리케이션이 많은 것으로 알고 있습니다. 그러나 JNI를 유용하게 쓸 수 있는 것은 레거시 애플리케이션의 개조만이 아닙니다. 믿지 않는 분들도 많으시겠지만, 아직도 신규 프로그램의 특정 부분을 어셈블리 언어로 작성하는 경우가 있습니다.

얼마 전 발표된 어떤 기사에서 저자는 "핫 코드 경로를 최대한 효율적으로 만들기 위해 아직도 제품에 어셈블리 언어를 사용하는 썬 파트너들이 많다는 사실을 발견했다. 최신 컴파일러는 종전보다 훨씬 효율적인 코드를 만들어내지만, 컴파일러로 작성한 코드는 각각의 마이크로프로세서 명령에서 최대한 성능을 짜내는 방법을 알고 있는 엔지니어가 수작업으로 작성한 어셈블리에 비해 성능이 떨어진다. 프로그래머의 재량이 발휘될 여지가 많은 어셈블리 언어는 여전히 강력한 최적화 도구로 남아 있으며 현명하게 사용하면 성능을 높일 수 있다"고 주장했습니다. 이러한 "혼합 언어" 애플리케이션에서 ASM 기능의 자바를 사용할 수 있다는 것은 분명히 이익이 됩니다.

여기서 제시하는 방법은 ASM이 아닌 언어의 자바 코드를 호출할 때도 사용할 수 있습니다. JInvoke를 .dll로 다시 작성하는 경우, 예를 들어 FORTRAN 언어로 작성한 코드를 여기에 링크하여 자바 메소드를 호출할 수 있습니다.

저는 레거시 ASM 코드의 JNI를 다음 두 가지에 사용했습니다.

  • 기능 향상: 앞서 언급했듯이 기존 ASM 애플리케이션에 메일링 기능을 추가했습니다.
  • 인터페이스 향상: 쌍방향 사용자 인터페이스를 추가했습니다(대부분 AWT이지만 일부 Swing도 포함).

이렇게 향상된 애플리케이션을 Windows 2000 및 XP에서 실행해 보았습니다. 사용한 자바 버전은 1.3, 1.4, 1.6입니다. 그 결과, 모든 애플리케이션이 원활하게 작동되었습니다.

제가 데모 코드에 사용한 어셈블리 언어의 버전은 MASM32입니다. 전체 MASM32 번들을 무료로 다운로드할 수 있으며, 자바-ASM의 상호 작용을 시험하려면 이 번들을 컴퓨터에 설치해야 합니다. Iczelion의 사이트에는 MASM 프로그래밍에 대한 매우 유용한 튜토리얼 세트가 있습니다. JNI에 관한 문서 중 최고는 Sheng Liang의 책 The Java Native Interface: Programmer's Guide and Specification입니다.

이 책도 무료로 다운로드할 수 있습니다. 이 기사에 수록된 자바 코드 샘플(AsmToJava)을 실행하려면 SDK(최소한 JRE)가 필요합니다. 데모에서 어셈블리 언어를 사용한 JInvoke 부분은 .exe 파일로 컴파일했으며 MASM 번들 없이도 실행됩니다. 소스 코드를 수정하여 다시 컴파일하려는 경우에만 어셈블러/링커가 필요합니다.


기본 원리

JNI는 종합적인 JVM 인터페이스입니다. 이 인터페이스는 주로 풍부한 함수로 알아볼 수 있습니다. 기본 코드는 이러한 함수를 호출하여 구현된 JVM과 상호 작용합니다. 자세한 함수 설명은 Sheng Liang의 책을 참조하십시오. 이러한 함수는 대부분 JVM을 만들어야만 액세스가 가능하지만 JNI에서 몇 가지 기본 함수를 직접 내보낼 수도 있습니다. 나중에 자세히 다루겠지만, 두 번째 유형의 함수로 JVM을 인스턴스화하여 다른 JNI 함수를 호출할 수도 있습니다.

일단 JVM이 작성되면 JVM 인스턴스가 있어야만 구현 가능한 JNI 함수에 어셈블리 언어 프로그램이 액세스할 수 있게 됩니다. 이러한 모든 JNI 함수에 대한 포인터는 함수 테이블이라는 테이블에 저장됩니다. JVM을 로드하는 ASM 코드는 사실상 포인터인 JNIEnv라는 변수를 수신합니다. 그리고 JNIEnv는 함수 테이블에 대한 실제 포인터가 들어 있는 메모리의 특정 위치를 가리킵니다. 그림 1은 이러한 액세스 체인을 보여 줍니다.

사용자 삽입 이미지
그림 1. JNI 함수 액세스

보시다시피 JNI 함수에 대한 각 포인터는 길이가 4바이트입니다. 따라서 함수 테이블의 시작 주소에 해당 함수 색인의 4배를 더하여 정의된 위치에서 원하는 함수의 포인터를 찾을 수 있습니다. 함수 색인은 0을 기준으로 합니다. 즉, 첫 번째 함수의 포인터는 색인 0, 두 번째 함수의 포인터는 색인 1과 같이 이어집니다. Sheng Liang의 책에는 모든 JNI 함수의 색인 값이 나열되어 있습니다.

ASM 코드로 자바 프로그램을 호출하려면 다음 단계를 실행해야 합니다.

  • JVM 인스턴스화
  • 클래스 찾기
  • 메소드 ID 확인
  • 메소드 호출

각 단계를 자세히 살펴보기 전에, 이 프로그램 및 유사 프로그램을 손쉽게 작성하기 위해 include 파일을 사용하는 방법부터 알아보겠습니다. 자바와 기본 코드 사이의 상호 작용은 JNI로 노출된 함수를 통해 이루어지므로 기본 프로세스에서 이러한 함수를 반복적으로 호출할 필요가 있습니다. 따라서 이 활동을 처리하기 위해 매크로를 사용하겠습니다. 그러면 길이가 상당히 길고 비슷한 코드를 반복해서 작성할 필요가 적어지며 오타로 인해 프로그램에 버그가 생길 위험도 줄일 수 있습니다.


매크로

ASM 코드에서 함수를 호출할 때는 함수 테이블에서 해당 함수의 포인터를 찾은 뒤 그 포인터를 사용하여 원하는 함수를 호출해야 합니다. 앞서 설명했듯이, 포인터를 찾으려면 JNIEnv 포인터 체인을 따라 함수 테이블의 시작 주소를 확인한 다음, 해당 함수의 색인을 사용하여 함수 포인터를 검색해야 합니다. 함수 테이블의 시작 주소를 확인하는 첫 번째 작업에는 항상 동일한 코드를 사용하며 다음과 같은 매크로로 처리할 수 있습니다.


    ;This macro returns the pointer to 
    ;Function Table in fnTblPtr
    GetFnTblPtr MACRO envPtr, fnTblPtr
        mov ebx, envPtr
        mov eax, [ebx]
        mov fnTblPtr, eax
    ENDM

위의 코드로 정의되는 매크로는 매개변수 두 개를 취합니다. 첫 번째는 JNIEnv 포인터이고, 두 번째는 이 매크로가 해당 포인터를 반환하게 될 함수 테이블의 위치를 가리킵니다. 매크로는 함수 테이블 포인터를 eax로 로드한 다음, 이를 fnTblPtr에 저장합니다. 프로그램 자체에 이 매크로를 정의하여 사용할 수도 있습니다. 아니면 여기서 선택한 방법처럼 이러한 매크로를 모두 include 파일에 정의한 다음, include 문을 통해 ASM 프로그램과 함께 이 파일을 사용해도 됩니다. 여기서 사용한 include 파일은 jav_asm.inc입니다. 이 파일은 GetFnTblPtr 매크로뿐 아니라 이 예제에 필요한 다른 매크로도 모두 정의합니다. 또한 jav_asm.inc는 매크로 외에도 JVM을 작성하는 함수의 원형은 물론 해당 함수의 매개변수로 사용할 구조도 정의합니다. 끝으로, java_asm.inc는 사용하기 편리하도록 모든 JNI 함수에 기호 이름을 지정해 줍니다.

함수 테이블에 대한 포인터가 확인되었으면 원하는 함수의 포인터를 검색해야 합니다. 여기 사용되는 코드도 색인 부분을 제외하면 항상 동일합니다. 이 작업은 다음 매크로로 처리합니다.


    ;This macro returns the pointer 
    ;to a function in fnPtr.
        GetFnPtr MACRO fnTblPtr, index, fnPtr
                mov eax, index
                mov ebx, 4
                mul ebx
                mov ebx, fnTblPtr
                add ebx, eax
                mov eax, [ebx]
                mov fnPtr, eax
        ENDM

이 매크로는 index 값에 4를 곱하고 그 결과를 함수 테이블의 시작 주소(fnTblPtr 참조)에 더하여 액세스할 함수의 포인터를 확인합니다. 그런 다음, 이 포인터를 fnPtr에 저장합니다.

나머지 세 매크로도 처리하는 매개변수의 수만 제외하면 거의 동일합니다.


    ;The next 3 macros push parameters as per 
    ;stdcall and call the function through fnPtr
        CallFunction2 MACRO param1, param2, fnPtr
                push param2
                push param1
                call [fnPtr]
        ENDM

        CallFunction3 MACRO param1, param2, param3, fnPtr
                push param3
                push param2
                push param1
                call [fnPtr]
        ENDM

        CallFunction4 MACRO param1, param2, param3, param4, fnPtr
                push param4
                push param3
                push param2
                push param1
                call [fnPtr]
        ENDM

보시다시피 이들 매크로는 fnPtr을 제외하고 stdcall을 위해 매개변수를 역순으로 푸시(push)한 다음, fnPtr을 포인터로 사용하여 해당 함수를 호출합니다.

이로써 기본적인 빌딩블록이 완성되었습니다. 이제 데모 애플리케이션의 4단계 시퀀스를 살펴보겠습니다.


JVM 인스턴스 작성

JInvoke는 명령줄에서 자바 애플리케이션을 실행할 때 java 명령이 하는 것과 거의 비슷한 방식으로 JVM 인스턴스를 작성합니다. 구현된 자바 가상 시스템(JVM)은 기본 애플리케이션이 가상 시스템을 로드할 수 있도록 하는 호출 인터페이스라는 메커니즘을 제공합니다. java 명령은 이 호출 인터페이스를 사용하는 C 프로그램을 호출하여 자바 애플리케이션을 실행하고, JInvoke도 바로 이 인터페이스를 사용합니다. JVM을 로드하는 코드는 다음과 같습니다.


    .
    .
    .
    va          vm_args <>
    jvmo        JavaVMOption <>

    .
    .
    .

    mov jvmo.optionString, offset opzero

    mov va.options, offset jvmo
    mov va.version, 00010002h
    mov va.nOptions,1
    mov va.ignoreUnrecognized, TRUE

    invoke JNI_CreateJavaVM, offset JavaVM, offset JNIEnv, 
        offset va

여기서는 우선 구조 두 개를 선언하겠습니다. 이미 설명했듯이 이 구조는 jav_asm.inc에 정의되어 있습니다. JVM을 작성하려면 몇 가지 매개변수를 지정해야 합니다. 지정된 매개변수는 이 구조를 통해 JNI_CreateJavaVM 함수로 전달됩니다.

이 예제에서는 AsmToJava 클래스의 main 메소드를 호출하겠습니다. 제 컴퓨터의 C:\j2sdk1.4.2_05\testjni 폴더에 클래스 파일이 들어 있습니다. JNI 사양에 설명된 방법에 따라 opzero 문자열로 이 경로를 정의합니다. opzerojvmo 구조로 로드되고, 그 다음에 jvmo에 대한 오프셋이 va 구조로 로드됩니다. JNI_CreateJavaVM 함수로 전달되는 마지막 매개변수는 va입니다. 따라서 로드된 JVM은 우리가 원하는 클래스를 어디서 찾아야 하는지 알게 됩니다.

JNI_CreateJavaVM은 반환과 동시에 성공한 경우에는 eax에 0을, 실패한 경우에는 eax에 음수값을 반환합니다. 함수로 JVM 인스턴스를 작성하는 데 성공한 경우에는 JVM 인터페이스에 대한 포인터와 해당하는 JNIEnv 포인터를 각각 JavaVMJNIEnv에서 사용할 수 있습니다.

JNI_CreateJavaVM 함수로 반환된 JInvoke는 즉시 eax의 내용을 확인합니다. 값이 0이 아니면 JVM이 로드되지 않은 것입니다. 사용자에게 이를 알리는 메시지가 표시되고 프로세스는 종료됩니다.


    .if eax == 0
        .
        .
        .
    .else
        invoke MessageBox, 0, addr Fail1Text, 
            addr Caption, 16; failed to create JVM
    .endif 

한편, eax의 내용이 0이라면 해당하는 메시지를 담은 메시지 상자(그림 2)가 표시되고 다음 단계가 실행됩니다.


    .if eax == 0
        invoke MessageBox, 0, addr VmText, 
            addr Caption, 64; indicate success

사용자 삽입 이미지
그림 2. JVM이 로드되었음을 알리는 메시지

단, JInvoke는 개념 제시를 위한 수단에 불과하므로 여기서는 가장 간단한 방법으로 JVM 인스턴스를 작성했음을 말씀드립니다. Sheng Liang이 책에서 설명했듯이 이 밖에도 다양한 매개변수를 지정할 수 있습니다.


클래스 찾기

JVM을 로드한 뒤, JInvoke는 원하는 자바 애플리케이션의 시작 지점이 될 클래스를 찾아야 합니다. 아래 코드는 이를 위해 FindClass 함수를 호출합니다.


    GetFnTblPtr JNIEnv, fntblptr
    GetFnPtr fntblptr, FI_FC, fnptr ; ptr to FindClass
    CallFunction2 JNIEnv, offset ProcName, 
        fnptr ; call FindClass

    .if eax != 0
        mov classid, eax
        invoke MessageBox, 0, addr FcText, 
            addr Caption, 64; class found

이 클래스 경로는 앞서 jvmo 구조에 로드되었으며(mov jvmo.optionString, offset opzero) 이미 JVM에 알려져 있습니다. FindClass 함수는 클래스를 찾을 수 있는 경우에는 eax에 해당 ID를 반환하고, 그렇지 않으면 0을 반환합니다. 클래스가 발견되면 해당 ID를 저장한 다음, 메시지 상자(그림 3)를 표시하여 사용자에게 알립니다.

사용자 삽입 이미지
그림 3. 클래스가 발견되었음을 알리는 메시지

클래스를 찾을 수 없는 경우에는 해당하는 메시지와 함께 프로세스가 종료됩니다. 그림 4는 호출한 함수가 성공하지 못했을 때 표시되는 메시지의 예입니다.

사용자 삽입 이미지
그림 4. 클래스를 찾을 수 없음을 알리는 메시지


메소드 ID 확인

메소드를 호출하려면 해당 ID를 확인해야 합니다. 정적 메소드의 ID를 반환하는 함수는 GetStaticMethodID입니다. 여기서는 AsmToJava 클래스의 main 메소드를 호출하기 위해 이 함수를 사용하겠습니다. 이 함수에는 JNIEnv 외에 다음과 같은 매개변수를 사용할 수 있습니다.

  • 해당 메소드가 속한 클래스의 ID. 이전 단계에서 참조한 classid 변수입니다.
  • 메소드 이름. methodname 문자열입니다.
  • 메소드의 매개변수와 반환 유형을 지정하는 메소드 설명자. 여기서 사용할 메소드의 설명자는 methodsig 문자열입니다. 이 경우에 매개변수는 String array이고 반환 유형은 void입니다. JNI 사양에 관한 Sheng Liang의 책에는 메소드 및 변수의 설명자를 작성하는 방법이 나와 있습니다.

GetStaticMethodID 호출은 지금까지 살펴본 다른 함수 호출과 매우 유사합니다.


    GetFnPtr fntblptr, FI_GSMID, fnptr ; ptr to GetStaticMethodID
    CallFunction4 JNIEnv, classid, offset methodname, 
        offset methodsig, fnptr ; GetStaticMethodID

    .if eax != NULL
        mov methid, eax
        invoke MessageBox, 0, addr GsmiText, addr Caption, 64

GetStaticMethodIDeax에 ID를 반환합니다. 메소드 ID를 확인하는 데 실패한 경우에는 대신 NULL이 반환됩니다. 그리고 JInvokeeax의 내용을 확인하여 다음 단계(그림 5)로 진행할지 아니면 프로세스를 종료할지를 결정합니다.

사용자 삽입 이미지
그림 5. 메소드 ID가 확인되었음을 알리는 메시지


대상 메소드 호출

반환 유형 void로 정적 메소드(여기서는 main 메소드 호출)를 호출하는 JNI 함수는 CallStaticVoidMethod입니다. 다음은 이 함수의 포인터를 확인한 후 필요한 매개변수로 호출하는 코드입니다.


    GetFnPtr fntblptr, FI_CSVM, fnptr ; get CallStVM ptr
    CallFunction3 JNIEnv, classid, methid, fnptr; call CallStVM

자바 애플리케이션이 성공적으로 실행되었음을 알리는 대화 상자가 나타납니다.

사용자 삽입 이미지
그림 6. 자바 메소드 호출 성공

호출한 자바 메소드가 반환되면 JInvoke는 종료됩니다.

사용자 삽입 이미지
그림 7. 프로세스 종료를 알리는 메시지

ExitProcess 함수를 호출하면 해당 프로세스의 모든 스레드가 정지되며 해당 프로세스에서 작성된 자바 스레드도 정지됩니다. 그러므로 이 방식으로 자바 프로그램을 실행할 때는 필요한 활동이 모두 완료된 뒤에 호출한 자바 메소드가 반환되도록 하는 것이 중요합니다. 실제로 호출 프로세스의 종료 및 JVM에 관한 문제에는 세심한 주의를 기울여야 합니다. 자세한 내용은 Windows API 설명서최신 JNI 사양을 참조하십시오.


결론

여기서는 어셈블리 언어 코드로 자바 애플리케이션을 실행하는 데 사용할 수 있는 기본적인 방법을 소개했습니다. 자바 환경에서 안전하게 작업하려면 기본 프로그램에 적절한 오류 확인 기능을 통합해야 합니다. Sheng Liang은 기본 코드의 예외 처리를 비롯하여 다양한 확인 예제를 다루고 있습니다. JNI와 연동되는 ASM 프로그램에는 가급적 이러한 오류 확인 메소드를 사용해야 합니다.

jav_asm.inc 파일을 사용하면 JNI 함수 색인을 손쉽게 지정할 수 있습니다. 번호 대신 기호 이름을 사용하여 색인을 지정하면 오류가 발생할 가능성이 적어집니다. 매크로를 사용하는 것도 오류 감소에 도움이 됩니다. 여러분의 코드에 자유롭게 이 파일을 사용하시고, 필요에 따라 수정하셔도 좋습니다. 그러나 면책 조항 없이 이 파일을 재배포하는 일은 없도록 주의해 주십시오.

JInvoke를 실행할 때 jvm.dll을 찾을 수 없다는 오류 메시지가 나타날 수 있습니다. 이 경우에는 jvm.dll이 포함된 디렉토리 경로를 PATH 환경 변수에 추가해야 할 수 있습니다. 이 DLL은 대개 자바 SDK 루트 폴더의 jre\bin\client 디렉토리에 들어 있습니다. 예를 들어, 제 컴퓨터에서 자바 1.4 릴리스의 해당 경로는 C:\j2sdk1.4.2_05\jre\bin\client입니다. 그러나 자바 1.3을 사용한다면 jre\bin\classic 폴더를 추가해야 합니다. 올바른 경로를 선택하도록 주의하십시오.

끝으로, JInvokeinclude 문에 명명된 파일 경로는 사용자 컴퓨터의 디렉토리 구조에 따라 결정된다는 점을 지적하고 싶습니다. JInvoke에 지정되어 있는 경로는 내 컴퓨터에 해당되는 경로입니다. JInvoke.exe의 경우에는 사용자 시스템에 MASM 구성요소(특히 .inc 또는 .lib 파일)가 로드되지 않아도 실행이 가능합니다. 코드를 수정하여 재컴파일하고 싶으면 해당 디렉토리를 설정한 방식에 맞게 경로 정보를 수정해야 합니다. 그리고 AsmToJava 클래스 파일을 opzero에 정의된 디렉토리에 로드해야 합니다. 아니면 이 클래스의 경로를 적용하여 opzero를 변경해도 됩니다. 그 경우에는 JInvoke의 소스 파일을 재컴파일해야 합니다. 이때 다른 경로 이름도 위의 설명과 같이 수정해야 한다는 점을 잊지 마십시오.


참고 자료

Biswajit Sarkar는 프로그램 방식의 산업 자동화를 전문으로 하는 전기공학 엔지니어입니다.


이 글의 영문 원본은
Launch Java Applications from Assembly Language Programs
에서 보실 수 있습니다.

출처 : http://sdnkorea.com

Java Press (http://www.gihyo.co.jp/magazines/javapress) 라는 일본의  
Java전문 서적(2003년 2월판)에서 발췌한 Java performance tips입니다.  

그중 Java 일반적사항에 관련하여 7개, String 관련2개, Collection관련 8개,  
IO관련2개등 총 4개 분야 19여개의 Tips에 대해 제가 나름대로 번역해본 자료입니다.  

출처 : javaservice.net 김선필(piper2)님의 글입니다.
이 문서에서는 JDBC용 Microsoft SQL Server 2000 드라이버를 사용하여 SQL Server 2000에 연결하는 방법을 설명합니다.


참고: JDBC용 Microsoft SQL Server 2000 드라이버의 설치 지침은 JDBC용 Microsoft SQL Server 2000 드라이버 설치 설명서를 참조하십시오.

JDBC용 Microsoft SQL Server 2000 드라이버를 설치한 후 연결 URL이나 JNDI 데이터 원본을 사용하여 프로그램에서 데이터베이스에 연결할 수 있습니다. 이 문서에서는 연결 URL을 사용하여 데이터베이스 연결을 구성하고 테스트하는 방법을 설명합니다.

데이터베이스에 연결하는 한 가지 방법은 JDBC 드라이버 관리자를 통해 DriverManager 클래스의 getConnection 메서드를 사용하는 것입니다. 이 메서드를 사용하는 가장 간단한 방법은 URL, 사용자 이름 및 암호가 포함된 문자열 매개 변수를 사용하는 것입니다. 다음 절에서는 JDBC 프로그램에서 JDBC용 Microsoft SQL Server 2000 드라이버를 로드하는 방법을 설명합니다.

맨 위로

CLASSPATH 변수를 설정하려면

JDBC용 Microsoft SQL Server 2000 드라이버의 .jar 파일이 CLASSPATH 변수에 나열되어 있어야 합니다. CLASSPATH 변수는 Java Virtual Machine(JVM)이 컴퓨터에서 JDBC 드라이버를 찾을 때 사용하는 검색 문자열입니다. 드라이버가 CLASSPATH 변수에 없으면 드라이버를 로드하려고 할 때 다음 오류 메시지가 나타납니다.
java.lang.ClassNotFoundException: com/microsoft/jdbc/sqlserver/SQLServerDriver
다음 항목을 포함하도록 시스템의 CLASSPATH 변수를 설정합니다.
  • \설치 경로\Lib\Msbase.jar
  • \설치 경로\Lib\Msbase.jar
  • \설치 경로\Lib\Mssqlserver.jar
다음은 구성된 CLASSPATH 변수의 예입니다.

CLASSPATH=.;c:\program files\Microsoft SQL Server 2000 Driver for JDBC\lib\msbase.jar;c:\program files\Microsoft SQL Server 2000 Driver for JDBC\lib\msutil.jar;c:\program files\Microsoft SQL Server 2000 Driver for JDBC\lib\mssqlserver.jar

맨 위로

드라이버를 등록하려면

드라이버를 등록하면 JDBC 드라이버 관리자에게 로드할 드라이버를 지시하게 됩니다. class.forName 함수를 사용하여 드라이버를 로드하는 경우 드라이버의 이름을 지정해야 합니다. 다음은 JDBC용 Microsoft SQL Server 2000 드라이버의 드라이버 이름입니다.

com.microsoft.jdbc.sqlserver.SQLServerDriver

다음 예제 코드에서는 드라이버를 등록하는 방법을 보여 줍니다.
Driver d = (Driver)Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver").newInstance();
				
맨 위로

연결 URL을 전달하려면

연결 URL의 형태로 데이터베이스 연결 정보를 전달해야 합니다. 다음은 JDBC용 Microsoft SQL Server 2000 드라이버의 템플릿 URL입니다. 사용 중인 데이터베이스에 맞는 값으로 변경하십시오.

jdbc:microsoft:sqlserver://servername:1433

다음 예제 코드에서는 연결 URL을 지정하는 방법을 보여 줍니다.
con = DriverManager.getConnection("jdbc:microsoft:sqlserver://localhost:1433", "userName", "password");
				
서버 이름 값은 IP 주소나 호스트 이름(네트워크가 호스트 이름을 IP 주소로 확인한다고 가정하는 경우)일 수 있습니다. 호스트 이름에 ping 명령을 실행하고 올바른 IP 주소와 함께 응답을 받는지 확인하여 서버 이름 값을 테스트할 수 있습니다.

서버 이름 뒤의 숫자 값은 데이터베이스가 수신하는 포트 번호입니다. 위에 있는 값은 예로 든 기본 값이므로 데이터베이스가 사용하는 포트 번호로 변경해야 합니다.

연결 URL 매개 변수의 전체 목록은 JDBC용 Microsoft SQL Server 2000 드라이버 HTML 도움말이나 온라인 가이드에서 "Connection String Properties" 절을 참조하십시오.

맨 위로

연결을 테스트할 예제 코드

다음 예제 코드는 데이터베이스에 연결하고 데이터베이스 이름, 버전 및 사용 가능한 카탈로그를 표시합니다. 서버 속성을 사용 중인 서버에 해당하는 값으로 바꾸십시오.
import java.*;
public class Connect{
     private java.sql.Connection  con = null;
     private final String url = "jdbc:microsoft:sqlserver://";
     private final String serverName= "localhost";
     private final String portNumber = "1433";
     private final String databaseName= "pubs";
     private final String userName = "user";
     private final String password = "password";
     // Informs the driver to use server a side-cursor, 
     // which permits more than one active statement 
     // on a connection.
     private final String selectMethod = "cursor"; 
     
     // Constructor
     public Connect(){}
     
     private String getConnectionUrl(){
          return url+serverName+":"+portNumber+";databaseName="+databaseName+";selectMethod="+selectMethod+";";
     }
     
     private java.sql.Connection getConnection(){
          try{
               Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver"); 
               con = java.sql.DriverManager.getConnection(getConnectionUrl(),userName,password);
               if(con!=null) System.out.println("Connection Successful!");
          }catch(Exception e){
               e.printStackTrace();
               System.out.println("Error Trace in getConnection() : " + e.getMessage());
         }
          return con;
      }

     /*
          Display the driver properties, database details 
     */ 

     public void displayDbProperties(){
          java.sql.DatabaseMetaData dm = null;
          java.sql.ResultSet rs = null;
          try{
               con= this.getConnection();
               if(con!=null){
                    dm = con.getMetaData();
                    System.out.println("Driver Information");
                    System.out.println("\tDriver Name: "+ dm.getDriverName());
                    System.out.println("\tDriver Version: "+ dm.getDriverVersion ());
                    System.out.println("\nDatabase Information ");
                    System.out.println("\tDatabase Name: "+ dm.getDatabaseProductName());
                    System.out.println("\tDatabase Version: "+ dm.getDatabaseProductVersion());
                    System.out.println("Avalilable Catalogs ");
                    rs = dm.getCatalogs();
                    while(rs.next()){
                         System.out.println("\tcatalog: "+ rs.getString(1));
                    } 
                    rs.close();
                    rs = null;
                    closeConnection();
               }else System.out.println("Error: No active Connection");
          }catch(Exception e){
               e.printStackTrace();
          }
          dm=null;
     }     
     
     private void closeConnection(){
          try{
               if(con!=null)
                    con.close();
               con=null;
          }catch(Exception e){
               e.printStackTrace();
          }
     }
     public static void main(String[] args) throws Exception
       {
          Connect myDbTest = new Connect();
          myDbTest.displayDbProperties();
       }
}

				
이 코드가 성공적으로 실행되면 다음과 비슷하게 출력됩니다.
Connection Successful!
Driver Information
        Driver Name: SQLServer
        Driver Version: 2.2.0022

Database Information
        Database Name: Microsoft SQL Server
        Database Version: Microsoft SQL Server  2000 - 8.00.384 (Intel X86)
        May 23 2001 00:02:52
        Copyright (c) 1988-2000 Microsoft Corporation
        Desktop Engine on Windows NT 5.1 (Build 2600: )

Avalilable Catalogs
        catalog: master
        catalog: msdb
        catalog: pubs
        catalog: tempdb
					
맨 위로

기본 연결 문제 해결

SQL Server에 연결할 때 나타날 수 있는 일반적인 오류 메시지는 다음과 비슷합니다.
java.sql.SQLException: [Microsoft][SQLServer 2000 Driver for JDBC][SQLServer]'user' 사용자가 로그인하지 못했습니다. 이유: 트러스트된 SQL Server 연결과 관련되지 않았습니다.

이 오류 메시지는 SQL Server 2000 인증 모드가 Windows 인증 모드로 설정된 경우 나타납니다. JDBC용 Microsoft SQL Server 2000 드라이버는 Windows NT 인증을 사용하여 연결하는 작업을 지원하지 않습니다. SQL Server의 인증 모드를 Windows 인증과 SQL Server 인증을 모두 허용하는 혼합 모드로 설정해야 합니다.


java.sql.SQLException: [Microsoft][SQLServer 2000 Driver for JDBC]이 버전의 JDBC 드라이버는 Microsoft SQL Server 2000만을 지원합니다. SQL Server 2000으로 업그레이드하거나 다른 버전의 드라이버를 지정하십시오.

이 오류 메시지는 SQL Server 2000 이전의 SQL Server 버전에 연결하려는 경우 나타납니다. JDBC용 Microsoft SQL Server 2000 드라이버는 SQL Server 2000에서만 연결을 지원합니다.