Project2011.10.06 15:17
우연히 동기의 질문에 오랫만에 다시 YHGCommunication 과 관련된 글을 확인하게 되었습니다.

잘 모르고 빨리 만들다 보니 허접한 코드가 되어 버렸습니다.
이벤트 방식의 메세지 처리, 객체 직렬화 메세지 전송 등 비 효율적인 면이 많이 있었습니다.
이 부분을 좀 더 수정해서 새로운 버전의 YHGComm 을 새로 만들까 하는 욕심이 생깁니다.


주요 수정 사항
1. 받은 메세지 처리 방식 수정
2. 전송되는 메세지 구조 수정
3. 서버에서 다양한 사용자 정보를 관리
4. 사용자 고유 번호 할당
5. Android 에서 UI thread 오류 없이 쉽게 동작

기대 효과
1. 많은 수의 메세지 클래스가 줄어듬
2. 조금 더 효율적인 전송 메세지 크기
3. 서버의 확장성
4. 타 플랫폼(Android)와의 호환성
5. 오류 수정

현재 메세지 처리 방식과 사용자 고유방식 관리에 대해서는 어느 정도 구상이 끝났습니다.
문제가 되는 부분은 전송 메세지 구조입니다. 최소한으로 전송하기 위해 어떻게 해야할지 고민이 되고 있습니다. 
모르는 것이 많다 보니 생각할 것도 많습니다.

언제 작업하고 언제 완성될지 모르겠지만 꼭 해볼렵니다.
이걸 실제로 사용하는 분이 있는지는 모르겠지만...  
신고
Posted by 초프(초보 프로그래머)
Project2010.05.13 10:41



  • 입장, 퇴장, 입장, 퇴장 등이 반복하여 일어났을때 생기는 클라이언트 번호 오류 문제를 해결 하였습니다.
  • 기본 제공되는 Message Class 를 최소한으로 줄였습니다.
  • 새로운 Message Class 가 생길때 마다 기존의 Message Class에서 type을 지정하던 것을 변경하여 Message Constants Class를 하나 만들었으며 다른 프로젝트에 적용할 때에는 예제와 같이 상속을 사용함
  • GUI (Swing) 을 이용한 예제


서버 화면으로 3개로 구분하여 로그가 나오게 하였습니다.

알림 / 보낸 메세지 / 받은 메세지 순으로 나옵니다.


클라이언트 화면입니다. 대화명은 테스트이므로 클라이언트 번호로 나오게 하였습니다.



채팅 서버
package yhg.comm.test;

import java.io.IOException;

import yhg.comm.message.Message;
import yhg.comm.server.CommClientManager;
import yhg.comm.server.CommServer;
import yhg.comm.server.ICommClientManagerEvent;
import yhg.comm.server.ICommServerEvent;

public class Server {
	private CommServer server;
	private ServerFrame frame;
	
	public Server(){
		frame = new ServerFrame();
		
		try {
			server = new CommServer(1234);
			setEvent();
			server.start();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		frame.setVisible(true);
	}
	
	private void setEvent(){
		server.setServerEvent(new ICommServerEvent(){
			public void onEnterClient(CommClientManager cm) {
				frame.addNoticeLog("["+cm.getNumber()+"] Enter");
				
				MSGEnterClient msgEC = new MSGEnterClient();
				msgEC.setClientNumber(cm.getNumber());
				try {
					server.sendAll(cm.getNumber(),msgEC);
				} catch (IOException e) {
					e.printStackTrace();
				}
			}

			public void onLeaveClient(CommClientManager cm) {
				frame.addNoticeLog("["+cm.getNumber()+"] Leave");

				MSGLeaveClient msgLC = new MSGLeaveClient();
				msgLC.setClientNumber(cm.getNumber());
				try {
					server.sendAll(cm.getNumber(),msgLC);
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		});
		
		server.setClientManagerEvent(new ICommClientManagerEvent(){
			public void onReceiveMessage(CommClientManager cm, Message msg) {
				frame.addReceiveLog("[R]["+cm.getNumber()+"]"+msg);
				
				switch(msg.getType()){
					case MSGConstants.type_MSGChat:
						MSGChat msgC = (MSGChat) msg;
						msgC.setClientNumber(cm.getNumber());
						try {
							server.sendAll(msg);
						} catch (IOException e) {
							e.printStackTrace();
						}
						break;
				}
			}

			public void onSendMessage(CommClientManager cm, Message msg) {
				frame.addSendLog("[S]["+cm.getNumber()+"]"+msg);
			}
		});
	}
	
	public static void main(String[] args){
		new Server();
	}
}

채팅 클라이언트
package yhg.comm.test;

import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.io.IOException;
import java.net.UnknownHostException;

import yhg.comm.client.CommClient;
import yhg.comm.client.ICommClientEvent;
import yhg.comm.message.Message;

public class Client {
	private CommClient client;
	private ClientFrame frame;
	
	public Client(){
		frame = new ClientFrame();
		
		try {
			client = new CommClient("127.0.0.1",1234);
			setEvent();
			client.start();
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		frame.setVisible(true);
	}
	
	private void setEvent(){
		client.setClientEvent(new ICommClientEvent(){
			public void onReceiveMessage(Message msg) {
				switch(msg.getType()){
					case MSGConstants.type_MSGEnterClient:
						MSGEnterClient msgEC = (MSGEnterClient) msg;
						frame.addNotice(msgEC.getClientNumber()+" 님 입장");
						break;
					case MSGConstants.type_MSGLeaveClient:
						MSGLeaveClient msgLC = (MSGLeaveClient) msg;
						frame.addNotice(msgLC.getClientNumber()+" 님 퇴장");
						break;
					case MSGConstants.type_MSGChat:
						MSGChat msgC = (MSGChat) msg;
						frame.addChat(msgC.getClientNumber()+"", msgC.getChat());
						break;
				}
			}

			public void onSendMessage(Message msg) {
				
			}
		});
		
		frame.getChatTextField().addKeyListener(new KeyListener(){
			public void keyPressed(KeyEvent arg0) {}

			public void keyReleased(KeyEvent evt) {
				if(evt.getKeyCode() != 10)	return ;
				
				String chat = frame.getChatTextField().getText();
				if(chat.length() == 0)	return;
				
				MSGChat msgC = new MSGChat();
				msgC.setChat(chat);
				
				try {
					client.send(msgC);
					frame.getChatTextField().setText("");
				} catch (IOException e) {
					e.printStackTrace();
				}
			}

			public void keyTyped(KeyEvent arg0) {}
		});
	}
	
	public static void main(String[] args){
		new Client();
	}
}

신고
Posted by 초프(초보 프로그래머)
Project2010.03.21 15:38

Message Class 가 좀 이상한거 같아서 바꿨음


그리고 CommClient가 비동기식으로만 동작한다는 것이 좀 문제가 될거 같아서 동기식으로 동작하는 것도 하나 만들어 보았습니다.

말만 거창한거 같네요... 아무것도 없는데 ㅋㅋㅋ

Message Class가 더 필요할 때에는 기존의 클래스를 상속받아서 사용하면 됩니다.

CommSynchronousClient

  • CommSynchronousClient(String address, int port)
    • 생성자이며 서버주소, 포트를 입력으로 받는다
  • boolean connect()
    • 서버에 접속하며 결과를 boolean 으로 리턴한다
  • Message getResponse(Message msg) throws IOException, ClassNotFoundException
    • 서버로 Message를 전송하고 응답 Message를 받는다


아주 간단히 만들었으며 직접 사용시 필요한 것만으로 만들어 보았습니다.


신고
Posted by 초프(초보 프로그래머)
Project2010.03.10 23:42

 




작성한 서버와 클라이언트 클래스를 이용해서 안드로이드와 데스크탑의 채팅을 만들어 보았습니다.

기존의 채팅서버와 채팅 클라이언트는 많이 변하지 않았고

안드로이드 클라이언트 추가가 주가 되었습니다.

안드로이드 실행시에는 아이피를 수정해주셔야 합니다~


신고
Posted by 초프(초보 프로그래머)
Project2010.03.10 23:20



만든지 얼마 지나지 않아... 혼자 테스트중 버그들이 발견되어 수정하였습니다.

  • 클라이언트 번호 할당 방식
  • MSGConnectServer 클래스 추가

 

신고
Posted by 초프(초보 프로그래머)
Project2010.03.10 11:12


  • yhg.comm.message
    • Message
      • int getType()
        • 클라이언트 종류 얻기
      • int getNumber()
      • void setNumber(int num)
        • 클라이언트 번호 설정
    • MSGBoolean extends Message
      • void setTrue()
      • void setFalse()
      • boolean get()
    • MSGChat extends Message
      • void setMessage(String str)
        • 채팅 메세지 설정
      • String getMessage()
    • MSGEnterClient extends Message
      • 서버에 클라이언트가 접속하였을 경우 이미 접속한 다른 클라이언트들에게 전송하는 메세지
    • MSGLeaveClient extends Message
      • 서버에 접속되어있는 클라이언트가 접속이 끊겼을 경우 다른 클라이언트들에게 전송하는 메세지
  • yhg.comm.server
    • ICommClientManagerEvent
      • void onReceiveMessage(CommClientManager cm, Message msg)
        • 서버가 클라이언트로부터 메세지를 받았을 때의 이벤트
      • void onSendMessage(CommClientManager cm, Message msg)
        • 서버가 클라이언트에게 메세지를 보낼때의 이벤트
    • ICommServerEvent
      • void onEnterClient(CommClientManager cm)
        • 서버에 클라이언트가 접속하였을 때의 이벤트
      • void onLeaveClient(CommClientManager cm)
        • 서버에서 클라이언트가 떠났을 때의 이벤트
    • CommClientManager
      • CommClientManager(CommServer server, Socket sock) throws IOException
      • void send(Message msg) throws IOException
        • 클라이언트에게 메세지 전송
      • Socket getSocket()
        • 클라이언트 소켓 얻기
      • InetAddress getLocalAddress()
        • 클라이언트 주소 얻기 (소켓을 얻어서 할경우 연결이 끊기면 정보를 잃기 때문에...)
    • CommServer
      • CommServer(int port) throws IOException
      • void setClientManagerEvent(ICommClientManagerEvent rec)
        • 이벤트 설정
      • void setServerEvent(ICommServerEvent apt)
        • 이벤트 설정
      • void sendAll(Message msg) throws IOException
        • 모든 클라이언트에게 메세지 전송
      • void sendAll(int num, Message msg) throws IOException
        • 지정한 클라이언트를 제외하고 메세지 전송
      • void sendTo(int num, Message msg) throws IOException
        • 지정한 하나의 클라이언트에게 메세지 전송
      • int getClientNumber(CommClientManager cm)
        • 클라이언트 번호 얻기
  • yhg.comm.client
    • ICommClientEvent
      • void onReceiveMessage(Message msg)
        • 클라이언트가 서버로부터 메세지를 받았을 경우 이벤트
      • void onSendMessage(Message msg)
        • 클라이언트가 서버로 메세지를 전송할 경우 이벤트
    • CommClient
      • CommClient(String address, int port) throws UnknownHostException, IOException
      • void send(Message msg) throws IOException
        • 서버로 메세지 전송
      • void setClientEvent(ICommClientEvent evt)
        • 이벤트 설정
      • Socket getSocket()
        • 서버 소켓 얻기

메세지를 추가할 경우 Message의 stataic변수를 만들면됩니다.
그리고 상속받는 Message클래스의 생성자에서 타입을 지정해야 합니다.

public으로 사용할수 있는 메소드등을 정리한 겁니다.

소스파일도 같이 올립니다.

신고
Posted by 초프(초보 프로그래머)
Project2010.03.09 19:09

스레드가 어려움
몇시간을 해도 통신이 잘 안됨
등등

프로젝트를 진행하면서 이런 문제점이 생길거 같아서 미리 이클래스를 작성 하였습니다.

현재로도 하나의 테스트 프로그램만을 작성해 보았으므로

다른 프로그램에서는 어떻게 동작할지는 예상할 수 없습니다 ^^;;

작성해본 간단한 채팅 프로그램의 서버와 클라이언트 입니다.

이것만 보셔도 대충 이해가 가실거라고 생각 됩니다.

--- Server ---

package yhg.comm.test;

import java.io.IOException;

import yhg.comm.message.MSGChat;
import yhg.comm.message.MSGEnterClient;
import yhg.comm.message.MSGLeaveClient;
import yhg.comm.message.Message;
import yhg.comm.server.CommClientManager;
import yhg.comm.server.CommServer;
import yhg.comm.server.ICommClientManagerEvent;
import yhg.comm.server.ICommServerEvent;

public class ChatServer {
	public static void main(String[] args){
		try {
			final CommServer server = new CommServer(1000);
			
			server.setClientManagerEvent(new ICommClientManagerEvent(){
				public void onReceiveMessage(CommClientManager cm, Message msg) {
					int sender = server.getClientNumber(cm);
					MSGChat content = (MSGChat)msg;
					
					System.out.println("["+cm.getSocket().getLocalAddress().toString()+"] receive Message : "+msg);
					
					switch(msg.getType()){
						case Message.type_MSGChat:
							MSGChat chat = new MSGChat();
							chat.setMessage("["+sender+"] "+content.getMessage());
							
							try {
								server.sendAll(sender, chat);
							} catch (IOException e) {
								e.printStackTrace();
							}
							break;
					}
				}
				
				public void onSendMessage(CommClientManager cm, Message msg) {
					
				}
			});
			server.setServerEvent(new ICommServerEvent(){
				public void onEnterClient(CommClientManager cm) {
					int newClient = server.getClientNumber(cm);
					MSGEnterClient ent = new MSGEnterClient();
					ent.setNumber(newClient);
					
					try {
						server.sendAll(newClient, ent);
						System.out.println("["+cm.getLocalAddress().toString()+"] Connect");
					} catch (IOException e) {
						e.printStackTrace();
					}
				}

				public void onLeaveClient(CommClientManager cm) {
					int clientNum = server.getClientNumber(cm);
					MSGLeaveClient ent = new MSGLeaveClient();
					ent.setNumber(clientNum);
					
					try {
						server.sendAll(clientNum, ent);
						System.out.println("["+cm.getLocalAddress().toString()+"] Disconnect");
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			});
			
			server.start();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}

--- Client ---

package yhg.comm.test;

import java.io.IOException;
import java.net.UnknownHostException;
import java.util.NoSuchElementException;
import java.util.Scanner;

import yhg.comm.client.CommClient;
import yhg.comm.client.ICommClientEvent;
import yhg.comm.message.MSGChat;
import yhg.comm.message.MSGEnterClient;
import yhg.comm.message.MSGLeaveClient;
import yhg.comm.message.Message;

public class ChatClient {
	public static void main(String[] args){
		try {
			CommClient client = new CommClient("127.0.0.1",1000);
			client.setClientEvent(new ICommClientEvent(){
				public void onReceiveMessage(Message msg) {
					try{
						switch(msg.getType()){
							case Message.type_MSGEnterClient:
								MSGEnterClient enterMsg = (MSGEnterClient)msg;
								System.out.println("-- "+enterMsg.getNumber()+" 님이 입장 하였습니다 --");
								break;
							case Message.type_MSGLeaveClient:
								MSGLeaveClient leaveMsg = (MSGLeaveClient)msg;
								System.out.println("-- "+leaveMsg.getNumber()+" 님이 퇴장 하였습니다 --");
							case Message.type_MSGChat:
								MSGChat chat = (MSGChat)msg;
								System.out.println(chat.getMessage());
								break;
						}
					} catch(ClassCastException e){}
				}

				public void onSendMessage(Message msg) {
					
				}
			});
			client.start();
			
			Scanner scanner = new Scanner(System.in);
			
			while(true){
				String input = scanner.next();
				
				if(!input.equals("")){
					MSGChat chat = new MSGChat();
					chat.setMessage(input);
					client.send(chat);
				}
			}
		} catch (UnknownHostException e) {
		} catch (IOException e) {
		} catch (NoSuchElementException e){
		}
	}
}

문제점이 있으시면 바로바로 알려주세요~

신고
Posted by 초프(초보 프로그래머)
Project2010.03.09 18:59

팀프로젝트를 미리 준비하면서 만들어 본 통신 클래스...;

복잡했던 스레드 이런걸 단방에 해결해 주도록 만들었습니다.

1:n의 통신이 가능하게 하였으며... 중요 클래스를 건드리지 않고

이벤트를 작성하는 형식으로 만들어 보았습니다.

아직 부족한게 많고 허접할거 같지만... 


일단 처음으로 통신과정 중에 왔다 갔다할 객체를 메세지라고 하여 만들었습니다.

그리고 그 메세지 클래스를 상속받아서 구체적인 메세지들이 구현이 됩니다.

--- Message.Java ---

package yhg.comm.message;

import java.io.Serializable;

/**
 * 통신 중에 주고 받는 메세지
 * 
 * @author	Yoon HyunGook
 * @since	2010-03-08
 */
public class Message implements Serializable {
	private static final long serialVersionUID = 5795268628773097426L;
	private int number;
	private int type;

	/**
	 * 미리 정해진 메세지 타입
	 * 필요시에 추가 하면됨
	 */
	public final static int type_MSGTable = 1;
	public final static int type_MSGBoolean = 2;
	public final static int type_MSGChat = 3;
	public final static int type_MSGEnterClient = 4;
	public final static int type_MSGLeaveClient = 5;
	
	/**
	 * 생성자
	 * 
	 * @author	Yoon HyunGook
	 * @since	2010-03-08
	 */
	public Message(){
		type = 0;
	}
	
	/**
	 * 메세지 타입 설정
	 * 
	 * @author	Yoon HyunGook
	 * @since	2010-03-08
	 * 
	 * @param	메세지 타입
	 */
	protected void setType(int type){
		this.type = type;
	}
	
	/**
	 * 메세지 타입 얻기
	 * 
	 * @author	Yoon HyunGook
	 * @since	2010-03-08
	 * 
	 * @return	메세지 타입
	 */
	public int getType(){
		return type;
	}
	
	/**
	 * 클라이언트 번호 얻기
	 * 
	 * @author	Yoon HyunGook
	 * @since	2010-03-08
	 * 
	 * @return	클라이언트 번호
	 */
	public int getNumber(){
		return number;
	}
	
	/**
	 * 클라이언트 번호 설정
	 * 
	 * @author	Yoon HyunGook
	 * @since	2010-03-08
	 * 
	 * @param	클라이언트 번호
	 */
	public void setNumber(int num){
		this.number = num;
	}
	
	/**
	 * 객체 출력
	 * 
	 * @author	Yoon HyunGook
	 * @since	2010-03-08
	 * 
	 * @return	String
	 */
	public String toString(){
		return Integer.toString(getNumber());
	}
}


--- MSGChat.java ---
package yhg.comm.message;

/**
 * 채팅 메세지
 * 
 * @author	Yoon HyunGook
 * @since	2010-03-08
 */
public class MSGChat extends Message{
	private static final long serialVersionUID = 6997142338486570285L;
	private String message;
	
	/**
	 * 생성자
	 * 
	 * @author	Yoon HyunGook
	 * @since	2010-03-08
	 */
	public MSGChat(){
		setType(Message.type_MSGChat);
	}
	
	/**
	 * 전송 내용 설정
	 * 
	 * @author	Yoon HyunGook
	 * @since	2010-03-08
	 * 
	 * @param	전송 내용
	 */
	public void setMessage(String str){
		message = new String(str);
	}
	
	/**
	 * 전송 내용 얻기
	 * 
	 * @author	Yoon HyunGook
	 * @since	2010-03-08
	 * 
	 * @return	전송 내용
	 */
	public String getMessage(){
		return message;
	}
	
	/**
	 * 객체 출력
	 * 
	 * @author	Yoon HyunGook
	 * @since	2010-03-08
	 * 
	 * @return	String
	 */
	public String toString(){
		return getMessage();
	}
}


메세지 타입은 새로운 메세지가 생길때 마다 수동적으로 추가 해줘야 합니다.

type : 메세지의 종류를 구분
number : 클라이언트의 번호 (필요시만 사용)
신고
Posted by 초프(초보 프로그래머)
Programming/Android2010.02.01 14:55
http 프로토콜을 이용하여 통신을 하는 예제입니다.

주소에 해당되는 문서의 내용을 받아옵니다. 

여기서는 그냥 일반 html 문서서를 가져 오도록 해보았습니다.  
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;

public class Test extends Activity {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        TextView text = (TextView)findViewById(R.id.htmlText);
        
        URL url = null;
        
		try {
			url = new URL("http://family7914.cafe24.com");
        	HttpURLConnection http = (HttpURLConnection)url.openConnection();
        	BufferedReader in = new BufferedReader(new InputStreamReader(http.getInputStream()));
        	StringBuffer buffer = new StringBuffer();
        	
        	int c;
        	
        	while((c=in.read()) != -1){
        		buffer.append((char)c);
        	}
        	
        	text.setText(buffer);
        	
		} catch (MalformedURLException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
    }
}

 

코드를 실행해보면 위와 같은 결과가 나옵니다.

만약 결과가 나오지 않거나 오류가 발생한다면... 
처음으로 생각해 볼수 있는 것이 Manifest의 Permission 입니다. 
이건 우리가 어떤 기능을 사용할 것이다 라고 미리 알려주는 것입니다. 
그래야만 사용할 수 있거든요~
두번째는 제가 어리석어서 생긴 경우인데...
실행을 할 경우 문서의 용량이 크면 로딩이 길게 되어있습니다. 
그리고 그전에 디버깅을 위한 메세지가 뜨는데 그것을 그냥 오류로 생각해 버리는겁니다.
그러므로 메세지를 잘 확인하세요~ ^^;
신고
Posted by 초프(초보 프로그래머)