반응형

자바에서 DatagramSocket (UDP 소켓)을 파라미터 없이 생성하거나, 포트 번호만 파라미터로 지정하면 특정 네트워크 인터페이스에 지정되는 것이 아니고, 인터페이스에 상관 없이 포트 번호만 맞으면 패킷을 주고 받는 식으로 작동한다.


실제로 인터넷에 검색해 보면 UDP 소켓을 특정한 네트워크 인터페이스에 지정하고자 하는 요구가 상당히 많은 것을 알 수 있다. 하지만 컴퓨터에 네트워크 인터페이스가 여러 개 있을 경우에 UDP 소켓에서 특정 네트워크 인터페이스를 지정하는 것이 Java Virtual Machine (JVM) 입장에서는 자연스럽지 않을 수 있다. "패킷이 컴퓨터 밖으로 나갈 때, 이를 내보낼 가장 적합한 네트워크 인터페이스를 선택한다"는 보편적인 논리에서 보면 이것은 원래 운영체제(OS)에 내장된 라우팅 프로토콜의 역할이기 때문에, User mode에 있는 JVM 위에서 돌아가는 자바 어플리케이션이 이 역할에 일부분 끼어드는 것처럼 보이는 것이다. 아무튼 자바 어플리케이션 입장에서 불가능하지는 않지만, 바람직한 구성은 아닐 수도 있다.


어쨌든 패킷을 받는 서버 입장에서 DatagramSocket을 생성할 때, 생성자(constructor) 파라미터에 특정 IP 주소와 포트 번호를 모두 파라미터로 넘김으로써 특정 네트워크 인터페이스에서만 패킷을 받도록 지정할 수는 있다.


byte[] receiveData = new byte[1024];

String ip = "192.168.3.4"; // 현재 컴퓨터의 특정 네트워크 인터페이스에 할당된 IP 주소

String port = 54321; // 원하는 포트 번호

DatagramSocket socket = new DatagramSocket(new InetSocketAddress(ip, port));

DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);

socket.receive(receivePacket);


패킷을 보내는 클라이언트 입장에서는, 그냥 DatagramPacket을 보낼 때 destination IP 주소를 어떻게 지정하느냐에 따라서 특정 네트워크 인터페이스를 통한 전송 여부가 결정된다. 이것은 운영체제의 라우팅 테이블 설정을 따라간다.


byte[] sendData = new byte[1024];

// sendData에 대한 메세지 생성 작업 수행

String ip = "192.168.3.6";

int port = 54321;

DatagramSocket clientSocket = new DatagramSocket();

DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, InetAddress.getByName(ip), port);

clientSocket.send(sendPacket);


예를 들어, 리눅스의 route 설정이 유선랜 eth0과 무선랜 wlan0 모두 활성화되어 있고, default (0.0.0.0)에 대한 게이트웨이가 유선랜(eth0) 게이트웨이로 설정되어 있다고 가정하자. 이 때, 로컬 네트워크 밖에 있는 인터넷 상의 어떤 기기에 UDP 패킷을 보내고자 한다면, 자바 어플리케이션은 eth0을 통해서 패킷을 보낼 것이다. 이런 경우에 반드시 무선랜 wlan0으로 패킷이 나가도록 하고 싶으면 리눅스에서 route 명령으로 wlan0을 통해서 인터넷으로 나가는 경로를 설정해 주어야 한다.


문제는 서버의 입장에서 DatagramSocket에 특정 IP 주소를 지정해 버리면, 브로드캐스트(broadcast) 메세지를 받지 못하는 문제가 생긴다. 상대편에서 정확하게 IP 주소를 지정해서 보내는 경우에만 패킷을 받을 수 있고, 테스트해 본 결과, 가령 192.168.3.255 와 같은 destination IP로 전송되는 패킷은 받지 못했다.


이 경우에는 서버의 입장에서는 특정한 IP 주소를 지정하지 말고 모든 인터페이스와 모든 주소에 대해서 받을 수 있도록 해 두고, 일단 패킷을 받고 나서 전송자의 IP 주소 대역을 보고 판단하는 루틴을 코드에 추가하는 것이 좋을 것 같다. (이것이 best solution인지는 알 수 없으나, 현재로써는 이것이 working solution이다.)


byte[] receiveData = new byte[1024];

String port = 54321; // 원하는 포트 번호

DatagramSocket socket = new DatagramSocket(port);

DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);

socket.receive(receivePacket);




반응형
블로그 이미지

Bryan_

,
반응형

OS: Windows 8.1 (64-bit)

Java: 1.8.0_60

Eclipse: Mars (64-bit)

프로젝트가 사용하는 JRE System Library: JavaSE-1.7



이클립스에서 프로젝트를 Export 명령으로 Runnable JAR 파일을 만들고 다른 환경(예를 들면 리눅스가 돌아가는 보드PC 같은 곳)에서 실행하려고 했는데 다음과 같은 에러가 발생했다:




Exception in thread "main" java.lang.UnsupportedClassVersionError: [실행하려는 클래스 이름] : Unsupported major.minor version 52.0

        at java.lang.ClassLoader.defineClass1(Native Method)

        at java.lang.ClassLoader.defineClass(ClassLoader.java:788)

        at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:14                                                                    2)

        at java.net.URLClassLoader.defineClass(URLClassLoader.java:447)

        at java.net.URLClassLoader.access$100(URLClassLoader.java:71)

        at java.net.URLClassLoader$1.run(URLClassLoader.java:361)

        at java.net.URLClassLoader$1.run(URLClassLoader.java:355)

        at java.security.AccessController.doPrivileged(Native Method)

        at java.net.URLClassLoader.findClass(URLClassLoader.java:354)

        at java.lang.ClassLoader.loadClass(ClassLoader.java:424)

        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)

        at java.lang.ClassLoader.loadClass(ClassLoader.java:357)

        at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:482)



일단 .jar 파일을 돌리려는 대상 보드PC (Odroid)에 설치된 자바는 OpenJDK고 버전이 1.7.0_25다.
이클립스 프로젝트에서 JRE 라이브러리는 비록 1.7로 되어 있었지만, 이클립스가 실제로 사용하는 컴파일러는 1.8로 되어 있었기에 빌드한 버전이 맞지 않아서 생기는 문제였다.

해결하려면 Window > Preferences 에 가서,
Java > Compiler를 선택하고, 오른쪽에 보이는 화면에서 Compiler compliance level을 1.7로 설정한다.


그러면 현재 프로젝트들을 모두 새로 빌드할 것이다.
그 뒤에 다시 Runnable JAR 파일을 만들어서 실행하면 될 것이다.



반응형
블로그 이미지

Bryan_

,
반응형

Java 버전이 올라가면서 향상된 for 루프문(enhanced for loop) 문법이 나왔는데, 이것으로는 배열을 초기화할 수 없다는 사실을 발견했다.


예를 들어, 10개짜리 문자열 배열(String array)을 만들고 향상된 for 루프를 쓰게 되면 아래와 같이 코딩할 수는 있는데,

String[] strArray = new String[10];

for( String str : strArray ){

str = "test_string";

}


for( String str: strArray ){

System.out.println(str); // NullPointerException 발생

}


위와 같이 하면 배열의 각 문자열이 초기화되지 않고 여전히 null 상태로 존재하게 된다.

번거롭더라도 배열 초기화는 index number를 사용해서 아래와 같이 명시적으로 해 주는 것이 좋다.


for(int i=0; i<strArray.length; i++){

strArray[i] = "test_string";

}


이 문제는 String 뿐만 아니라 객체 배열(Object array), 기본 타입 배열(primitive type array; 예를 들어, int[], double[])에서도 마찬가지로 발생한다.

다만 기본 타입(primitive type) 배열의 경우에는 향상된 for 루프를 쓰더라도 NullPointerException은 발생하지 않지만, 값들이 0인 상태로 바뀌지 않은 채 존재할 것이다.



반응형
블로그 이미지

Bryan_

,
반응형

Development environment: Oracle Java 1.7


This post shows a case when the "createTextNode" method with the null parameter causes "javax.xml.transform.TransformerException: java.lang.NullPointerException" without detailed information (difficult to find the exact line of code causing NullPointerException).


Using DocumentBuilderFactory and TransformerFactory, users can create an XML document with user-defined tags. Usually, I convert a user-defined object into the XMlL representation in String using DocumentBuilderFactory and TransformerFactory. Below is a simple example which causes a NullPointerException:


/* Person.java */

public class Person {

String name;

String phone;


public Person(){ }


public String getName() {

return name;

}

public void setName(String name) {

this.name = name;

}

public String getPhone() {

return phone;

}

public void setPhone(String phone) {

this.phone = phone;

}

}


/* TestMain.java */

public class TestMain {

public static void main(String[] args){

Person person = new Person();

// person.setName("Smith"); // commented for making NullPointerException

person.setPhone("01011112222");


try {

DocumentBuilderFactory dbfac = DocumentBuilderFactory.newInstance();

DocumentBuilder docBuilder = dbfac.newDocumentBuilder();

Document doc = docBuilder.newDocument();

// root (person) tag

Element ePerson = doc.createElement("person");

doc.appendChild(ePerson);

// name tag

Element eName = doc.createElement("name");

ePerson.appendChild(eName);

Text nameText = doc.createTextNode(person.getName());

eName.appendChild(nameText);

// phone tag

Element ePhone = doc.createElement("phone");

ePerson.appendChild(ePhone);

Text phoneText = doc.createTextNode(person.getPhone());

ePhone.appendChild(phoneText);

//set up a transformer

TransformerFactory transfac = TransformerFactory.newInstance();

Transformer trans = transfac.newTransformer();

trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");

trans.setOutputProperty(OutputKeys.INDENT, "yes");

//create string from xml tree

StringWriter sw = new StringWriter();

StreamResult result = new StreamResult(sw);

DOMSource source = new DOMSource(doc);

trans.transform(source, result); // the line where the Exception indicates

// print the XML output

System.out.println(sw.toString());

} catch (Exception e) {

e.printStackTrace();

}

}

}


As the TestMain.java shows, if the member variable "name" in the Person class is not set, it generates an exception like below:

ERROR:  ''

javax.xml.transform.TransformerException: java.lang.NullPointerException

at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(Unknown Source)

at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(Unknown Source)

at org.usera.xml.TestMain.main(TestMain.java:39)

Caused by: java.lang.NullPointerException

at com.sun.org.apache.xml.internal.serializer.ToUnknownStream.characters(Unknown Source)

at com.sun.org.apache.xalan.internal.xsltc.trax.DOM2TO.parse(Unknown Source)

at com.sun.org.apache.xalan.internal.xsltc.trax.DOM2TO.parse(Unknown Source)

at com.sun.org.apache.xalan.internal.xsltc.trax.DOM2TO.parse(Unknown Source)

at com.sun.org.apache.xalan.internal.xsltc.trax.DOM2TO.parse(Unknown Source)

at com.sun.org.apache.xalan.internal.xsltc.trax.DOM2TO.parse(Unknown Source)

at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transformIdentity(Unknown Source)

... 3 more

---------

java.lang.NullPointerException

at com.sun.org.apache.xml.internal.serializer.ToUnknownStream.characters(Unknown Source)

at com.sun.org.apache.xalan.internal.xsltc.trax.DOM2TO.parse(Unknown Source)

at com.sun.org.apache.xalan.internal.xsltc.trax.DOM2TO.parse(Unknown Source)

at com.sun.org.apache.xalan.internal.xsltc.trax.DOM2TO.parse(Unknown Source)

at com.sun.org.apache.xalan.internal.xsltc.trax.DOM2TO.parse(Unknown Source)

at com.sun.org.apache.xalan.internal.xsltc.trax.DOM2TO.parse(Unknown Source)

at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transformIdentity(Unknown Source)

at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(Unknown Source)

at com.sun.org.apache.xalan.internal.xsltc.trax.TransformerImpl.transform(Unknown Source)

at org.usera.xml.TestMain.main(TestMain.java:39)


The exception guides me to "trans.transform(source, result)", but it doesn't seem to contain any null parameter. This means the other parts in the XML document object contains null values.

In this example, we can quickly find that the member variable "name" should be initialized with a valid String because it is a very simplified code. However, if I handle complex  objects including tens of member variables, it is not easy to find which variables are set to null.


Thus, I tried to make a wrapping method which make the "createTextNode" always get a "not null" parameter. The simple solution might be like below:

public static Text createTextNodeWithoutNull(Document doc, String str)

{

Text textNode;

if(str != null) textNode = doc.createTextNode(str);

else textNode = doc.createTextNode("null");

return textNode;

}


Then the modified code (a part of TestMain) is:

// name tag

Element eName = doc.createElement("name");

ePerson.appendChild(eName);

Text nameText = createTextNodeWithoutNull (doc, person.getName());

eName.appendChild(nameText);

// phone tag

Element ePhone = doc.createElement("phone");

ePerson.appendChild(ePhone);

Text phoneText = createTextNodeWithoutNull (doc, person.getPhone());

ePhone.appendChild(phoneText);


Likewise, the XML Document object won't contain any null values inside.



반응형
블로그 이미지

Bryan_

,
반응형
Java에서 사용자가 정의한 클래스(Class)를 가지고 서로 같은 것인지 비교해야 할 때가 있다.
Java의 모든 클래스는 Object 클래스를 상속하고, Object 클래스가 기본으로 가지고 있는 메쏘드 중에서 equals와 hashCode가 있다.
예를 들어, 학교의 수업관리 시스템에서 학생(Student) 클래스를 사용한다고 가정하고 Student 클래스를 정의하면:
public class Student {
    private int studentId;
    private String name;
    private String major;
   
    public int getStudentId() {
        return studentId;
    }
    public void setStudentId(int studentId) {
        this.studentId = studentId;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getMajor() {
        return major;
    }
    public void setMajor(String major) {
        this.major = major;
    }
}


위 클래스를 사용하여 아래와 같이 똑같은 학생을 두 개의 다른 인스턴스(instance)로 만들어 비교해 보면:
public static void main(String[] args){
        Student s1 = new Student();
        s1.setStudentId(20101234);
        s1.setName("Gildong Hong");
        s1.setMajor("Computer Science");

        Student s2 = new Student();
        s2.setStudentId(20101234);
        s2.setName("Gildong Hong");
        s2.setMajor("Computer Science");
       
        System.out.println(s1.equals(s2));    // false
}
실행 결과는 false가 나온다.

위 상황에서 두 학생이 학번이 같고 이름도 같고 전공도 같기 때문에 의미상 똑같은 객체라는 것을 Java가 인식할 수 있게 하기 위한 방법이 equals 메쏘드를 override하는 것이다. Student 클래스에 equals를 override하여 구현하면:
public class Student {
    private int studentId;
    private String name;
    private String major;
   
    public int getStudentId() {
        return studentId;
    }
    public void setStudentId(int studentId) {
        this.studentId = studentId;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getMajor() {
        return major;
    }
    public void setMajor(String major) {
        this.major = major;
    }
   
    @Override
    public boolean equals(Object o){
        Student s = (Student) o;
        if(studentId == s.getStudentId()
            && name.equals(s.getName())
            && major.equals(s.getMajor())){
            return true;
        } else {
            return false;
        }
    }
}


이 클래스를 가지고 다시 equals 메쏘드로 위의 s1과 s2를 비교하면 같은 것으로 인식할 수 있다.
하지만 equals만 가지고는 HashMap, HashSet과 같이 key, value의 쌍으로 구성되는 자료구조에서는 제대로 작동하지 않는다. 이 때에는 hashCode 메쏘드를 추가로 override하여 구현하면, Student 클래스의 예와 같이 같은 학번, 이름, 전공을 가진 객체의 경우에는 동일한 key로 간주하도록 할 수 있다.
public class Student {
    private int studentId;
    private String name;
    private String major;
   
    public int getStudentId() {
        return studentId;
    }
    public void setStudentId(int studentId) {
        this.studentId = studentId;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getMajor() {
        return major;
    }
    public void setMajor(String major) {
        this.major = major;
    }
   
    @Override
    public boolean equals(Object o){
        Student s = (Student) o;
        if(studentId == s.getStudentId()
            && name.equals(s.getName())
            && major.equals(s.getMajor())){
            return true;
        } else {
            return false;
        }
    }
   
    @Override
    public int hashCode(){
        String s = ""+studentId+name+major;
        return s.hashCode();
    }
}


public static void main(String[] args){
        Student s1 = new Student();
        s1.setStudentId(20101234);
        s1.setName("Gildong Hong");
        s1.setMajor("Computer Science");

        Student s2 = new Student();
        s2.setStudentId(20101234);
        s2.setName("Gildong Hong");
        s2.setMajor("Computer Science");
       
        HashMap<Student, String> advisors = new HashMap<Student, String>();
        advisors.put(s1, "prof. Kim");              // s1 클래스를 key로 사용
        System.out.println(advisors.get(s2));   // s2 클래스를 key로 사용
}

위 예제는 같은 멤버변수 값을 가질 경우 같은 hashCode를 리턴하도록 hashCode를 override함으로써 s2 인스턴스를 가지고 "prof. Kim"을 얻을 수 있다.


반응형
블로그 이미지

Bryan_

,