레이블이 smack인 게시물을 표시합니다. 모든 게시물 표시
레이블이 smack인 게시물을 표시합니다. 모든 게시물 표시

2013년 8월 12일 월요일

asmack(smack android library) 사용 시 참고 사항들

이전 블로그에서 이전 함 (원본 글 2013/08/12 작성)

asmack을 사용하다 생긴 사소한 사항들을 정리


[reconnect 상황에서 null pointer exception 발생 현상]

= 증상 설명
# precondition
  : XMPP connection이 기타 사유(network connection close, 동일 id로 타 deivce에서 login 등)으로 close 됨
  : MUC(Multi User Chat) extension을 사용함.

 - XMPP connection을 재 생성하고자 connect를 할 경우 아래와 같이 NullPointerException이 발생

08-12 18:57:36.783: W/System.err(598): java.lang.NullPointerException
08-12 18:57:36.783: W/System.err(598):  at org.jivesoftware.smackx.muc.MultiUserChat$1.connectionCreated(MultiUserChat.java:115)
08-12 18:57:36.783: W/System.err(598):  at org.jivesoftware.smack.XMPPConnection.initConnection(XMPPConnection.java:663)
08-12 18:57:36.783: W/System.err(598):  at org.jivesoftware.smack.XMPPConnection.connectUsingConfiguration(XMPPConnection.java:615)
08-12 18:57:36.783: W/System.err(598):  at org.jivesoftware.smack.XMPPConnection.connect(XMPPConnection.java:1034)

자세한 원인은 모르겠지만 여러번 테스트와 약간의 소스를 확인한 결과 
잘못된 connect 루틴을 사용한 사소한 문제로 발생된 것으로 판단.

Smack에서 제공하는 connect의 sample 예제는 다음과 같다.
일반적으로 connection configuration을 만들고 이를 사용하여 connect를 하고 login을 하게 됨.

 // Create the configuration for this new connection
ConnectionConfiguration config = new ConnectionConfiguration("jabber.org", 5222); config.setCompressionEnabled(true); config.setSASLAuthenticationEnabled(true); Connection connection = new XMPPConnection(config); // Connect to the server connection.connect(); // Log into the server connection.login("username", "password", "SomeResource"); .... // Disconnect from the server connection.disconnect();

하지만 이를 reconnect 상황에서 그래도 사용할 경우 위와 같은 현상이 발생하는 것으로 보이고
로그상에서의 표면적인 원인은 MultiUserChat에서 사용하는 ServiceDiscoveryManager의 instance가 null이라 발생함.
더 세부적인 원인으로는 새롭게 생성하려는 XMPPConnection이 network 상황들로 인해 제대로 configuration이 되지 않은 상태에서 connection을 시도하여 발생한 것으로 그냥 추측만 할 뿐.. 자세한 내용을 알려면 XMPP spec 과 smack initialize 코드를 봐야 함. ㅜㅜ 나중에...

그래서 임시적인 solution으로는 
reconnect 상황에서 connection이 null 이 아닌 경우에만 connection을 생성하여 NullPointerException을 방지하고
불필요한 login도 줄여서 reconnect 함. 대충 아래와 같이?

 if(null == conn_){
connection = new XMPPConnection(config);
}
connection.connect();
if(false == conn_.isAuthenticated()){
connection.login(_info_.getXmppId(), password);
}


** 추가로 재접속 시에 connection이 끊겼다고 해서 굳이 disconnect를 할 필요가 없다.
smack에서는 자동적으로 connection을 timout을 조정하면서 재 연결하려는 시도를 하고 있고 이를 좀 더 즉각적으로 하기 위해서는 connect를 명시적으로 호출할 경우 재접속 시도는 cancel되고 바로 connect를 시도하게 된다. 
이 때 disconnect를 호출하게되면 재접속 시도를 명시적으로 cancel하게 하는 것이라 완전히 종료를 위해서만 사용하는게 맞겠음.

 Connections can be reused between connections. This means that an Connection may be connected, disconnected and then connected again. Listeners of the Connection will be retained accross connections.

If a connected Connection gets disconnected abruptly then it will try to reconnect again. To stop the reconnection process, use disconnect(). Once stopped you can use connect() to manually connect to the server.

 By default Smack will try to reconnect the connection in case it was abruptly disconnected. Use ConnectionConfiguration#setReconnectionAllowed(boolean) to turn on/off this feature. The reconnection manager will try to immediately reconnect to the server and increase the delay between attempts as successive reconnections keep failing. 
In case you want to force a reconnection while the reconnetion manager is waiting for the next reconnection, you can just use Connection#connect() and a new attempt will be made. If the manual attempt also failed then the reconnection manager will still continue the reconnection job.



[Multi User Chat extension을 사용하여 채팅 시 메시지 중복 수신 현상]

# precondition
  : XMPP connection이 close 된 후 재 연결된 상태
  : MUC(Multi User Chat) extension을 사용하여 채팅 중

- 사용자가 전송한 메세지가 중복으로 수신되는 현상

좀 바보같은 이유때문에 발생한 현상이지만 그래도 XMPP spec과 smack library의 특성으로 인해 충분히 발생할 상황이라 정리

multiuser chat에서의 join 예제를 보면 다음과 같고 
// Create a MultiUserChat using a Connection for a room
<pre style="font-family: 'courier new', monospaced;">      MultiUserChat muc2 = new MultiUserChat(conn1, "myroom@conference.jabber.org");

      // User2 joins the new room using a password
      // The room service will decide the amount of history to send
      muc2.join("testbot2", "password");</pre>
smack API reference에서는 join류 API들을 다음과 같이 설명하고 있음.
bold체로 되어있는 부분에서는 join을 호출하면 기존 muc room을 leave하고 join한다고 되어 있음.

 join
public void join(java.lang.String nickname) throws XMPPException

Joins the chat room using the specified nickname. If already joined using another nickname,this method will first leave the room and then re-join using the new nickname.The default timeout of Smack for a reply from the group chat server that the join succeeded will be used. After joining the room, the room will decide the amount of history to send.



<dl style="color: rgb(0, 0, 0); font-family: Gulim; font-size: medium; line-height: normal;">
Parameters:
nickname - the nickname to use.
Throws:
XMPPException - if an error occurs joining the room. In particular, a 401 error can occur if no password was provided and one is required; or a 403 error can occur if the user is banned; or a 404 error can occur if the room does not exist or is locked; or a 407 error can occur if user is not on the member list; or a 409 error can occur if someone is already in the group chat with the same nickname.
</dl>


하지만 asmack을 사용해서 테스트 해보니 좀 이상하다.
leave가 제대로 안되는 것인지는 모르겠지만 
connection이 재설정되는 상황에서 
 - re-join을 하지 않을 경우 메세지 송수신이 되지 않고
 - leave를 명시적으로 호출하지 않고 re-join을 하게 될 경우 메세지가 중복되어 수신된다.
 - leave를 명시적으로 호출하고 re-join을 하게 될 경우 메세지가 정상적으로 하나만 수신된다.

암튼 내가 잘못 코딩한것일 수 도 있을 수도 있으니 좀 더 테스트는 필요하겠음.



2013년 5월 29일 수요일

Smack library의 connection 관련 참고 사항

이전 블로그에서 이전 함 (원본 글 2013/05/29 작성)

Android에서 smack library (아마도 asmack https://github.com/Flowdalic/asmack)를 사용하다면
connection 시 암호화 관련 사항(SASL authentication )에서 exception이 발생하거나
connection 시 timeout 이 걸릴때가 많다.

SASL authentication에 대해서는 Android내부의 subsystem을 사용해서 처리하도록 하기도 하지만
별 소용이 없는 것 같고 connection timeout 관련해서는 reconnect에서 문제가 생기는 것으로 보이지만 정확한 원인을 찾지는 못하겠다.

그래서 일단 처리하는 방법은
connection timeout을 줄이고 SASL authentication을 disable 시키는 방법이다.
자세한 설명 및 코드는 아래 링크를 참고.


38        SmackConfiguration.setPacketReplyTimeout(packetReplyTimeout);
39         
40        config = new ConnectionConfiguration(server, port);
41        config.setSASLAuthenticationEnabled(false);
42        config.setSecurityMode(SecurityMode.disabled);
43         
44        connection = new XMPPConnection(config);
45        connection.connect();

2013년 4월 28일 일요일

Sample chat with Smack library

이전 블로그에서 이전 함 (원본 글 2013/04/28 작성)

* Smack library로 sample chat을 실행 해 봄.
* Server는 OpenFire 설치 후 계정 설정 후 테스트

* Android상에서 테스트 하려면 아래의 경로의 asmack.jar를 사용해야 한다.
* XMPP의 id는 email과 비슷하다는 것을 꼭 유념하고 상대 id에 domain이 포함되도록 해야한다.
  포함되지 않으면 메세지 전송 시 remote server not found (404) error를 리턴한다.




Smack library의 simple sample은 아래에서 보여주고 있음.
 // Create a connection to the igniterealtime.org XMPP server. Connection con = new XMPPConnection("igniterealtime.org");
 // Connect to the server
 con.connect();
 // Most servers require you to login before performing other tasks.
 con.login("jsmith", "mypass");
 // Start a new conversation with John Doe and send him a message.
 Chat chat = connection.getChatManager().createChat("jdoe@igniterealtime.org", new MessageListener() {
 
public void processMessage(Chat chat, Message message) { // Print out any messages we get back to standard out. System.out.println("Received message: " + message); } }); chat.sendMessage("Howdy!"); // Disconnect from the server con.disconnect();


XMPP기반 서버에 연결하기 위해 XMPPConnection을 사용하여 host name을 명시하고 connect하는 코드
혹시 IP, port base로 연결을 위해서는 ConnectionConfiguration을 사용해야 함.

 // Create a connection to the igniterealtime.org XMPP server.
 Connection con = new XMPPConnection("igniterealtime.org");
 // Connect to the server
 con.connect();


서버에서 Authentication이 필요하다면 계정 정보를 입력

 // Most servers require you to login before performing other tasks.
 con.login("jsmith", "mypass");


1:1 chatting에서는 상대방 계정(jdoe@igniterealtime.org)을 명시 필요.
Group chatting을 위해서는 MultiUserChat을 사용해야 함.. 관련 내용은 아래 링크를 참고

 // Start a new conversation with John Doe and send him a message.
 Chat chat = connection.getChatManager().createChat("jdoe@igniterealtime.org", new MessageListener() {
 
public void processMessage(Chat chat, Message message) { // Print out any messages we get back to standard out. System.out.println("Received message: " + message); } });



Message는 다음과 같은 Type을 가지고 각 Type별 mandatory, optional field는 다음과 같다. 

 Represents XMPP message packets. A message can be one of several types:
  • Message.Type.NORMAL -- (Default) a normal text message used in email like interface.
  • Message.Type.CHAT -- a typically short text message used in line-by-line chat interfaces.
  • Message.Type.GROUP_CHAT -- a chat message sent to a groupchat server for group chats.
  • Message.Type.HEADLINE -- a text message to be displayed in scrolling marquee displays.
  • Message.Type.ERROR -- indicates a messaging error.
For each message type, different message fields are typically used as follows:

 Message type
FieldNormalChatGroup ChatHeadlineXMPPError
subjectSHOULDSHOULD NOTSHOULD NOTSHOULD NOTSHOULD NOT
threadOPTIONALSHOULDOPTIONALOPTIONALSHOULD NOT
bodySHOULDSHOULDSHOULDSHOULDSHOULD NOT
errorMUST NOTMUST NOTMUST NOTMUST NOTMUST




뭐 message를 전달하고 connection을 close하는 코드...


  chat.sendMessage("Howdy!");
// Disconnect from the server con.disconnect();



사실 상 XMPP spec를 모르는 상태에서도 library를 사용하는데는 별 무리가 없을 정도로 쉽고 일관적이다.
Documentation이나 web 상에서 관련 질문이나 답변도 쉽게 찾을 수 있고..


아래는 작성해서 테스트 해본 샘플 코드...
Smack library에서 제공하는 것과 별 차이 없다.
* 다만 OpenFire의 default port는 5222


ChatRoom c1 = new ChatRoom("localhost", 5222);
c1.setAuthInfo("h@c-portable", "1234");
c1.talkWith("c@c-portable", new MessageListener() {
public void processMessage(Chat chat, Message message) {
System.out.print("h is received message => ");
if(message.getSubject() != null)
{
System.out.println(message.getSubject() + " : ");
}
System.out.println(message.getBody());
}
});

ChatRoom c2 = new ChatRoom("localhost", 5222);

c2.setAuthInfo("c@c-portable", "1234");
c2.talkWith("h@c-portable", new MessageListener() {
public void processMessage(Chat chat, Message message) {
System.out.print("c is received message => ");
if(message.getSubject() != null)
{
System.out.println(message.getSubject() + " : ");
}
System.out.println(message.getBody());
}
});




 class ChatRoom {
private Connection connection;
private Chat chat;
private String accountID;
private String accountPW;

ChatRoom(String host, int port) {
connection = new XMPPConnection(new ConnectionConfiguration(host, port));
}

void setAuthInfo(String id, String pw) {
accountID = id;
accountPW = pw;
}

void talkWith(String NameOfPersonTalkWith, MessageListener messageListener) {
try {
connection.connect();
connection.login(accountID, accountPW);

chat = connection.getChatManager().createChat(NameOfPersonTalkWith,
messageListener);
chat.sendMessage("Entered!");
} catch (XMPPException e) {
e.printStackTrace();
}

}

void send(String msg) {
if (chat != null) {
try {
chat.sendMessage(msg);
} catch (XMPPException e) {
e.printStackTrace();
}과
}
}

void close() {
connection.disconnect();
}

}

실행 결과
 h is received message => Entered!
c is received message => Entered!
h is received message => msg send 2
c is received message => msg send 3
h is received message => msg send 4
c is received message => msg send 5
h is received message => msg send 6
c is received message => msg send 7
h is received message => msg send 8
c is received message => msg send 9
h is received message => msg send 10